Compare commits

..
80 Commits
Author SHA1 Message Date
naiba e54060e527 🐛 fix shit 2024-03-24 14:01:10 +08:00
github-actions[bot] ca14674c36 update contributors[no ci] 2024-03-24 05:34:24 +00:00
wang 1e27fb9413 修改default主题 (#334)
* 修改国旗图标为4*3的大国旗

* Update main.css
2024-03-24 13:34:08 +08:00
github-actions[bot] 460483fdef update contributors[no ci] 2024-03-24 05:33:33 +00:00
xykt 3a733c85ad 美化三网延迟界面 (#335)
* Update network.html

修复以下问题:
- 延迟默认最高300ms,超过后默认抹平
- 曲线上有很多ping值为0的无效散点,导致毛刺很多,干扰效果
- 图标的y轴比例失调,上方大片留白,大大降低了有效显示区域
- 默认只显示最近不到半小时左右的延迟表现,想看全天需要拖动,影响效果
- 曲线不显示极大极小值,不够直观

* Update config.go

修复了以下问题:
- 延迟默认最高300ms,超过后默认抹平
- 曲线上有很多ping值为0的无效散点,导致毛刺很多,干扰效果
- 图标的y轴比例失调,上方大片留白,大大降低了有效显示区域
- 默认只显示最近不到半小时左右的延迟表现,想看全天需要拖动,影响效果
- 曲线不显示极大极小值,不够直观

* Update network.html

美化了三网延迟界面:
1. 将Marker缩小降低曲线遮挡
2. 设置为曲线对应的颜色使其更加直观
3. 设置Marker透明度降低对画面的影响
4. 将延迟极小值的Marker倒置显示以快速区别极大值
5. 将超过MaxTCPPingValue的点删除
6. 显示丢包分布(细线),以及服务器断流区间(粗线)

* Update network.html

* Update network.html

* Update network.html

* Update network.html
2024-03-24 13:33:19 +08:00
naiba 7d85c4c4a5 Update dependencies and fix test assertions 2024-03-23 13:00:47 +08:00
HsukqiLee b1348eb545 网络监控页面相关优化 (#333)
* Update network.html

* Update network.html

* Update network.html
2024-03-23 12:40:38 +08:00
github-actions[bot] 753199a3b8 update contributors[no ci] 2024-03-23 04:19:22 +00:00
dependabot[bot] 64ba3cd0a7 Bump google.golang.org/protobuf from 1.31.0 to 1.33.0 (#332)
Bumps google.golang.org/protobuf from 1.31.0 to 1.33.0.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-23 12:19:03 +08:00
nap0o 193455c675 主题切换相关优化 (#331)
* angel-kanade模版增加主题切换功能

* Add files via upload

* 调整legend图例间距

* 渲染方式调整会canvas

* Update home.html

* Add files via upload

* Add files via upload

* Update footer.html

* Add files via upload

* Add files via upload

* 消除echarts折线上无效的0值

* 消除echarts折线的0值,替换为平均值

* 消除echarts折线图上的0值

* 修复MaxTCPPingValue取值错误
2024-03-04 09:36:17 +08:00
github-actions[bot] dfb08caae1 update contributors[no ci] 2024-03-02 07:20:48 +00:00
nap0o 7f7763606d 修复echarts不显示bug (#330)
* angel-kanade模版增加主题切换功能

* Add files via upload

* 调整legend图例间距

* 渲染方式调整会canvas

* Update home.html
2024-03-02 15:20:32 +08:00
naiba 77de1b44bc kkgithub 已失效 2024-02-28 23:30:59 +08:00
naiba da6a3463ed 后台关闭切换主题功能 2024-02-27 21:05:39 +08:00
github-actions[bot] 6fa149519d update contributors[no ci] 2024-02-27 12:43:12 +00:00
nap0o 8e67d3487e angel-kanade模版增加主题切换功能 (#329) 2024-02-27 20:42:56 +08:00
github-actions[bot] 8033c6a0b7 update contributors[no ci] 2024-02-26 12:59:18 +00:00
naiba abb108a83e 💄 限制最大宽度 2024-02-26 20:58:50 +08:00
Darc Z cf9d9bd248 Fix DDNS bugs and split up ddns module (#326)
* fix: webhook retry logic
fix: adjust record type based on ipv4 ipv6
refract: move ddns providers to a new module

* refract: move ddns module to pkg/
2024-02-26 11:49:28 +08:00
naiba 5e528c42c7 🐛 查看密码逻辑修复 2024-02-26 10:11:02 +08:00
github-actions[bot] daa7e03240 update contributors[no ci] 2024-02-26 01:31:35 +00:00
nap0o ab39782c78 fix bug (#328)
* fix bugs

1. 修复default主题mixin.js文件丢失
2. 修复主题默认值为后台设置值
3. default主题样式优化

* 修复前台切换 theme-custom 不生效
2024-02-26 09:31:16 +08:00
github-actions[bot] e24925b049 update contributors[no ci] 2024-02-25 15:35:34 +00:00
naiba 1d4245ee09 Update mixin.js file path in header.html 2024-02-25 23:35:06 +08:00
nap0o 8df863a3e0 优化default和serverstatus主题模版 (#327)
1.优化default主题模版
2.优化serverstatus主题模版
2024-02-25 23:16:57 +08:00
naiba 07e0382598 🚸 校验自定义主题、将 Theme 列表插入所有页面 2024-02-25 14:15:19 +08:00
naiba 8dd509aa08 🔒️ 增强 ping 历史 API 鉴权 2024-02-24 23:21:33 +08:00
naiba 99ac12c9fd ✨ 前端切换模板 2024-02-24 22:28:07 +08:00
github-actions[bot] ebc45f9b8f update contributors[no ci] 2024-02-24 13:10:48 +00:00
Darc Z 3b5ee464a7 Add DDNS support (#324)
* feat: add ddns updater framework

Note: no functionality implemented yet

* feat: add webhook ddns provider

* feat: update dashboard template

* fix: check nil and cron task string

* fix: webhook string formated with unexcepted param

* fix: webhook header split error

* feat: cloudflare ddns provider

* refract: move ddns update trigger into ReportSystemInfo

* lang: update other languages text

* fix: clear codes and logics

* fix: move update ddns to goroutine to avoid blocking

* fix: clear unused codes

* fix: update timestamp to prevent cache
2024-02-24 21:10:27 +08:00
github-actions[bot] c4b2c47beb update contributors[no ci] 2024-02-23 14:55:59 +00:00
1ridic 0835f13f28 优化安装逻辑 (#325)
* Optimize install process

If `getenforce` is not exist, then do not execute
it. This fixes the `command not found` error on
Arch Linux systems without SELinux installed.

* bump installer version
2024-02-23 22:55:42 +08:00
github-actions[bot] ee3cda8aed update contributors[no ci] 2024-02-21 12:19:50 +00:00
凌 604fc7d4c0 Arch 修复 nezha-agent 主目录遗留问题 (#323)
* Arch 修复 nezha-agent 主目录遗留问题

使用useradd -m创建的用户会产生主目录文件夹,使用userdel需要添加-r参数

* Update install_en.sh

* bump installer version
2024-02-21 20:19:34 +08:00
github-actions[bot] 5c549261dd update contributors[no ci] 2024-02-19 13:00:09 +00:00
HsukqiLee 00041c0ebd 增加了网络页面对 Neko Meui 主题的支持 (#322)
* 修改 Neko-mdui 主题的菜单栏

增加了“网络”选项卡

* 增加 Neko-mdui 主题的网络页面
2024-02-19 20:59:49 +08:00
xykt dbfea9a00b 对历史延迟图表进行了相关修复/美化工作 (#321)
* Update network.html

修复以下问题:
- 延迟默认最高300ms,超过后默认抹平
- 曲线上有很多ping值为0的无效散点,导致毛刺很多,干扰效果
- 图标的y轴比例失调,上方大片留白,大大降低了有效显示区域
- 默认只显示最近不到半小时左右的延迟表现,想看全天需要拖动,影响效果
- 曲线不显示极大极小值,不够直观

* Update config.go

修复了以下问题:
- 延迟默认最高300ms,超过后默认抹平
- 曲线上有很多ping值为0的无效散点,导致毛刺很多,干扰效果
- 图标的y轴比例失调,上方大片留白,大大降低了有效显示区域
- 默认只显示最近不到半小时左右的延迟表现,想看全天需要拖动,影响效果
- 曲线不显示极大极小值,不够直观
2024-02-19 13:52:43 +08:00
github-actions[bot] e8b208edfa update contributors[no ci] 2024-02-18 12:00:59 +00:00
Ko no dio 7d38634933 feat: add index on monitor history (#320) 2024-02-18 20:00:36 +08:00
github-actions[bot] 5bd4b57cbf update contributors[no ci] 2024-02-14 05:12:49 +00:00
Ko no dioandLvGJ fdb2fd8839 fix: revert service html and block service in backend (#318)
Co-authored-by: LvGJ <[email protected]>
2024-02-14 13:12:24 +08:00
github-actions[bot] f3fac9325c update contributors[no ci] 2024-02-13 15:09:58 +00:00
Ko no dioandLvGJ 626613441d fix: add toggle to service (#317)
* fix: short nework word

* fix: add toggle on service

* fix: delete console log update main.js version

---------

Co-authored-by: LvGJ <[email protected]>
2024-02-13 23:09:40 +08:00
github-actions[bot] b86b73a433 update contributors[no ci] 2024-02-12 06:16:26 +00:00
Ko no dioandLvGJ e8b8e59bd7 feat: add network monitor hitory (#316) · 三网ping
* feat: add network monitor hitory

* fix: revert proto change and add indexStore

* fix: update monitor delete unuse monitor history

* fix: delete unuse monitor type

---------

Co-authored-by: LvGJ <[email protected]>
2024-02-12 14:16:04 +08:00
naiba c9bcba6f28 ♻️ 更换可用镜像地址 2024-02-09 20:59:22 +08:00
github-actions[bot] ad9dc5470a update contributors[no ci] 2024-01-18 05:35:21 +00:00
黑歌 8539fb6559 replace daocloud with fgit (#315)
* Update add custom mirror

* Update add custom mirror

* bump install version
2024-01-18 13:34:58 +08:00
github-actions[bot] 11bcc47d49 update contributors[no ci] 2023-12-26 02:26:27 +00:00
Crazy bb789f7652 add supervise daemon for alpine (#313)
alpine在自动更新agent以后不会自动重启agent,导致批量掉线,添加守护进程以后问题解决
2023-12-26 10:26:11 +08:00
dependabot[bot] 51c1e41123 Bump golang.org/x/crypto from 0.15.0 to 0.17.0 (#312)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.15.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.15.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-19 09:43:33 +08:00
naiba 83efdc6258 replace statifile cdn with bytedance cdn 2023-12-11 21:53:56 +08:00
github-actions[bot] bf58ad24b5 update contributors[no ci] 2023-12-08 13:17:02 +00:00
spiritlhl 53581d48fc 更新判断面板是否是通过docker安装的逻辑 (#311)
* 更新判断面板是否是通过docker安装的逻辑

* Updated logic for determining whether a panel is installed via docker or not
2023-12-08 21:16:36 +08:00
naiba b00a00e791 Add comment about missing riscv64 image in latest Alpine image 2023-12-08 09:55:17 +08:00
github-actions[bot] b91f29077c update contributors[no ci] 2023-12-07 13:59:01 +00:00
nap0oandSuperHsiao 99111a3115 improve server-status theme (#310)
* Add files via upload

* Add files via upload

* Add files via upload

* improve theme-server-status

1.前台分组展示agent
2.一些小优化

---------

Co-authored-by: SuperHsiao <[email protected]>
2023-12-07 21:58:42 +08:00
github-actions[bot] 30652c3d79 update contributors[no ci] 2023-12-07 13:41:51 +00:00
Mmx ac0103a1ee Improve: use alpine as dockerfile base image (#309)
* build: use alpine as base image

* build: use alpine edge instead of latest
2023-12-07 21:41:26 +08:00
github-actions[bot] f59b31590e update contributors[no ci] 2023-12-06 14:42:42 +00:00
Applexad 1bcddf1944 installer: fix docker compose command determination (#307) 2023-12-06 22:42:22 +08:00
卖女孩的小火柴 d790ca04d6 Fix select_version (#306) 2023-12-06 21:35:24 +08:00
github-actions[bot] 2106ca41e1 update contributors[no ci] 2023-12-06 01:35:54 +00:00
Mmx 927bf13ce4 fix: field name "VerifySSL" to "SkipVerifySSL" in Transport config (#305) 2023-12-06 09:35:38 +08:00
github-actions[bot] 859e263815 update contributors[no ci] 2023-12-05 15:23:19 +00:00
Mmx 1dcd899591 Fix Coroutine Leaks and Proxy Handling in http.Client (#304)
* fix: builtin variable conflict

* feat: add pkg/utils/http HttpClientSkipTlsVerify HttpClient

* fix: realtime create http Transport

* fix: http keepalive connection may not reusable

* feat: allow http request use proxy from environment
2023-12-05 23:22:25 +08:00
naiba 3059bf2d49 Update installer version to v0.15.2 2023-12-04 10:23:56 +08:00
naiba 74255717ef Update destination repository URL in sync workflow 2023-12-04 10:21:13 +08:00
github-actions[bot] 8fa59a4639 update contributors[no ci] 2023-11-30 05:16:55 +00:00
Applexad b3e38e8924 Exit after installing agent (#303)
If entered with install_agent argument, variable IS_DOCKER_NEZHA will not be set, thus leading select menu to appear, which could be annoying.
2023-11-30 13:16:39 +08:00
github-actions[bot] 2280e0267c update contributors[no ci] 2023-11-30 02:49:24 +00:00
Applexad 1871c481e4 Fix various bugs in fresh installation (#302) 2023-11-30 10:49:06 +08:00
naiba c3d1eec6bf Update GitHub Actions workflow to include specific paths 2023-11-30 09:52:24 +08:00
github-actions[bot] 6fc8ec9446 update contributors[no ci] 2023-11-30 01:39:22 +00:00
Applexad 52703f38cd Add standalone installation method & OpenRC support for nezha-agent (#300)
* Add standalone installation method & OpenRC support for nezha-agent

* Add missing translations & more precise query

* bump installer version
2023-11-30 09:39:00 +08:00
卖女孩的小火柴 d0707b9b05 Fix: get.daocloud.io 503 (#301) 2023-11-30 09:31:39 +08:00
naiba ad99a5d032 Export custom static folder 2023-11-29 17:13:45 +08:00
naiba a823da1f29 📦 static link 2023-11-29 15:45:32 +08:00
naiba b0b7e9da61 Update badges in README 2023-11-29 09:44:47 +08:00
naiba 6a25f39e36 🐛 fix third-party template loading 2023-11-29 09:42:51 +08:00
103 changed files with 5488 additions and 1321 deletions

No files matched your search

+2 -2
View File
@@ -30,12 +30,12 @@ jobs:
v: true
x: false
race: false
ldflags: -s -w -X github.com/naiba/nezha/service/singleton.Version=${{ steps.extract_branch.outputs.tag }}
ldflags: -s -w --extldflags '-static -fpic' -X github.com/naiba/nezha/service/singleton.Version=${{ steps.extract_branch.outputs.tag }}
buildmode: default
- name: fix dist
run: |
cp dist/dashboard-linux-arm-7 dist/dashboard-linux-arm
mv dist/dashboard-linux-arm-7 dist/dashboard-linux-arm
- name: Log in to the GHCR
uses: docker/login-action@master
+1 -1
View File
@@ -11,6 +11,6 @@ jobs:
- uses: actions/checkout@v4
- uses: adambirds/[email protected]
with:
destination_repository: git@jihulab.com:nezha/dashboard.git
destination_repository: git@gitee.com:naibahq/nezha.git
destination_branch_name: master
destination_ssh_key: ${{ secrets.GITLAB_SSH_KEY }}
+15 -3
View File
@@ -17,10 +17,22 @@ jobs:
- name: Unit test
run: |
go test -v ./...
- name: Build test
run: |
go build cmd/dashboard/main.go
- name: Run Gosec Security Scanner
uses: securego/gosec@master
with:
args: --exclude=G104,G402 ./...
- name: xgo build
uses: crazy-max/ghaction-xgo@v2
with:
xgo_version: latest
go_version: 1.21
dest: dist
pkg: cmd/dashboard
prefix: dashboard
targets: linux/amd64,linux/arm64,linux/arm-7,linux/s390x,linux/riscv64 # linux/386,
v: true
x: false
race: false
ldflags: -s -w --extldflags '-static -fpic' -X github.com/naiba/nezha/service/singleton.Version=test
buildmode: default
+20 -11
View File
@@ -4,14 +4,12 @@ on:
push:
branches:
- master
paths-ignore:
- ".github/workflows/codeql-analysis.yml"
- ".github/workflows/test-on-pr.yml"
- ".github/workflows/contributors.yml"
- ".github/workflows/sync.yml"
- ".github/workflows/release.yml"
- "README.md"
- ".goreleaser.yml"
paths:
- "**.go"
- "go.mod"
- "go.sum"
- "resource/**"
- ".github/workflows/test.yml"
jobs:
tests:
@@ -26,10 +24,21 @@ jobs:
- name: Unit test
run: |
go test -v ./...
- name: Build test
run: |
go build cmd/dashboard/main.go
- name: Run Gosec Security Scanner
uses: securego/gosec@master
with:
args: --exclude=G104,G402 ./...
- name: xgo build
uses: crazy-max/ghaction-xgo@v2
with:
xgo_version: latest
go_version: 1.21
dest: dist
pkg: cmd/dashboard
prefix: dashboard
targets: linux/amd64,linux/arm64,linux/arm-7,linux/s390x,linux/riscv64 # linux/386,
v: true
x: false
race: false
ldflags: -s -w --extldflags '-static -fpic' -X github.com/naiba/nezha/service/singleton.Version=test
buildmode: default
+1 -1
View File
@@ -19,4 +19,4 @@
/cmd/dashboard/main
/config.yml
/resource/template/theme-custom
/resource/static/theme-custom
/resource/static/custom
+10 -10
View File
@@ -1,18 +1,18 @@
FROM ubuntu:focal-20221130
ENV TZ="Asia/Shanghai"
# latest 镜像还没有 riscv64 https://hub.docker.com/_/alpine/tags
FROM alpine:edge
ARG TARGETOS
ARG TARGETARCH
COPY ./script/entrypoint.sh /entrypoint.sh
RUN apk update && \
apk upgrade --no-cache && \
apk add --no-cache tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo 'Asia/Shanghai' >/etc/timezone && \
rm -rf /var/cache/apk/*
RUN export DEBIAN_FRONTEND="noninteractive" && \
apt update && apt install -y ca-certificates tzdata && \
update-ca-certificates && \
ln -fs /usr/share/zoneinfo/$TZ /etc/localtime && \
dpkg-reconfigure tzdata && \
chmod +x /entrypoint.sh
COPY ./script/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
WORKDIR /dashboard
COPY dist/dashboard-${TARGETOS}-${TARGETARCH} ./app
+44 -103
View File
@@ -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?style=for-the-badge&logo=github&label=Dashboard">&nbsp;<img src="https://img.shields.io/github/v/release/nezhahq/agent?color=brightgreen&label=Agent&style=for-the-badge&logo=github">&nbsp;<img src="https://img.shields.io/github/actions/workflow/status/nezhahq/agent/agent.yml?label=Agent%20CI&logo=github&style=for-the-badge">&nbsp;<img src="https://img.shields.io/badge/Installer-v0.15.0-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">&nbsp;<img src="https://img.shields.io/github/v/release/nezhahq/agent?color=brightgreen&label=Agent&style=for-the-badge&logo=github">&nbsp;<img src="https://img.shields.io/github/actions/workflow/status/nezhahq/agent/agent.yml?label=Agent%20CI&logo=github&style=for-the-badge">&nbsp;<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,108 +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/hhhkkk520" title="Kris">
<img src="https://avatars.githubusercontent.com/u/52115472?v=4" width="50;" alt="Kris"/>
</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/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/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/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/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/1ridic" title="1ridic"><img src="https://avatars.githubusercontent.com/u/88495501?v=4" width="50;" alt="1ridic"/></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/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/uubulb" title="UUBulb"><img src="https://avatars.githubusercontent.com/u/35923940?v=4" width="50;" alt="UUBulb"/></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/xykt" title="xykt"><img src="https://avatars.githubusercontent.com/u/152045469?v=4" width="50;" alt="xykt"/></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/ysicing" title="缘生"><img src="https://avatars.githubusercontent.com/u/8605565?v=4" width="50;" alt="缘生"/></a>
<a href="https://github.com/sunnywangzi" title="wang"><img src="https://avatars.githubusercontent.com/u/88769439?v=4" width="50;" alt="wang"/></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
+52 -7
View File
@@ -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,
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}))
}
+124 -29
View File
@@ -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,
}))
+26 -7
View File
@@ -4,7 +4,9 @@ import (
"fmt"
"html/template"
"io/fs"
"log"
"net/http"
"os"
"strconv"
"strings"
"sync"
@@ -16,7 +18,6 @@ import (
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/naiba/nezha/pkg/mygin"
"github.com/naiba/nezha/pkg/utils"
"github.com/naiba/nezha/resource"
"github.com/naiba/nezha/service/singleton"
)
@@ -30,12 +31,7 @@ func ServeWeb(port uint) *http.Server {
if err != nil {
panic(err)
}
if utils.IsFileExists("resource/template") {
tmpl, err = tmpl.ParseGlob("resource/template/**/*.html")
if err != nil {
panic(err)
}
}
tmpl = loadThirdPartyTemplates(tmpl)
r.SetHTMLTemplate(tmpl)
if singleton.Conf.Debug {
gin.SetMode(gin.DebugMode)
@@ -47,6 +43,7 @@ func ServeWeb(port uint) *http.Server {
panic(err)
}
r.StaticFS("/static", http.FS(staticFs))
r.Static("/static-custom", "resource/static/custom")
routers(r)
page404 := func(c *gin.Context) {
@@ -87,6 +84,28 @@ func routers(r *gin.Engine) {
}
}
func loadThirdPartyTemplates(tmpl *template.Template) *template.Template {
var ret = tmpl
themes, err := os.ReadDir("resource/template")
if err != nil {
log.Printf("NEZHA>> Error reading themes folder: %v", err)
return ret
}
for _, theme := range themes {
if !theme.IsDir() {
continue
}
// load templates
t, err := ret.ParseGlob(fmt.Sprintf("resource/template/%s/*.html", theme.Name()))
if err != nil {
log.Printf("NEZHA>> Error parsing templates %s error: %v", theme.Name(), err)
continue
}
ret = t
}
return ret
}
var funcMap = template.FuncMap{
"tr": func(id string, dataAndCount ...interface{}) string {
conf := i18n.LocalizeConfig{
+1 -1
View File
@@ -19,7 +19,7 @@ type guestPage struct {
func (gp *guestPage) serve() {
gr := gp.r.Group("")
gr.Use(mygin.Authorize(mygin.AuthorizeOption{
Guest: true,
GuestOnly: true,
IsPage: true,
Msg: "您已登录",
Btn: "返回首页",
+27 -1
View File
@@ -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,7 +28,7 @@ type memberAPI struct {
func (ma *memberAPI) serve() {
mr := ma.r.Group("")
mr.Use(mygin.Authorize(mygin.AuthorizeOption{
Member: true,
MemberOnly: true,
IsPage: false,
Msg: "访问此接口需要登录",
Btn: "点此登录",
@@ -185,7 +186,17 @@ func (ma *memberAPI) delete(c *gin.Context) {
var err error
switch c.Param("model") {
case "server":
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,8 @@ type serverForm struct {
Tag string
Note string
HideForGuest string
EnableDDNS string
DDNSDomain string
}
func (ma *memberAPI) addOrEditServer(c *gin.Context) {
@@ -304,6 +317,8 @@ 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.DDNSDomain = sf.DDNSDomain
if s.ID == 0 {
s.Secret, err = utils.GenerateRandomString(18)
if err == nil {
@@ -383,6 +398,7 @@ type monitorForm struct {
MaxLatency float32
LatencyNotify string
EnableTriggerTask string
EnableShowInService string
FailTriggerTasksRaw string
RecoverTriggerTasksRaw string
}
@@ -404,6 +420,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 +444,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)
@@ -836,6 +860,7 @@ type settingForm struct {
EnableIPChangeNotification string
EnablePlainIPInNotification string
DisableSwitchTemplateInFrontend string
}
func (ma *memberAPI) updateSetting(c *gin.Context) {
@@ -883,6 +908,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
+1 -2
View File
@@ -17,7 +17,7 @@ type memberPage struct {
func (mp *memberPage) serve() {
mr := mp.r.Group("")
mr.Use(mygin.Authorize(mygin.AuthorizeOption{
Member: true,
MemberOnly: true,
IsPage: true,
Msg: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "YouAreNotAuthorized"}),
Btn: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "Login"}),
@@ -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,
}))
}
+12 -2
View File
@@ -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++
break
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.SortedServerLock.RUnlock()
}
+22 -25
View File
@@ -5,8 +5,8 @@ go 1.21
toolchain go1.21.3
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-20240322144145-4816192cb114
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 +15,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.100.0
golang.org/x/crypto v0.21.0
golang.org/x/oauth2 v0.18.0
golang.org/x/sync v0.6.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.62.1
google.golang.org/protobuf v1.33.0
gorm.io/driver/sqlite v1.5.5
gorm.io/gorm v1.25.8
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
@@ -66,12 +64,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 +76,11 @@ 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.22.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+76 -464
View File
@@ -1,64 +1,15 @@
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-20240322144145-4816192cb114 h1:7KJsK0gPah7XfO46ARJA2TUXHZaJEnPI80mzxQLre68=
code.cloudfoundry.org/bytefmt v0.0.0-20240322144145-4816192cb114/go.mod h1:TNAH6xe5WOszc47+zM7NnpaJWBR8oYt12BUf4WLIZ98=
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=
@@ -66,18 +17,12 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
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/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
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,11 +34,8 @@ 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-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
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=
@@ -111,73 +53,22 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4
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.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
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-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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-20240320155624-b11c3daa6f07 h1:57oOH2Mu5Nw16KnZAVLdlUjmPH/TSYCKTJgG0OVfX0Y=
github.com/google/pprof v0.0.0-20240320155624-b11c3daa6f07/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
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=
@@ -189,15 +80,10 @@ github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUD
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,13 +92,9 @@ 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=
@@ -241,12 +123,12 @@ 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.0 h1:kdnunFXpBjbzN56hcJHrXZ8M+LOkenKA7NnBzTNigTI=
github.com/onsi/ginkgo/v2 v2.17.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk=
github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg=
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 +139,36 @@ 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 +186,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.100.0 h1:jaOtYj5nWI19+9oVVmgy233pax2oYqucwetogYU46ks=
github.com/xanzy/go-gitlab v0.100.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 +197,84 @@ 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.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
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.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.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.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.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.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
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.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
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.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
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/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
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 +283,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.8 h1:WAGEZ/aEcznN4D03laj8DKnehe1e9gYQAjW8xyPRdeo=
gorm.io/gorm v1.25.8/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=
+6 -6
View File
@@ -85,13 +85,13 @@ func (r *AlertRule) Snapshot(cycleTransferStats *CycleTransferStats, server *Ser
// Check 传入包含当前报警规则下所有type检查结果的空接口 返回报警持续时间与是否通过报警检查(通过则返回true)
func (r *AlertRule) Check(points [][]interface{}) (int, bool) {
var max int // 报警持续时间
var maxNum int // 报警持续时间
var count int // 检查未通过的个数
for i := 0; i < len(r.Rules); i++ {
if r.Rules[i].IsTransferDurationRule() {
// 循环区间流量报警
if max < 1 {
max = 1
if maxNum < 1 {
maxNum = 1
}
for j := len(points[i]) - 1; j >= 0; j-- {
if points[i][j] != nil {
@@ -104,8 +104,8 @@ func (r *AlertRule) Check(points [][]interface{}) (int, bool) {
total := 0.0
fail := 0.0
num := int(r.Rules[i].Duration)
if num > max {
max = num
if num > maxNum {
maxNum = num
}
if len(points) < num {
continue
@@ -124,5 +124,5 @@ func (r *AlertRule) Check(points [][]interface{}) (int, bool) {
}
}
// 仅当所有检查均未通过时 返回false
return max, count != len(r.Rules)
return maxNum, count != len(r.Rules)
}
+1
View File
@@ -8,6 +8,7 @@ import (
const CtxKeyAuthorizedUser = "ckau"
const CtxKeyViewPasswordVerified = "ckvpv"
const CtxKeyPreferredTheme = "ckpt"
const CacheKeyOauth2State = "p:a:state"
type Common struct {
+31
View File
@@ -99,6 +99,7 @@ type Config struct {
TLS bool
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
View File
@@ -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
+14 -3
View File
@@ -1,11 +1,22 @@
package model
import (
"time"
"gorm.io/gorm"
)
// MonitorHistory 历史监控记录
type MonitorHistory struct {
Common
MonitorID uint64
AvgDelay float32 // 平均延迟,毫秒
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
}
+8 -10
View File
@@ -1,7 +1,6 @@
package model
import (
"crypto/tls"
"errors"
"fmt"
"io"
@@ -111,17 +110,14 @@ func (n *Notification) setRequestHeader(req *http.Request) error {
}
func (ns *NotificationServerBundle) Send(message string) error {
var verifySSL bool
var client *http.Client
n := ns.Notification
if n.VerifySSL != nil && *n.VerifySSL {
verifySSL = true
client = utils.HttpClient
} else {
client = utils.HttpClientSkipTlsVerify
}
transCfg := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: verifySSL},
}
client := &http.Client{Transport: transCfg, Timeout: time.Minute * 10}
reqBody, err := ns.reqBody(message)
if err != nil {
return err
@@ -147,13 +143,15 @@ func (ns *NotificationServerBundle) Send(message string) error {
if err != nil {
return err
}
if resp.StatusCode < 200 || resp.StatusCode > 299 {
defer func() {
_ = resp.Body.Close()
}()
if resp.StatusCode < 200 || resp.StatusCode > 299 {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("%d@%s %s", resp.StatusCode, resp.Status, string(body))
} else {
_, _ = io.Copy(io.Discard, resp.Body)
}
return nil
+24 -10
View File
@@ -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))
}
}
}
+4 -1
View File
@@ -17,6 +17,8 @@ type Server struct {
Note string `json:"-"` // 管理员可见备注
DisplayIndex int // 展示排序,越大越靠前
HideForGuest bool // 对游客隐藏
EnableDDNS bool // 是否启用DDNS 未在配置文件中启用DDNS 或 DDNS检查时间为0时此项无效
DDNSDomain string // DDNS中的前缀 如基础域名为abc.oracle DDNSName为mjj 就会把mjj.abc.oracle解析服务器IP 为空则停用
Host *Host `gorm:"-"`
State *HostState `gorm:"-"`
@@ -51,5 +53,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,"DDNSDomain": %s}`, s.ID, name, secret, s.DisplayIndex, tag, note, boolToString(s.HideForGuest), boolToString(s.EnableDDNS), ddnsDomain)) // #nosec
}
+6 -3
View File
@@ -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)
}
}
}
+177
View File
@@ -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
}
+14
View File
@@ -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
}
+7
View File
@@ -0,0 +1,7 @@
package ddns
type ProviderDummy struct{}
func (provider ProviderDummy) UpdateDomain(domainConfig *DomainConfig) bool {
return false
}
+61
View File
@@ -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 // 当域名不包含子域名时,无前缀
}
+59
View File
@@ -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
}
+7 -5
View File
@@ -12,8 +12,8 @@ import (
)
type AuthorizeOption struct {
Guest bool
Member bool
GuestOnly bool
MemberOnly bool
IsPage bool
AllowAPI bool
Msg string
@@ -24,7 +24,7 @@ type AuthorizeOption struct {
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
}
+1
View File
@@ -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)]
// 站点标题
+30
View File
@@ -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)
}
+52
View File
@@ -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()
}
}
+47
View File
@@ -0,0 +1,47 @@
package utils
import (
"crypto/tls"
"net/http"
"time"
)
var (
HttpClientSkipTlsVerify *http.Client
HttpClient *http.Client
)
func init() {
HttpClientSkipTlsVerify = httpClient(_httpClient{
Transport: httpTransport(_httpTransport{
SkipVerifySSL: true,
}),
})
HttpClient = httpClient(_httpClient{
Transport: httpTransport(_httpTransport{
SkipVerifySSL: false,
}),
})
}
type _httpTransport struct {
SkipVerifySSL bool
}
func httpTransport(conf _httpTransport) *http.Transport {
return &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: conf.SkipVerifySSL},
Proxy: http.ProxyFromEnvironment,
}
}
type _httpClient struct {
Transport *http.Transport
}
func httpClient(conf _httpClient) *http.Client {
return &http.Client{
Transport: conf.Transport,
Timeout: time.Minute * 10,
}
}
+12 -6
View File
@@ -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
}
}
+26 -2
View File
@@ -398,7 +398,7 @@ other = "Virtualization"
other = "Swap"
[NetTransfer]
other = "Network Transfer"
other = "Transfer"
[Load]
other = "Load"
@@ -419,7 +419,7 @@ other = "Last Active"
other = "Version"
[NetSpeed]
other = "Network Speed"
other = "Speed"
[Uptime]
other = "Uptime"
@@ -609,3 +609,27 @@ other = "Hide for Guest"
[Menu]
other = "Menu"
[NetworkSpiter]
other = "Network"
[EnableShowInService]
other = "Enable Show in Service"
[EnableDDNS]
other = "Enable DDNS"
[DDNSDomain]
other = "DDNS Domain"
[Feature]
other = "Feature"
[Template]
other = "Template"
[Stat]
other = "Stat"
[DisableSwitchTemplateInFrontend]
other = "Disable Switch Template in Frontend"
+25 -1
View File
@@ -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,27 @@ other = "Ocultar para Invitados"
[Menu]
other = "Menú"
[NetworkSpiter]
other = "Red"
[EnableShowInService]
other = "Mostrar en servicio"
[EnableDDNS]
other = "Habilitar DDNS"
[DDNSDomain]
other = "Dominio DDNS"
[Feature]
other = "Característica"
[Template]
other = "Plantilla"
[Stat]
other = "Stat"
[DisableSwitchTemplateInFrontend]
other = "Deshabilitar Cambio de Plantilla en Frontend"
+24
View File
@@ -609,3 +609,27 @@ other = "对游客隐藏"
[Menu]
other = "菜单"
[NetworkSpiter]
other = "网络"
[EnableShowInService]
other = "在服务中显示"
[EnableDDNS]
other = "启用DDNS"
[DDNSDomain]
other = "DDNS域名"
[Feature]
other = "功能"
[Template]
other = "主题"
[Stat]
other = "信息"
[DisableSwitchTemplateInFrontend]
other = "禁止前台切换模板"
+24
View File
@@ -609,3 +609,27 @@ other = "對遊客隱藏"
[Menu]
other = "菜單"
[NetworkSpiter]
other = "網絡"
[EnableShowInService]
other = "在服務中顯示"
[EnableDDNS]
other = "啟用DDNS"
[DDNSDomain]
other = "DDNS網域"
[Feature]
other = "功能"
[Template]
other = "主題"
[Stat]
other = "信息"
[DisableSwitchTemplateInFrontend]
other = "禁止前台切換主題"
+11
View File
@@ -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,11 @@ 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");
}
showFormModal(".server.modal", "#serverForm", "/api/server");
}
@@ -339,6 +345,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 {
+226
View File
@@ -0,0 +1,226 @@
/* 屏幕适配 */
@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.card>.content>.header,.ui.cards>.card>.content>.header {
padding-top: 0.5rem;
display: flex;
color: rgba(0, 0, 0, .85);
align-items: center;
padding-bottom: .2em;
border-bottom: 1px solid rgba(0, 0, 0, .2);
}
.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
View File
@@ -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);
}
});
}
}
}
@@ -1,6 +1,6 @@
/*!
* Bootstrap v3.3.7 (http://getbootstrap.com)
* Copyright 2011-2016 Twitter, Inc.
* Bootstrap v3.4.1 (https://getbootstrap.com/)
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-succLine truncated
*/.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;text-shadow:0 1px 0 #fff;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-succLine truncated
/*# sourceMappingURL=bootstrap-theme.min.css.map */
@@ -1 +1 @@
{"version":3,"sources":["less/theme.less","less/mixins/vendor-prefixes.less","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":"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 truncated
{"version":3,"sources":["bootstrap-theme.css","dist/css/bootstrap-theme.css","less/theme.less","less/mixins/vendor-prefixes.less","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":"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 truncated
@@ -1,6 +0,0 @@
/*!
* Bootstrap v3.3.7 (http://getbootstrap.com)
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\Line truncated
/*# sourceMappingURL=bootstrap.min.css.map */
@@ -1 +0,0 @@
{"version":3,"sources":["less/normalize.less","less/print.less","bootstrap.css","dist/css/bootstrap.css","less/glyphicons.less","less/scaffolding.less","less/mixins/vendor-prefixes.less","less/mixins/tab-focus.less","less/mixins/image.less","less/type.less","less/mixins/text-emphasis.less","less/mixins/background-variant.less","less/mixins/text-overflow.less","less/code.less","less/grid.less","less/mixins/grid.less","less/mixins/grid-framework.less","less/tables.less","less/mixins/table-row.less","less/forms.less","less/mixins/forms.less","less/buttons.less","less/mixins/buttons.less","less/mixins/opacity.less","less/component-animations.less","less/dropdowns.less","less/mixins/nav-divider.less","less/mixins/reset-filter.less","less/button-groups.less","less/mixins/border-radius.less","less/input-groups.less","less/navs.less","less/navbar.less","less/mixins/nav-vertical-align.less","less/utilities.less","less/breadcrumbs.less","less/pagination.less","less/mixins/pagination.less","less/pager.less","less/labels.less","less/mixins/labels.less","less/badges.less","less/jumbotron.less","less/thumbnails.less","less/alerts.less","less/mixins/alerts.less","less/progress-bars.less","less/mixins/gradients.less","less/mixins/progress-bar.less","less/media.less","less/list-group.less","less/mixins/list-group.less","less/panels.less","less/mixins/panels.less","less/responsive-embed.less","less/wells.less","less/close.less","less/modals.less","less/tooltip.less","less/mixins/reset-text.less","less/popovers.less","less/carousel.less","less/mixins/clearfix.less","less/mixins/center-block.less","less/mixins/hide-text.less","less/responsive-utilities.less","less/mixins/responsive-visibility.less"],"names":[],"mappings":"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 truncated
+78 -51
View File
@@ -1,73 +1,83 @@
body[theme="dark"] {
background: #212e36 url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PGRlZnM+PGZpbHRlciBpZD0iZGFya3JlYWRlci1pbWFnZS1maWx0ZXIiPjxmZUNvbG9yTWF0cml4IHR5cGU9Im1hdHJpeCIgdmFsdWVzPSIwLjMzMyAtMC42NjcgLTAuNjY3IDAuMDAwIDEuMDAwIC0wLjY2NyAwLjMzMyAtMC42NjcgMC4wMDAgMS4wMDAgLTAuNjY3IC0wLjY2NyAwLjMzMyAwLjAwMCAxLjAwMCAwLjAwMCAwLjAwMCAwLjAwMCAxLjAwMCAwLjAwMCIgLz48L2ZpbHRlcj48L2RlZnM+PGltYWdlIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIiBmaWx0ZXI9InVybCgjZGFya3JlYWRlci1pbWFnZS1maWx0ZXIpIiB4bGluazpocmVmPSJkYXRhOmltYWdlL3BuZztiYXNlNjQsaVZCT1J3MEtHZ29BQUFBTlNVaEVVZ0FBQU1nQUFBRElCQU1BQUFCZmRyT3RBQUFBTUZCTVZFWHc4UER4OGZIeTh2THo4L1AwOVBUMTlmWDI5dmIzOS9mNCtQajUrZm42K3ZyNysvdjgvUHo5L2YzKy92Ny8vLzlwVXFOWkFBQkxoMGxFUVZSNEFRWEJSMkJTQndNQTRBZFpoS29RTWdoVUpRbGtnSU1FTXJGS1FnWUpEckpEY0pCSmdOYVN4UWdPa3BCSnRVQTJXR1ZQYTZtdC9vNlQxdGFxSjdmV2szdWQyQTg0L2Q4SFRFZnRHaFRUa2s2ZmdRWlpPSkU4bklWclNQNnFQVDY0YkU1R0ZPWEUxcFVRbEMrSFRjZ0pUT0RNNDdjbmxkWWhPOFU3NDRyMTQzSUtMMk1RUTBWZkcwVWVoclZZSTh2eFZSMHBrOS9LMkErVnhnUUd6RVlNanVYTFBuZWVTQUhnaHRSWHBZaU42cHZWcDhmMXdZVE5tWDJnY0ROa0piaXFSRXBpK2IvQksvOXhpcldzcTNucytyYWdGcGNvOVJzMFJkSnJHZXg4MklPT2IzdmFveVhlUlVwb1RUTW91VnB2WE95NzUyWXJza013cjdUbDJmUVdoalVpTzFZa2VyY0JoUHBQdXNzQ2hPOFM2NEs1OUlrR01IWDNodDAzU0I5SURPWHNLWnIva0xGRGxQSys1TUw0eE1zNlpRbmthZmtQSmJsUE1uRmRYTC9nVktYNXhTUnZPUytHOUxhbEI1cjJTekwvNHlwVEU4R2FyZmFjbDJNYXcyUVU2MTFmdXpPUFlCb0FhSGMwYlN2V0RaSkUzUXcvWlYvRlFxaEJUQ0QrTVdsd1lDOUxOS2lweUJDN0doc3luOHZ1Y1dBUWJmZ0FTaW1FK2p4SFJ2TWNoRi93ZmEvcWpCaHJNSVZrYVFJbnNiT0RYK3YyMVVoODVmU0o5V2pPWmtwVGRPVjBidksvZVY1QXB5bWJEWTJJcGNJWEJRak1TcUNCM0VqOG94TzNBbnd0M0c0cWZ3SGxDZUh4a1l1NW9yQjAwL1NNWDNncEFSMWVaMVBVZ1dINkdod2NWMmJYQkJ2RkM5Sm83WkZHVXF5U2JnTkFnNFk1R1NuZWtxeUxxeThsTHp1bXhWTElJMEFlVFNSVHVJRm1kczdjSDA1bG5TaEF3UFdWUnBOM0VDVitBcGxhNDI5RkZMcnZ0YWtia1BGaVhEUEdoejBnYS91YXprWmx2bW9RUXpNaUc3c2RCV0hIQlIwL1BLMWhkVWRsNG14cnFGQkU2M282anFQbVJJdC9GSTRFQVhJeXdFTzEza2NoeGpOOUZuSjZFeWo3U2Rydms1MVR0VnhKeExhb1FUUXRiOUEvY2JSV0gzUHlPckxBbFY4NzUwTk1VVy9EeStSZmJNUDNtMzhZY3Z4aDkyclJ0N1BwOHZSSWtaZ29BVWtuYS9tWGg1UWo5YkZrT2pJL2hEZHVKSDBvQVh5ZTQwS3JyK1M0eFJDYjhvNjJCRGdhaGpZMmh1RDBoSWcvTE1wdXA1TGhuakNlTGtsNkxEK25tM1ZrOEVqSnBnRmxrdUVhOVpzRzlRUFpIdXZLNy9YZTBsUlFqeEJrK1ptN1JyRW1tcGhVSEtkNHB3RGJ3aEdQSkRLdFNaOEZLbHcyMGNrTjgrdFM3MGJoR3p6UFBmOUJ6UnQyWFpuMHVpcDhIdHpxeUFjQ3ZSLzl2b0ZYTW5GRHpXcUQva00xZGtGZm1NVko4a0E2d2pQNXdzTnVMbjJTaENzQXJzR1ZpVEJUNlI0cC9zWXNtYUNLTGRPblVXQ05GellhSzZBRGkxNldLWVkwMWszNXF0aTVqTGRUN0c3OGs5bzkrUnV1c2VQeTVRaU1YY2Q1T01YTFpJRk1lZ0w2eVJDOUUzT2I0TFVVUi9JMzI1SCtTZDZRN1k4NXI3a3Jub3lncllRc1NuZit5OGtmYXcxdlpuK2RvMFpINkVtT3VNU1lKUEFCM1IvbmpJVVRZQnJMQkx0UCtXWUJFMFhqMHZJL3BZdmduVDZrQ0cxNmFkK3hPZ2pLdDIrVStvYVZSZFhYbTVRanJVOGxPRlp6UEltODVBd01uODV1QVRNdjVpVUU5YWV4anBmcFhrYWRIMDdIWjRTdGlJYjBlMm5INXRZL3liY0tBREsxOE8rRkxWV1lLRktjWFJkYTBzQ1FqN0tQTzFTdmlqWHloUmhLTTJuNTNFbWU0UHFhdjdWWFB1dzZBdlBFK2NhMW5EczVQT2J3cTE2MkZJZ3dFRXNyUGhSOVkreWU0aUpzNDlta04zVXRDaUFHbUYrSnZCVlN5T0pkNm93WWdORHFqOU9GOTJyWTNZUi8wc1Y1bGtnQkZ1YTJhQkVVZExqSW0ybnpTQkJvNFByQ3ZqeFpqTVR1MWIxczRsVk51Z1duUzZSZzJnRVY3R3ZYMlRWa3hMWGRwYnB1K3JGRTlxbFF1U0NKOWhtN1pKSFNrOTExVVRtdjBIeGJ0dFc4Q3ZTNmZUcWFtaENmZ1hjWEJ3ZS9zN1diU3hEOXVXSEZkc2NTaUdLM2Q4ZFZQSnNxQnFGREN1SmFEWW4vVnJZSEN3dUl2T3Y2K0lBUm9vZ1hlMXVXdzNsbmVpUy9xMzlxbDcxakhXaGJDRXEvYTE1OVplY1JwK0s5U25kYWpIZ0U0Q01HYzU5TUdKdmdUNkhuWVRLTFpRL1Y0bDdDZFlrQ1ZON3NaRHhkS1VyK1gra21sVFZFM1N2RlBGQnZSZmJkN0dIUjhsN1c0YnBFbGl4Ni9uS296OWl4OFlyd3pjclFaNDVTV1BHT1NSWVUvMDlJMDVkZEg5eTB5dmd3em1ZMC9BY1VPQXVVL2JPeDZwOTdHdndHTDNZc2tHQ3N6b3JOZmFzdStYdm9WNW9xVVBYdHdISjRERmM5SEcwOG5TdUx3alZDeWlmaTFwWHNSN3BMVFowUERFWUxOMlFXeTYwUEhkNjU1aUJSYkpHRThOK3FyYjdNN3ppa0lOKzRsQnBOd2JVQUNGbGFnSGFpU2ZZa0N6ZlQ1WmxEVkdXQVNkdnNRRnhQaHl4YzZWWFNNbDJMdUlURkQ4bS9BTmFZUVF5ZGVWVzNkUWtXdzdNM0Z2eVMzU1JTUUNzdVdYdkw5SzRuMm1vdkpJOEY1OGtEZythTlhhMllpR2lySnlXUVRZY01SMGFBbU9NRVhQS1U2aTEzUE1FZXpLRUYyOFVUSGFZbDc3TGtRK0pXR1BvKzZac1ZkWkNnYm04Sm81WEZtUUdpcGo4aFduZkdSUFBaMlh5STMwMG1URnd6MEJ2d2oxdHd0WEp3WTh2TXNyOWRVNVFXVlhuWFhINml1S3N5eUtFbk5BTnRsbGpaejZOSVgrdXgvTnkvVGV5V3ZNdTlpRFdycjVNM2p2Y1JjUmFkbmFPUjUvdWc3RDdZdlRiMi9Cd29VcExtd09sdlVDV1B1MDdLVFNIOHQvbGQvcklEcU1sWUd6bEYvN0ZLdk1CMFYvREdNMlBMZEZwYnJGKzVJZnd5eEFieXlTNitmMjVUeW5obzdhZnM4UWVMditRamY1OUJUT1lHOUdSb1h3ellvcDhPNmM4MkxBVnFEYjBURHlBSVo4T25EUGE4S2lSU1VwbUJPa1JMbldmc1FGN3hYL1l6WS9rUDlVZE5lbXNmTG9FWnJtSW5vU01LSHFIcE52cFFXdk5YWU5uakpNK3VCTGhIeTB2RExHVlNiampCeUoyTjFTcVQ3YjZjOHdQSTBMcEdYbVpLcEsrYkEzMEhNT1ZSaVJHQStpdkoycmJJaUFaTCtaZUZRT2I1YzM4UnVYMk54MmV6bitiOFZwM3FYeUFYT2NIVWt4MzhlSk95ZUQxY3VuY2VZSE1JZ1N3RWV2clR1Z0xlRisvRHRVZzhxa010NHdFMGJxWXMyQ3kyVnZpY212SzB1OTA0K0ZDRThXUFBlS1JEVTFyNmVvbzhLZkZoOWd4aUk3V0hKQ2dmM3R2aHRCTVJxL1V2a1FZb0xWaXBIS3Q0QnNGbGQ3NUl1VFNXRWE4R0l0bkhrU1VlS2s0NEZTcmFQcEFZYnpRSzU2TVNvNkgxNDlyNTNxNVhTU2U0aTUrb081ejFMMVFhL0tLdlYxbHYvcHgvYVNMaDc5SmRXbXdRYWt5WmkrbzBkVlhnQ0p1Q0NacU45WTJ4SkNPelBwaHhTUzk5by9pcDNnb3U3eDdJQm5MUWxuNk5JZU4vOGhPMUhOQzZaYnJYdDNJQzVieVRRemRsM0dXSkc5TWZsSkduWkcreHA5ZFFiOUc0bW9JZ21qemRIMXdVTThkQXFWaW5zelRqNm1adnJSLine truncated
color: rgb(200, 195, 188);;;
background: #31363b;
color: #f1f1f1;
}
body[theme="dark"] .navbar-brand {
color: #fff;
body[theme="dark"] .navbar .navbar-brand {
color: #ffffff;
}
body[theme="dark"] .navbar-inverse, .nav.navbar-nav {
background-image: linear-gradient(rgb(60, 60, 60) 0px, rgb(34, 34, 34) 100%) !important;
body[theme="dark"] .navbar .navbar-brand {
color: #ffffff;
}
body[theme="dark"] .navbar-inverse .navbar-brand {
color: #fff;
}
body[theme="dark"] .dropdown-menu {
body[theme="dark"] .navbar .dropdown-menu {
list-style-image: initial;
background-color: rgb(24, 26, 27);
border-color: rgba(140, 130, 115, 0.15);
background-color: #171a1e;
border-color: #31363b;
box-shadow: rgba(0, 0, 0, 0.18) 0px 6px 12px;
}
body[theme="dark"] .dropdown-menu > li > a {
color: rgb(200, 195, 188);
body[theme="dark"] .navbar .dropdown-menu > li > a {
color: #c8c3bc;
}
body[theme="dark"] .dropdown-menu > li > a:focus,
body[theme="dark"] .dropdown-menu > li > a:hover {
background-color: rgb(37, 40, 42);
background-image: linear-gradient(rgb(30, 32, 33) 0px, rgb(37, 40, 42) 100%);
body[theme="dark"] .navbar .dropdown-menu > li > a:focus,
body[theme="dark"] .navbar .dropdown-menu > li > a:hover {
background-color: #25282a;
background-image: linear-gradient(#1c1d26 0, #1c1d26 100%);
}
body[theme="dark"] .navbar-inverse .navbar-nav > .open > a:focus,
body[theme="dark"] .navbar-inverse .navbar-nav > .open > a:hover,
body[theme="dark"] .navbar-inverse .navbar-nav > .active > a,
body[theme="dark"] .navbar-inverse .navbar-nav > .open > a {
body[theme="dark"] .navbar .navbar-nav > .open > a:focus,
body[theme="dark"] .navbar .navbar-nav > .open > a:hover,
body[theme="dark"] .navbar .navbar-nav > .active > a,
body[theme="dark"] .navbar .navbar-nav > .open > a{
background-image: none !important;
box-shadow: none !important;
background-color: transparent !important;
}
body[theme="dark"] .navbar .navbar-nav .open .dropdown-menu>li>a {
color: #f1f1f1;
}
body[theme="dark"] .toolbox i{
color: rgba(241,241,241,1);
background-color: rgba(0,0,0,0.5);
}
body[theme="dark"] .content {
background-image: initial;
background-color: rgb(24, 26, 27);
border-color: rgb(62, 67, 70);
box-shadow: rgba(0, 0, 0, 0.1) 0 1px 10px;
-webkit-box-shadow: rgba(0, 0, 0, 0.1) 0 1px 10px;
-moz-box-shadow: rgba(0, 0, 0, 0.1) 0 1px 10px;
background-color: #1c1d26;
border: none;
box-shadow: rgba(0, 0, 0, 0.5) 0 0.625em 2em;
-webkit-box-shadow: rgba(0, 0, 0, 0.5) 0 0.625em 2em;
box-shadow: rgba(0, 0, 0, 0.5) 0 0.625em 2em;
}
body[theme="dark"] .table {
background-image: initial;
background-color: rgb(24, 26, 27);
background-color: #1c1d26;
border-color: #545b5e;
}
body[theme="dark"] .table>thead>tr.node-group-tag>th {
background-color: #1c1d26;
}
body[theme="dark"] .table-striped tbody > tr.even > td,
body[theme="dark"] .table-striped tbody > tr.even > th {
background-color: rgb(24, 26, 27);;
background-color: #1c1d26;
}
body[theme="dark"] .table-striped tbody > tr.odd > td,
body[theme="dark"] .table-striped tbody > tr.odd > th {
background-color: rgb(27, 30, 31);
background-color: #1c1d26;
}
body[theme="dark"] .progress {
background-image: linear-gradient(rgb(35, 38, 40) 0px, rgb(30, 32, 33) 100%);
background-image: linear-gradient(#2c2c2c 0,#1c1d26 100%);
background-color: #1c1d26;
}
body[theme="dark"] .progress-bar {
@@ -75,58 +85,75 @@ body[theme="dark"] .progress-bar {
}
body[theme="dark"] .progress-bar-success {
background-image: linear-gradient(rgb(77, 133, 58) 0px, rgb(54, 126, 54) 100%);
background-image: linear-gradient(#4d853a 0, #367e36 100%);
}
body[theme="dark"] .progress-offline .progress-bar-success {
background-image: linear-gradient(grey 0, grey 100%);
}
body[theme="dark"] .progress-bar-warning {
background-image: linear-gradient(rgb(153, 95, 13) 0px, rgb(181, 113, 15) 100%);
background-image: linear-gradient(#995f0d 0, #b5710f 100%);
}
body[theme="dark"] .progress-offline .progress-bar-warning {
background-image: linear-gradient(grey 0, grey 100%);
}
body[theme="dark"] .progress-bar-danger {
background-image: linear-gradient(rgb(148, 35, 32) 0px, rgb(161, 38, 35) 100%);
background-image: linear-gradient(#942320 0, #a12623 100%);
}
body[theme="dark"] .table > tbody > tr > td,
body[theme="dark"] .table > tbody > tr > th,
body[theme="dark"] .table > tfoot > tr > td,
body[theme="dark"] .table > tfoot > tr > th,
body[theme="dark"] .table > thead > tr > td,
body[theme="dark"] .table > thead > tr > th {
border-top-color: rgb(58, 62, 65);
body[theme="dark"] .progress-offline .progress-bar-danger {
background-image: linear-gradient(grey 0, grey 100%);
}
body[theme="dark"] .table > thead > tr > th {
border-bottom-color: rgb(58, 62, 65);
body[theme="dark"] .table > tbody > tr > td:before,
body[theme="dark"] .table > tfoot > tr > td:before,
body[theme="dark"] .table > thead > tr > td:before,
body[theme="dark"] .table > thead > tr.node-group-cell > th:before{
background-color: rgba(58, 62, 65, 1);
}
body[theme="dark"] .table > tbody > tr.expandRow > td:before,
body[theme="dark"] .table > thead > tr.node-group-tag > th:before{
background-color: #1c1d26;
}
body[theme="dark"] .table-hover > tbody > tr:not(.expandRow):hover > td {
background-color: #222526;
background-color: #171a1e;
}
body[theme="dark"] .table-striped > tbody > tr:nth-of-type(2n+1) {
background-color: rgb(27, 30, 31);
background-color: #1c1d26;
}
body[theme="dark"] .node-cell.status .status-icon.online {
background-image: linear-gradient(rgb(77, 133, 58) 0px, rgb(54, 126, 54) 100%);
background-image: linear-gradient(#4d853a 0, #367e36 100%);
}
body[theme="dark"] .node-cell.status .status-icon.offline {
background-color: grey;
background-image: linear-gradient(#9b2522 0, #9b2522 100%);
}
body[theme="dark"] .service-status .danger {
background-image: linear-gradient(rgb(148, 35, 32) 0px, rgb(161, 38, 35) 100%);
background-image: linear-gradient(#942320 0, #a12623 100%);
}
body[theme="dark"] .service-status .good {
background-image: linear-gradient(rgb(77, 133, 58) 0px, rgb(54, 126, 54) 100%);
background-image: linear-gradient(#4d853a, #367e36 100%);
}
body[theme="dark"] .service-status .warning {
background-image: linear-gradient(rgb(153, 95, 13) 0px, rgb(181, 113, 15) 100%);
background-image: linear-gradient(#995f0d 0, #b5710f 100%);
}
body[theme="dark"] .service-day-status-icon {
background-color: grey;
}
body[theme="dark"] footer p a,
body[theme="dark"] footer p a:hover {
color: #f1f1f1;
text-decoration: none;
}
+73 -17
View File
@@ -2,17 +2,27 @@ body[theme="light"] {
background: #ebebeb url('data:image/png;base64,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 truncated
}
body[theme="light"] .navbar-brand {
body[theme="light"] .navbar .navbar-brand {
color: #fff;
}
body[theme="light"] .navbar-inverse,
body[theme="light"] .nav.navbar-nav {
background-image: linear-gradient(rgb(60, 60, 60) 0px, rgb(34, 34, 34) 100%) !important;
body[theme="light"] .navbar .navbar-brand {
color: #fff;
}
body[theme="light"] .navbar-inverse .navbar-brand {
color: #fff;
body[theme="light"] .navbar .navbar-nav > .open > a:focus,
body[theme="light"] .navbar .navbar-nav > .open > a:hover,
body[theme="light"] .navbar .navbar-nav > .active > a,
body[theme="light"] .navbar .navbar-nav > .open > a {
background-image: none !important;
box-shadow: none !important;
background-color: transparent !important;
}
body[theme="light"] .toolbox i{
color: rgba(94, 114, 228, .9);
background-color: rgba(255, 255, 255, .5);
box-shadow:0 .125rem .25rem rgba(0, 0, 0, .15) !important;
}
body[theme="light"] .content {
@@ -27,6 +37,10 @@ body[theme="light"] .table {
background: #ffffff;
}
body[theme="light"] .table>thead>tr.node-group-tag>th {
background-color:#ffffff;
}
body[theme="light"] .table-striped tbody > tr.even > td,
body[theme="light"] .table-striped tbody > tr.even > th {
background-color: #F9F9F9;
@@ -34,11 +48,47 @@ body[theme="light"] .table-striped tbody > tr.even > th {
body[theme="light"] .table-striped tbody > tr.odd > td,
body[theme="light"] .table-striped tbody > tr.odd > th {
background-color: #FFF;
background-color: #ffffff;
}
body[theme="light"] .table > tbody > tr > td:before,
body[theme="light"] .table > tfoot > tr > td:before,
body[theme="light"] .table > thead > tr > td:before,
body[theme="light"] .table > thead > tr.node-group-cell > th:before{
background-color: rgba(221, 221, 221, 1);
}
body[theme="light"] .table > tbody > tr.expandRow > td:before,
body[theme="light"] .table > thead > tr.node-group-tag > th:before{
background-color: rgba(255, 255, 255, 1);
}
body[theme="light"] .progress-bar {
color: #000;
color: #000000;
}
body[theme="light"] .progress-bar-success {
background-image: linear-gradient(rgb(92, 184, 92) 0, rgb(68, 157, 68) 100%);
}
body[theme="light"] .progress-offline .progress-bar-success {
background-image: linear-gradient(grey 0, grey 100%);
}
body[theme="light"] .progress-bar-warning {
background-image: linear-gradient(rgb(240, 173, 78) 0, rgb(236, 151, 31) 100%);
}
body[theme="light"] .progress-offline .progress-bar-warning {
background-image: linear-gradient(grey 0, grey 100%);
}
body[theme="light"] .progress-bar-danger {
background-image: linear-gradient(rgb(217, 83, 79) 0, rgb(201, 48, 44) 100%);
}
body[theme="light"] .progress-offline .progress-bar-danger {
background-image: linear-gradient(grey 0, grey 100%);
}
body[theme="light"] .table-hover > tbody > tr:hover > td {
@@ -50,29 +100,35 @@ body[theme="light"] tr.even.expandRow > :hover {
}
body[theme="light"] tr.odd.expandRow > :hover {
background: #FFF !important;
background: #ffffff !important;
}
body[theme="light"] .node-cell.status .status-icon.online {
background-color: rgb(92, 184, 92);
background-image: linear-gradient(rgb(92, 184, 92) 0, rgb(68, 157, 68) 100%);
}
body[theme="light"] .node-cell.status .status-icon.offline {
background-color: grey;
}
body[theme="light"] .service-status .danger {
background-image: linear-gradient(rgb(217, 83, 79) 0px, rgb(204, 65, 62) 100%);
background-image: linear-gradient(rgb(217, 83, 79) 0, rgb(201, 48, 44) 100%);
}
body[theme="light"] .service-status .good {
background-image: linear-gradient(rgb(92, 184, 92) 0px, rgb(68, 157, 68) 100%);
background-image: linear-gradient(rgb(92, 184, 92) 0, rgb(68, 157, 68) 100%);
}
body[theme="light"] .service-status .warning {
background-image: linear-gradient(rgb(240, 173, 78) 0px, rgb(238, 162, 54) 100%);
background-image: linear-gradient(rgb(240, 173, 78) 0, rgb(236, 151, 31) 100%);
}
body[theme="light"] .service-status .danger {
background-image: linear-gradient(rgb(217, 83, 79) 0, rgb(201, 48, 44) 100%);
}
body[theme="light"] .service-day-status-icon {
background-color: #ebebeb;
}
body[theme="light"] footer p a,
body[theme="light"] footer p a:hover {
color: #333333;
text-decoration: none;
}
+226 -107
View File
@@ -1,51 +1,125 @@
body {
padding-top: 70px !important;
padding-top:70px !important;
padding-bottom: 30px !important;
}
/* 导航部分 开始*/
.navbar {
min-height: 40px !important;
margin:0 auto;
font-size: 14px;
}
.navbar-collapse:not([aria-expanded]) .navbar-nav .dropdown-toggle {
.navbar .container{
max-width: 95vw;
margin: 0 auto;
}
.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-brand {
font-size: 20px;
text-align: center;
.navbar .navbar-toggle {
margin-right:0
}
.node-cell-expand {
max-width: 420px;
.navbar .navbar-brand {
font-size: 20px;
text-align: center;
padding:12px 0 0 0;
margin-right:30px;
}
.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;
}
.dropdown {
margin-right: 10px;
.navbar .navbar-nav {
margin:0px -15px;
}
.navbar-inverse, .nav.navbar-nav {
background-image: linear-gradient(rgb(60, 60, 60) 0px, rgb(34, 34, 34) 100%) !important;
.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;
@@ -63,6 +137,40 @@ body {
text-align: left;
}
.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 > 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;
}
@@ -72,12 +180,15 @@ body {
padding-left: 5px;
}
.expandRow > td {
padding: 0 !important;
border-top: 0 !important;
}
.accordian-body{
margin: 10px 0px 10px 18px;
}
.node-cell.center {
text-align: center !important;
}
@@ -106,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%;
@@ -138,113 +273,97 @@ 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 th{
font-size:16px;
padding-bottom:6px;
}
}
@media only screen and (max-width: 767px) {
body {
font-size: 10px !important;
padding-top:60px !important;
}
.content {
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.location,
.node-cell.uptime {
display: none;
visibility: hidden;
}
}
@media only screen and (max-width: 720px) {
body {
font-size: 10px !important;
}
.content {
padding: 0;
}
.node-cell.os,
.node-cell.location,
.node-cell.uptime {
display: none;
visibility: hidden;
}
.service-day-status-icon {
width: 8px;
height: 8px;
margin-right: 2px;
border-radius: 3px;
}
}
@media only screen and (max-width: 620px) {
body {
font-size: 10px !important;
}
.content {
padding: 0;
}
.node-cell.type,
.node-cell.location,
.node-cell.uptime,
.node-cell.traffic {
.node-cell.traffic{
display: none;
visibility: hidden;
}
.service-day-status-icon {
.node-cell.location{
display: table-cell;
visibility: visible;
}
}
@media only screen and (max-width: 533px) {
body {
font-size: 10px !important;
}
.content {
padding: 0;
}
.node-cell.os,
.node-cell.location,
.node-cell.uptime,
.node-cell.traffic {
.node-cell-os-text,.node-cell-location-text{
display: none;
visibility: hidden;
}
.service-day-status-icon {
.accordian-body{
margin: 5px 0px 5px 10px;
}
}
@media only screen and (max-width: 450px) {
body {
font-size: 10px !important;
.table .node-group-tag th{
font-size:16px;
padding-bottom:6px;
}
.content {
padding: 0;
.service-status .service-status-th{
min-width:30px;
}
.node-cell.type,
.node-cell.location,
.node-cell.uptime,
.node-cell.traffic {
.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;
}
.node-cell.cpu,
.node-cell.ram,
.node-cell.hdd {
min-width: 25px;
max-width: 50px;
.service-status .service-averagelatency-th{
min-width:70px;
}
.service-status .service-30daysonline-th{
min-width:75px;
}
.toolbox {
right: 18px;
}
}
@@ -1,7 +0,0 @@
/*!
* Bootstrap v3.3.7 (http://getbootstrap.com)
* Copyright 2011-2016 Twitter, Inc.
* Licensed under the MIT license
*/
if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:tLine truncated
this.activeTarget=b,this.clear();var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")},b.prototype.clear=function(){a(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.3.7",c.TRANSITION_DURATION=150,c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a"),f=a.Event("hide.bs.tab",{relatedTarget:b[0]}),g=a.Event("show.bs.tab",{relatedTarget:e[0]});if(e.trigger(f),b.trigger(g),!g.isDefaultPrevented()&&!f.isDefaultPrevented()){var h=a(d);this.activate(b.closest("li"),c),this.activate(h,h.parent(),function(){e.trigger({type:"hidden.bs.tab",relatedTarget:b[0]}),b.trigger({type:"shown.bs.tab",relatedTarget:e[0]})})}}},c.prototype.activate=function(b,d,e){function f(){g.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e<c&&"top";if("bottom"==this.affixed)return null!=c?!(e+this.unpin<=f.top)&&"bottom":!(e+g<=a-d)&&"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&e<=c?"top":null!=d&&i+j>=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery);
-2
View File
@@ -1,2 +0,0 @@
/*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */
!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0<t&&t-1 in e)}S.fn=S.prototype={jquery:f,constructor:S,length:0,toArray:function(){return s.call(this)},get:function(e){return null==e?s.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=S.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return S.each(this,e)},map:function(n){return this.pushStack(S.map(this,function(e,t){return n.call(e,t,e)}))},slice:function(){return this.pushStack(s.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(S.grep(this,function(e,t){return(t+1)%2}))},odd:function(){return this.pushStack(S.grep(this,function(e,t){return t%2}))},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(0<=n&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:u,sort:t.sort,splice:t.splice},S.extend=S.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||m(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)r=e[t],"__proto__"!==t&&a!==r&&(l&&r&&(S.isPlainObject(r)||(i=Array.isArray(r)))?(n=a[t],o=i&&!Array.isArray(n)?[]:i||S.isPlainObject(n)?n:{},i=!1,a[t]=S.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},S.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==o.call(e))&&(!(t=r(e))||"function"==typeof(n=v.call(t,"constructor")&&t.constructor)&&a.call(n)===l)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e,t,n){b(e,{nonce:t&&t.nonce},n)},each:function(e,t){var n,r=0;if(p(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},makeArray:function(e,t){var n=t||[];return null!=e&&(p(Object(e))?S.merge(n,"string"==typeof e?[e]:e):u.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:i.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r=[],i=0,o=e.length,a=!n;i<o;i++)!t(e[i],i)!==a&&r.push(e[i]);return r},map:function(e,t,n){var r,i,o=0,a=[];if(p(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&a.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&a.push(i);return g(a)},guid:1,support:y}),"function"==typeof Symbol&&(S.fn[Symbol.iterator]=t[Symbol.iterator]),S.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){n["[object "+t+"]"]=t.toLowerCase()});var d=function(n){var e,d,b,o,i,h,f,g,w,u,l,T,C,a,E,v,s,c,y,S="sizzle"+1*new Date,p=n.document,k=0,r=0,m=ue(),x=ue(),A=ue(),N=ue(),D=function(e,t){return e===t&&(l=!0),0},j={}.hasOwnProperty,t=[],q=t.pop,L=t.push,H=t.push,O=t.slice,P=function(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1},R="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",I="(?:\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",W="\\["+M+"*("+I+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+I+"))|)"+M+"*\\]",F=":("+I+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+W+")*)|.*)\\)|)",B=new RegExp(M+"+","g"),$=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),_=new RegExp("^"+M+"*,"+M+"*"),z=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-lLine truncated
+107 -10
View File
@@ -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;
}
}
}
+4 -4
View File
@@ -6,11 +6,11 @@
{{.Version}}</small>
</div>
</div>
<script src="https://cdn.staticfile.org/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.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/semantic-ui/2.4.1/semantic.min.js"></script>
<script src="/static/semantic-ui-alerts.min.js"></script>
<script src="https://cdn.staticfile.org/vue/2.6.14/vue.min.js"></script>
<script src="/static/main.js?v20230614"></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?v20240224"></script>
<script>
(function () {
updateLang({{.LANG }});
+2 -2
View File
@@ -9,8 +9,8 @@
<meta content="telephone=no" name="format-detection">
<title>{{.Title}}</title>
<link rel="stylesheet" type="text/css"
href="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.min.css">
<link href="https://cdn.staticfile.org/font-logos/0.17/font-logos.min.css" type="text/css"
href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/semantic-ui/2.4.1/semantic.min.css">
<link href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/font-logos/0.17/font-logos.min.css" type="text/css"
rel="stylesheet" />
<link rel="stylesheet" type="text/css" href="/static/semantic-ui-alerts.min.css">
<link rel="stylesheet" type="text/css" href="/static/main.css?v2022042314">
+1
View File
@@ -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">
+7
View File
@@ -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}}
+10
View File
@@ -26,6 +26,16 @@
<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">
<label>{{tr "DDNSDomain"}}</label>
<input type="text" name="DDNSDomain" placeholder="{{tr "DDNSDomain"}}">
</div>
<div class="field">
<label>{{tr "Note"}}</label>
<textarea name="Note"></textarea>
+5 -1
View File
@@ -29,6 +29,8 @@
<th>IP</th>
<th>{{tr "VersionNumber"}}</th>
<th>{{tr "HideForGuest"}}</th>
<th>{{tr "EnableDDNS"}}</th>
<th>{{tr "DDNSDomain"}}</th>
<th>{{tr "Secret"}}</th>
<th>{{tr "OneKeyInstall"}}</th>
<th>{{tr "Note"}}</th>
@@ -45,6 +47,8 @@
<td>{{$server.Host.IP}}</td>
<td>{{$server.Host.Version}}</td>
<td>{{$server.HideForGuest}}</td>
<td>{{$server.EnableDDNS}}</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>
@@ -85,7 +89,7 @@
</div>
{{template "component/server" .}}
{{template "common/footer" .}}
<script src="https://cdn.staticfile.org/clipboard.js/2.0.10/clipboard.min.js" type="application/javascript"></script>
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/clipboard.js/2.0.10/clipboard.min.js" type="application/javascript"></script>
<script>
var clipboard = new ClipboardJS('.ui.icon.green.mini.button');
const checkBoxList = document.querySelectorAll('tbody > tr > td > input.nezha-servers[type=checkbox]')
+11 -2
View File
@@ -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}}
+4 -4
View File
@@ -8,7 +8,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>tty@{{.ServerName}} - {{.Title}}</title>
<link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" />
<link href="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/xterm/4.11.0/xterm.css" type="text/css" rel="stylesheet"/>
<link href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/xterm/4.11.0/xterm.css" type="text/css" rel="stylesheet"/>
</head>
<style>
@@ -28,9 +28,9 @@
<body onresize="onResize()">
<div id="terminal-container"></div>
<script src="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/xterm/4.11.0/xterm.js" type="application/javascript"></script>
<script src="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/xterm/4.11.0/addons/attach/xterm-addon-attach.js"></script>
<script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/xterm/4.11.0/addons/fit/xterm-addon-fit.js"></script>
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/xterm/4.11.0/xterm.js" type="application/javascript"></script>
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/xterm/4.11.0/addons/attach/xterm-addon-attach.js"></script>
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/xterm/4.11.0/addons/fit/xterm-addon-fit.js"></script>
<script>
let sendResizing = false;
+6 -5
View File
@@ -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>&copy; <a style="color: white;" href="/">{{.Conf.Site.Brand}}</a></b> |
@@ -8,17 +9,17 @@
</small>
</div>
</div>
<script src="https://cdn.staticfile.org/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.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/semantic-ui/2.4.1/semantic.min.js"></script>
<script src="/static/semantic-ui-alerts.min.js"></script>
<script src="https://cdn.staticfile.org/vue/2.6.14/vue.min.js"></script>
<script src="/static/main.js?v20230614"></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?v20240224"></script>
<script src="/static/theme-default/js/mixin.js?v20240302"></script>
<script>
(function () {
updateLang({{.LANG }});
})();
</script>
</body>
</html>
{{end}}
+3 -3
View File
@@ -9,10 +9,10 @@
<meta content="telephone=no" name="format-detection">
<title>{{.Title}}</title>
<link rel="stylesheet" type="text/css"
href="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.min.css">
<link href="https://cdn.staticfile.org/font-logos/0.17/font-logos.min.css" type="text/css"
href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/semantic-ui/2.4.1/semantic.min.css">
<link href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/font-logos/0.17/font-logos.min.css" type="text/css"
rel="stylesheet" />
<link href="https://cdn.staticfile.org/bootstrap-icons/1.10.3/font/bootstrap-icons.css" type="text/css"
<link href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/bootstrap-icons/1.8.1/font/bootstrap-icons.min.css" type="text/css"
rel="stylesheet" />
<link rel="stylesheet" type="text/css" href="/static/semantic-ui-alerts.min.css">
<link rel="stylesheet" type="text/css" href="/static/main.css?v2022042314">
+4 -3
View File
@@ -4,7 +4,6 @@
{{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>
@@ -15,7 +14,7 @@
<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://cdn.staticfile.org/flag-icon-css/6.6.5/flags/1x1/'+server.Host.CountryCode + '.svg'" alt="国家"/>&nbsp;<i v-if='server.Host.Platform == "darwin"'
<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="国家"/>&nbsp;<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"}}]')#@
@@ -120,7 +119,6 @@
</div>
</div>
</div>
</div>
</div>
{{template "theme-angel-kanade/footer" .}}
<script>
@@ -129,10 +127,13 @@
el: '#app',
delimiters: ['@#', '#@'],
data: {
defaultTemplate: {{.Conf.Site.Theme}},
templates: {{.Themes}},
data: initData,
groups: [],
cache: [],
},
mixins: [mixinsVue],
created() {
this.group()
},
+12
View File
@@ -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
View File
@@ -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}}
+5 -4
View File
@@ -10,8 +10,8 @@
<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://cdn.staticfile.org/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<script src="https://cdn.staticfile.org/jquery/3.6.0/jquery.min.js"></script>
<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>
{{if ts .CustomCode}}
{{.CustomCode|safe}}
@@ -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}}
@@ -165,8 +166,8 @@
</footer>
<script src="/static/theme-daynight/js/main.js?v202102012266"></script>
<script src="https://cdn.staticfile.org/vue/2.6.14/vue.min.js"></script>
<script src="https://cdn.staticfile.org/limonte-sweetalert2/11.4.4/sweetalert2.all.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://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/limonte-sweetalert2/11.4.4/sweetalert2.all.min.js"></script>
<script>
const initData = JSON.parse('{{.Servers}}').servers;
+376
View File
@@ -0,0 +1,376 @@
{{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" style="font-size: .6em">
<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>&copy; <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}};
if (MaxTCPPingValue == null) {
MaxTCPPingValue = 1000;
}
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'];
let maxPing = 0;
for (let i = 0; i < monitorInfo.result.length; i++) {
for (let j = 0; j < monitorInfo.result[i].created_at.length; j++) {
avgDelay = Math.round(monitorInfo.result[i].avg_delay[j]);
if ( avgDelay < MaxTCPPingValue && avgDelay > maxPing) {
maxPing = avgDelay;
}
}
}
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([monitorInfo.result[i].created_at[j], maxPing]);
}
}
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,
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 }
]
}
});
tSeries.push({
name: legendName,
type: 'bar',
smooth: true,
symbol: 'none',
data: datal,
itemStyle: { color: rgbaColorBar }
});
}
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);
},
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>
<style>
#network {
display: grid;
/*grid-template-columns: repeat(5, 1fr);*/
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
grid-gap: 1em;
width: 100%;
width: calc(100vw - 6em);
max-width: 1400px;
margin: 1em auto;
align-content: start;
}
#monitor-info-container{
margin: 0em auto;
align-content: start;
background-color: #F1F1F2;
}
</style>
</html>
{{end}}
+4 -3
View File
@@ -8,9 +8,9 @@
<title>{{.Title}}</title>
<link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" />
<link rel="stylesheet" href="/static/theme-daynight/css/service.css?v202108042286" />
<link rel="stylesheet" href="https://cdn.staticfile.org/mdui/1.0.2/css/mdui.min.css"/>
<link href="https://cdn.staticfile.org/font-awesome/6.0.0/css/all.min.css" rel="stylesheet" />
<script src="https://cdn.staticfile.org/jquery/3.6.0/jquery.min.js"></script>
<link rel="stylesheet" href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/mdui/1.0.2/css/mdui.min.css"/>
<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>
{{if ts .CustomCode}}
{{.CustomCode|safe}}
@@ -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}}
+23
View File
@@ -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>&copy; <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
View File
@@ -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}}
+238 -26
View File
@@ -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">
<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>&nbsp;<i v-if='server.Host.Platform == "darwin"'
<img v-if="server.Host.CountryCode" style="border-radius: 5px;width:22px;" :src="'https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/6.6.5/flags/4x3/'+server.Host.CountryCode + '.svg'" alt="国家"/>&nbsp;<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)#@
&nbsp;
<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)#@
&nbsp;
<i class="bi bi-memory" style="font-size: 1.1rem; color: #00ac0d;"></i> @#getK2Gb(server.Host.MemTotal)#@
&nbsp;
<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);
});
},
methods: {
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
View File
@@ -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}}
+267
View File
@@ -0,0 +1,267 @@
{{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#@ &nbsp;<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}};
if (MaxTCPPingValue == null) {
MaxTCPPingValue = 1000;
}
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'];
let maxPing = 0;
for (let i = 0; i < monitorInfo.result.length; i++) {
for (let j = 0; j < monitorInfo.result[i].created_at.length; j++) {
avgDelay = Math.round(monitorInfo.result[i].avg_delay[j]);
if ( avgDelay < MaxTCPPingValue && avgDelay > maxPing) {
maxPing = avgDelay;
}
}
}
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([monitorInfo.result[i].created_at[j], maxPing]);
}
}
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,
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 }
]
}
});
tSeries.push({
name: legendName,
type: 'bar',
smooth: true,
symbol: 'none',
data: datal,
itemStyle: { color: rgbaColorBar }
});
}
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
View File
@@ -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}}
+5 -5
View File
@@ -14,8 +14,8 @@
<meta http-equiv="Content-Language" content="zh" />
<!-- Styles -->
<link rel="stylesheet" type="text/css" href="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.min.css">
<link rel="stylesheet" type="text/css" href="https://cdn.staticfile.org/font-awesome/6.0.0/css/all.min.css">
<link rel="stylesheet" type="text/css" href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/semantic-ui/2.4.1/semantic.min.css">
<link rel="stylesheet" type="text/css" href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/font-awesome/6.0.0/css/all.min.css">
<link rel="stylesheet" type="text/css" href="/static/semantic-ui-alerts.min.css">
<link rel="stylesheet" href="/static/theme-hotaru/css/core.css?v202012121912" type="text/css">
<link rel="stylesheet" href="/static/theme-hotaru/css/main.css?v202101171153" type="text/css">
@@ -141,10 +141,10 @@
<a href="/server">{{tr "AdminPanel"}}</a>
</p>
</footer>
<script src="https://cdn.staticfile.org/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.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/semantic-ui/2.4.1/semantic.min.js"></script>
<script src="/static/semantic-ui-alerts.min.js"></script>
<script src="https://cdn.staticfile.org/vue/2.6.14/vue.min.js"></script>
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/vue/2.6.14/vue.min.js"></script>
<script>
const initData = JSON.parse('{{.Servers}}').servers;
var statusCards = new Vue({
+5 -5
View File
@@ -9,7 +9,7 @@
<link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" />
<!-- MDUI CSS -->
<link rel="stylesheet" href="https://cdn.staticfile.org/mdui/1.0.2/css/mdui.min.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;}
@@ -37,7 +37,7 @@
<div class="mdui-col" v-for='server in servers' :id="server.ID">
<div :class="'mdui-card mt' + (server.live?'':' offline')">
<div class="mdui-card-header">
<img class="mdui-card-header-avatar" :src="'https://cdn.staticfile.org/flag-icon-css/4.1.5/flags/1x1/' + (server.Host.CountryCode?server.Host.CountryCode:'cn') + '.svg'"/>
<img class="mdui-card-header-avatar" :src="'https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/flag-icon-css/4.1.5/flags/1x1/' + (server.Host.CountryCode?server.Host.CountryCode:'cn') + '.svg'"/>
<div class="mdui-card-header-title">@#server.Name#@</div>
<div class="mdui-card-header-subtitle">@#server.Host.CountryCode.toUpperCase()#@ | @#server.Host.Platform#@ @#server.Host.PlatformVersion#@</div>
</div>
@@ -153,9 +153,9 @@
{{template "theme-mdui/footer" .}}
<script src="/static/theme-mdui/mdui.js"></script>
<script src="https://cdn.staticfile.org/mdui/1.0.2/js/mdui.min.js"></script>
<script src="https://cdn.staticfile.org/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/vue/2.6.14/vue.min.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>
var container=document.querySelector("#container");
+1
View File
@@ -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}}
+292
View File
@@ -0,0 +1,292 @@
{{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#@ &nbsp;</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}};
if (MaxTCPPingValue == null) {
MaxTCPPingValue = 1000;
}
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'];
let maxPing = 0;
for (let i = 0; i < monitorInfo.result.length; i++) {
for (let j = 0; j < monitorInfo.result[i].created_at.length; j++) {
avgDelay = Math.round(monitorInfo.result[i].avg_delay[j]);
if ( avgDelay < MaxTCPPingValue && avgDelay > maxPing) {
maxPing = avgDelay;
}
}
}
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([monitorInfo.result[i].created_at[j], maxPing]);
}
}
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,
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 }
]
}
});
tSeries.push({
name: legendName,
type: 'bar',
smooth: true,
symbol: 'none',
data: datal,
itemStyle: { color: rgbaColorBar }
});
}
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}}
+4 -4
View File
@@ -9,7 +9,7 @@
<link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" />
<!-- MDUI CSS -->
<link rel="stylesheet" href="https://cdn.staticfile.org/mdui/1.0.2/css/mdui.min.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">
{{if ts .CustomCode}}
{{.CustomCode|safe}}
@@ -116,9 +116,9 @@
{{template "theme-mdui/footer" .}}
<script src="/static/theme-mdui/mdui.js"></script>
<script src="https://cdn.staticfile.org/mdui/1.0.2/js/mdui.min.js"></script>
<script src="https://cdn.staticfile.org/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/vue/2.6.14/vue.min.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>
var container=document.querySelector("#container");
container.style.minHeight=window.innerHeight-document.body.clientHeight+container.clientHeight+'px';
+3 -3
View File
@@ -8,7 +8,7 @@
<title>{{.Title}}</title>
<!-- MDUI CSS -->
<link rel="stylesheet" href="https://cdn.staticfile.org/mdui/1.0.2/css/mdui.min.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">
{{if ts .CustomCode}}
{{.CustomCode|safe}}
@@ -34,8 +34,8 @@
</div>
</div>
<script src="https://cdn.staticfile.org/mdui/1.0.2/js/mdui.min.js"></script>
<script src="https://cdn.staticfile.org/jquery/3.6.0/jquery.min.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>
var $input = mdui.$('#container').find('.mdui-textfield-input');
var $dialog = new mdui.Dialog(mdui.$('.mdui-dialog'));
+14 -2
View File
@@ -1,8 +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 href="https://github.com/cppla/ServerStatus">ServerStatus</a> | Powered by <a
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}}
+43 -27
View File
@@ -1,7 +1,6 @@
{{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">
<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>
@@ -9,39 +8,56 @@
<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;">
<a href="/" class="navbar-brand">
<img src="/static/logo.svg" style="height: 2rem;display: inline-block;">
{{.Conf.Site.Brand}}
</a>
</div>
<div class="navbar-collapse collapse">
<nav id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="/">{{tr "Home" }}</a></li>
<li><a href="/service">{{tr "Services" }}</a></li>
<li><a href="/"><i class="home icon"></i>{{tr "Home" }}</a></li>
<template v-if="isMobile">
<li class="dropdown">
<a data-toggle="dropdown"><i class="bi bi-gear-wide-connected" style="position:relative;top:1px;margin-right:3px;font-size:1.1rem;"></i>{{tr "Feature" }}<b class="caret"></b></a>
<ul class="dropdown-menu" style="min-width:100px;">
<li><a href="/service"><i class="rss icon"></i>{{tr "Services" }}</a></li>
<li><a href="/network"><i class="bi bi-hdd-network icon"></i>{{tr "NetworkSpiter"}}</a></li>
</ul>
</li>
</template>
<template v-else>
<li><a href="/service"><i class="rss icon"></i>{{tr "Services" }}</a></li>
<li><a href="/network"><i class="bi bi-hdd-network icon"></i>{{tr "NetworkSpiter"}}</a></li>
</template>
{{ 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">
<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>
{{ end }}
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a data-toggle="dropdown" href="#">{{tr "Menu" }}<b class="caret"></b></a>
<ul class="dropdown-menu">
{{if .Admin}}
<li><a href="/server">{{tr "AdminPanel" }} ({{.Admin.Name}})</a></li>
<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">{{tr "Login" }}</a></li>
<li><a href="/login"><i class="sign-in icon"></i>{{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>
</ul>
</li>
</ul>
</nav>
</div>
</div>
</div>
</div>
</header>
{{end}}
+13 -13
View File
@@ -1,20 +1,21 @@
{{define "theme-server-status/header"}}
<!DOCTYPE html>
<html lang="{{.Conf.Language}}">
<head>
<title>{{ .Title }}</title>
<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?v20231109">
<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 href="https://cdn.staticfile.org/font-logos/0.17/font-logos.min.css" type="text/css" rel="stylesheet" />
<link rel="stylesheet" type="text/css" href="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.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]/font/bootstrap-icons.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/gh/lipis/[email protected]/css/flag-icons.min.css">
<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>
@@ -23,12 +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://cdn.staticfile.org/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/[email protected]"></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}}
{{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">&nbsp;@#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}}
+228 -157
View File
@@ -2,140 +2,18 @@
{{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">
<thead>
<tr>
<th class="node-cell status center">🍀 {{tr "Status"}}</th>
<th class="node-cell name">🚀 {{tr "Name"}}</th>
<th class="node-cell os">🗂 {{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 nodes">
<tr :id="'r'+index" data-toggle="collapse" :data-target="'#rt'+index" 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">@#node.name#@</td>
<td class="node-cell os">
<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>
@#node.os#@
</td>
<td style="text-align: center;" class="node-cell location">
<i :class="node.location + ' flag'"></i>&nbsp;
<span class="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">
<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">
<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">
<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'+index">
<div style="display: flex;align-items: center;justify-content: center;flex-direction: column;">
<div style="display: flex;align-items: flex-start;justify-content: center;flex-direction: column; width: 450px;max-width: 90vw">
<span class="node-cell-expand">
<span class="node-cell-expand-label">{{tr "Platform"}}:</span>
@#node.host.Platform#@
</span>
<span class="node-cell-expand">
<span class="node-cell-expand-label">{{tr "Location"}}:</span>
<i :class="node.location + ' flag'"></i>
</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 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 "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 "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">
<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 "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" v-if="node.host.Virtualization">
<span class="node-cell-expand-label">{{tr "Virtualization"}}:</span>
@#node.host.Virtualization#@
</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>
</div>
</div>
</div>
</td>
</tr>
<!-- 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>
</tbody>
</table>
<br/>
</div>
{{template "theme-server-status/content-footer" .}}
</div>
<script>
@@ -143,16 +21,38 @@
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 = this.handleNodes(initData);
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) {
@@ -233,17 +133,22 @@
return x !== "NaN undefined" ? x : '0B'
},
formatPercent(live, used, total) {
const percent = live ? (this.toFixed2(used / total * 100) || 0) : 1
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 = 1;
percent = 0;
}
if (percent >= 100) {
percent = 100;
}
if (!this.cache[percent]) {
this.cache[percent] = {
class: 'progress-bar progress-bar-success',
style: `width: ${parseInt(percent)}%`,
style: `width: ${parseInt(percent)+1}%`,
percent,
}
if (percent < 80) {
@@ -265,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++) {
@@ -282,27 +193,32 @@
data.servers[i].live = data.now - lastActive <= 10 * 1000;
}
}
this.nodes = this.handleNodes(data.servers)
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) {
let nodes = []
servers?.forEach(server => {
let platform = server.Host.Platform
if (this.isWindowsPlatform(server.Host.Platform)) {
let platform = server.Host.Platform;
if (this.isWindowsPlatform(platform)) {
platform = "windows"
}else if (platform === "immortalwrt") {
platform = "openwrt"
}
let node = {
ID: server.ID,
name: server.Name,
os: platform,
location: server.Host.CountryCode,
@@ -310,13 +226,14 @@
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,
state: server.State,
host: server.Host,
lastActive: server.LastActive,
Tag: server.Tag,
}
nodes.push(node)
})
@@ -324,10 +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);
}
});
}
}
})
</script>
{{template "theme-server-status/footer" .}}
{{end}}
+250
View File
@@ -0,0 +1,250 @@
{{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#@&nbsp;&nbsp;<i :class="'fi fi-' + server.Host.CountryCode"></i><span class="node-cell-location-text text-uppercase">&nbsp;&nbsp;@#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}};
if (MaxTCPPingValue == null) {
MaxTCPPingValue = 1000;
}
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'];
let maxPing = 0;
for (let i = 0; i < monitorInfo.result.length; i++) {
for (let j = 0; j < monitorInfo.result[i].created_at.length; j++) {
avgDelay = Math.round(monitorInfo.result[i].avg_delay[j]);
if ( avgDelay < MaxTCPPingValue && avgDelay > maxPing) {
maxPing = avgDelay;
}
}
}
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([monitorInfo.result[i].created_at[j], maxPing]);
}
}
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,
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 }
]
}
});
tSeries.push({
name: legendName,
type: 'bar',
smooth: true,
symbol: 'none',
data: datal,
itemStyle: { color: rgbaColorBar }
});
}
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}}
+32 -51
View File
@@ -2,55 +2,28 @@
{{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">
<thead>
<tr>
<th class="node-cell" style="min-width: 60px">🍀 {{tr "Status"}}</th>
<th class="node-cell" style="min-width: 60px">🚀 {{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>
</thead>
<tbody id="servers">
<template v-for="service in services">
<tr>
<td style="text-align: left" class="node-cell">
<div class="delay-today">
<i class="delay-today" :class="service.health.className"></i>
@#service.health.text#@
</div>
</td>
<td class="node-cell">@#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>
<!-- 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>
</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>
<!-- 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>
</tbody>
</table>
</div>
<div class="container" style="margin-bottom: 20px;padding:unset;max-width: 95vw">
<section class="container content table-responsive" style="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">
<table class="table table-striped table-condensed table-hover">
<thead>
<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>
@@ -89,23 +62,29 @@
{{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) {
+10
View File
@@ -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
+1 -2
View File
@@ -6,10 +6,9 @@ services:
restart: always
volumes:
- ./data:/dashboard/data
- ./static-custom/static:/dashboard/resource/static/custom:ro
- ./theme-custom/template:/dashboard/resource/template/theme-custom:ro
- ./theme-custom/static:/dashboard/resource/static/theme-custom:ro
- ./dashboard-custom/template:/dashboard/resource/template/dashboard-custom:ro
- ./dashboard-custom/static:/dashboard/resource/static/dashboard-custom:ro
ports:
- nz_site_port:80
- nz_grpc_port:nz_grpc_port
+474 -26
View File
@@ -11,7 +11,10 @@ NZ_BASE_PATH="/opt/nezha"
NZ_DASHBOARD_PATH="${NZ_BASE_PATH}/dashboard"
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
NZ_AGENT_SERVICE="/etc/systemd/system/nezha-agent.service"
NZ_VERSION="v0.15.0"
NZ_AGENT_SERVICERC="/etc/init.d/nezha-agent"
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
NZ_VERSION="v0.15.9"
red='\033[0;31m'
green='\033[0;32m'
@@ -47,7 +50,7 @@ pre_check() {
if [[ -z "${CN}" ]]; then
if [[ $(curl -m 10 -s https://ipapi.co/json | grep 'China') != "" ]]; then
echo "根据ipapi.co提供的信息,当前IP可能在中国"
read -e -r -p "是否选用中国镜像完成安装? [Y/n] " input
read -e -r -p "是否选用中国镜像完成安装? [Y/n] (自定义镜像输入 3):" input
case $input in
[yY][eE][sS] | [yY])
echo "使用中国镜像"
@@ -57,6 +60,17 @@ pre_check() {
[nN][oO] | [nN])
echo "不使用中国镜像"
;;
[3])
echo "使用自定义镜像"
read -e -r -p "请输入自定义镜像 (例如:dn-dao-github-mirror.daocloud.io),留空为不使用: " input
case $input in
*)
CUSTOM_MIRROR=$input
;;
esac
;;
*)
echo "使用中国镜像"
CN=true
@@ -65,6 +79,13 @@ pre_check() {
fi
fi
if [[ -n "${CUSTOM_MIRROR}" ]]; then
GITHUB_RAW_URL="gitee.com/naibahq/nezha/raw/master"
GITHUB_URL=$CUSTOM_MIRROR
Get_Docker_URL="get.docker.com"
Get_Docker_Argu=" -s docker --mirror Aliyun"
Docker_IMG="registry.cn-shanghai.aliyuncs.com\/naibahq\/nezha-dashboard"
else
if [[ -z "${CN}" ]]; then
GITHUB_RAW_URL="raw.githubusercontent.com/naiba/nezha/master"
GITHUB_URL="github.com"
@@ -72,12 +93,14 @@ pre_check() {
Get_Docker_Argu=" "
Docker_IMG="ghcr.io\/naiba\/nezha-dashboard"
else
GITHUB_RAW_URL="jihulab.com/nezha/dashboard/-/raw/master"
GITHUB_RAW_URL="gitee.com/naibahq/nezha/raw/master"
GITHUB_URL="dn-dao-github-mirror.daocloud.io"
Get_Docker_URL="get.daocloud.io/docker"
Get_Docker_URL="get.docker.com"
Get_Docker_Argu=" -s docker --mirror Aliyun"
Docker_IMG="registry.cn-shanghai.aliyuncs.com\/naibahq\/nezha-dashboard"
fi
fi
}
confirm() {
@@ -124,7 +147,8 @@ install_base() {
(command -v git >/dev/null 2>&1 && command -v curl >/dev/null 2>&1 && command -v wget >/dev/null 2>&1 && command -v unzip >/dev/null 2>&1 && command -v getenforce >/dev/null 2>&1) ||
(install_soft curl wget git unzip)
}
install_arch(){
install_arch() {
echo -e "${green}提示: ${plain} Arch安装libselinux需添加nezha-agent用户,安装完会自动删除,建议手动检查一次\n"
read -e -r -p "是否安装libselinux? [Y/n] " input
case $input in
@@ -135,7 +159,7 @@ install_arch(){
cd /tmp; git clone https://aur.archlinux.org/libsepol.git; cd libsepol; makepkg -si --noconfirm --asdeps; cd ..;
git clone https://aur.archlinux.org/libselinux.git; cd libselinux; makepkg -si --noconfirm; cd ..;
rm -rf libsepol libselinux'
sed -i '/nezha-agent/d' /etc/sudoers && sleep 30s && killall -u nezha-agent&&userdel nezha-agent
sed -i '/nezha-agent/d' /etc/sudoers && sleep 30s && killall -u nezha-agent && userdel -r nezha-agent
echo -e "${red}提示: ${plain}已删除用户nezha-agent,请务必手动核查一遍!\n"
;;
[nN][oO] | [nN])
@@ -162,7 +186,7 @@ install_dashboard() {
echo -e "> 安装面板"
# 哪吒监控文件夹
if [ ! -d "${NZ_DASHBOARD_PATH}" ]; then
if [ ! -d "${NZ_DASHBOARD_PATH}/docker-compose.yaml" ]; then
mkdir -p $NZ_DASHBOARD_PATH
else
echo "您可能已经安装过面板端,重复安装会覆盖数据,请注意备份。"
@@ -204,11 +228,51 @@ install_dashboard() {
fi
}
selinux(){
install_dashboard_standalone() {
install_base
echo -e "> 安装面板"
# 哪吒监控文件夹
if [ ! -d "${NZ_DASHBOARD_PATH}/app" ]; then
mkdir -p $NZ_DASHBOARD_PATH
else
echo "您可能已经安装过面板端,重复安装会覆盖数据,请注意备份。"
read -e -r -p "是否退出安装? [Y/n] " input
case $input in
[yY][eE][sS] | [yY])
echo "退出安装"
exit 0
;;
[nN][oO] | [nN])
echo "继续安装"
;;
*)
echo "退出安装"
exit 0
;;
esac
fi
if [ ! -d "${NZ_DASHBOARD_PATH}/resource/template/theme-custom" ] || [ ! -d "${NZ_DASHBOARD_PATH}/resource/static/custom" ]; then
mkdir -p "${NZ_DASHBOARD_PATH}/resource/template/theme-custom" "${NZ_DASHBOARD_PATH}/resource/static/custom" >/dev/null 2>&1
fi
chmod 777 -R $NZ_DASHBOARD_PATH
modify_dashboard_config_standalone 0
if [[ $# == 0 ]]; then
before_show_menu
fi
}
selinux() {
#判断当前的状态
if [ "$os_alpine" != 1 ];then
command -v getenforce >/dev/null 2>&1
if [ $? -eq 0 ]; then
getenforce | grep '[Ee]nfor'
if [ $? -eq 0 ];then
if [ $? -eq 0 ]; then
echo -e "SELinux是开启状态,正在关闭!"
setenforce 0 &>/dev/null
find_key="SELINUX="
@@ -269,12 +333,19 @@ install_agent() {
modify_agent_config() {
echo -e "> 修改Agent配置"
if [ "$os_alpine" != 1 ];then
if [ "$os_alpine" != 1 ]; then
wget -t 2 -T 10 -O $NZ_AGENT_SERVICE https://${GITHUB_RAW_URL}/script/nezha-agent.service >/dev/null 2>&1
if [[ $? != 0 ]]; then
echo -e "${red}文件下载失败,请检查本机能否连接 ${GITHUB_RAW_URL}${plain}"
return 0
fi
else
wget -t 2 -T 10 -O $NZ_AGENT_SERVICERC https://${GITHUB_RAW_URL}/script/nezha-agent >/dev/null 2>&1
chmod +x $NZ_AGENT_SERVICERC
if [[ $? != 0 ]]; then
echo -e "${red}Fail to download service, please check if the network can link ${GITHUB_RAW_URL}${plain}"
return 0
fi
fi
if [ $# -lt 3 ]; then
@@ -283,7 +354,7 @@ modify_agent_config() {
read -ep "请输入面板RPC端口 (默认值 5555): " nz_grpc_port &&
read -ep "请输入Agent 密钥: " nz_client_secret &&
read -ep "是否启用针对 gRPC 端口的 SSL/TLS加密 (--tls),需要请按 [y],默认是不需要,不理解用户可回车跳过: " nz_grpc_proxy
grep -qiw 'Y' <<< "${nz_grpc_proxy}" && args='--tls'
grep -qiw 'Y' <<<"${nz_grpc_proxy}" && args='--tls'
if [[ -z "${nz_grpc_host}" || -z "${nz_client_secret}" ]]; then
echo -e "${red}所有选项都不能为空${plain}"
before_show_menu
@@ -302,29 +373,32 @@ modify_agent_config() {
fi
fi
if [ "$os_alpine" != 1 ];then
if [ "$os_alpine" != 1 ]; then
sed -i "s/nz_grpc_host/${nz_grpc_host}/" ${NZ_AGENT_SERVICE}
sed -i "s/nz_grpc_port/${nz_grpc_port}/" ${NZ_AGENT_SERVICE}
sed -i "s/nz_client_secret/${nz_client_secret}/" ${NZ_AGENT_SERVICE}
[ -n "${args}" ] && sed -i "/ExecStart/ s/$/ ${args}/" ${NZ_AGENT_SERVICE}
else
echo "@reboot ${NZ_AGENT_PATH}/nezha-agent -s ${nz_grpc_host}:${nz_grpc_port} -p ${nz_client_secret} ${args}" >> /etc/crontabs/root
crond
sed -i "s/nz_grpc_host/${nz_grpc_host}/" ${NZ_AGENT_SERVICERC}
sed -i "s/nz_grpc_port/${nz_grpc_port}/" ${NZ_AGENT_SERVICERC}
sed -i "s/nz_client_secret/${nz_client_secret}/" ${NZ_AGENT_SERVICERC}
[ -n "${args}" ] && sed -i "/command_args/ s/\"$/ ${args}\"/" ${NZ_AGENT_SERVICERC}
fi
echo -e "Agent配置 ${green}修改成功,请稍等重启生效${plain}"
if [ "$os_alpine" != 1 ];then
if [ "$os_alpine" != 1 ]; then
systemctl daemon-reload
systemctl enable nezha-agent
systemctl restart nezha-agent
else
nohup ${NZ_AGENT_PATH}/nezha-agent -s ${nz_grpc_host}:${nz_grpc_port} -p ${nz_client_secret} ${args} >/dev/null 2>&1 &
rc-update add nezha-agent
rc-service nezha-agent restart
fi
if [[ $# == 0 ]]; then
before_show_menu
fi
#if [[ $# == 0 ]]; then
# before_show_menu
#fi
}
modify_dashboard_config() {
@@ -393,6 +467,77 @@ modify_dashboard_config() {
fi
}
modify_dashboard_config_standalone() {
echo -e "> 修改面板配置"
echo -e "正在下载配置模板"
wget -t 2 -T 10 -O /tmp/nezha-config.yaml https://${GITHUB_RAW_URL}/script/config.yaml >/dev/null 2>&1
if [[ $? != 0 ]]; then
echo -e "${red}下载文件失败,请检查本机能否连接 ${GITHUB_RAW_URL}${plain}"
return 0
fi
echo "关于 GitHub Oauth2 应用:在 https://github.com/settings/developers 创建,无需审核,Callback 填 http(s)://域名或IP/oauth2/callback" &&
echo "关于 Gitee Oauth2 应用:在 https://gitee.com/oauth/applications 创建,无需审核,Callback 填 http(s)://域名或IP/oauth2/callback" &&
read -ep "请输入 OAuth2 提供商(github/gitlab/jihulab/gitee,默认 github): " nz_oauth2_type &&
read -ep "请输入 Oauth2 应用的 Client ID: " nz_github_oauth_client_id &&
read -ep "请输入 Oauth2 应用的 Client Secret: " nz_github_oauth_client_secret &&
read -ep "请输入 GitHub/Gitee 登录名作为管理员,多个以逗号隔开: " nz_admin_logins &&
read -ep "请输入站点标题: " nz_site_title &&
read -ep "请输入站点访问端口: (默认 8008)" nz_site_port &&
read -ep "请输入用于 Agent 接入的 RPC 端口: (默认 5555)" nz_grpc_port
if [[ -z "${nz_admin_logins}" || -z "${nz_github_oauth_client_id}" || -z "${nz_github_oauth_client_secret}" || -z "${nz_site_title}" ]]; then
echo -e "${red}所有选项都不能为空${plain}"
before_show_menu
return 1
fi
if [[ -z "${nz_site_port}" ]]; then
nz_site_port=8008
fi
if [[ -z "${nz_grpc_port}" ]]; then
nz_grpc_port=5555
fi
if [[ -z "${nz_oauth2_type}" ]]; then
nz_oauth2_type=github
fi
sed -i "s/nz_oauth2_type/${nz_oauth2_type}/" /tmp/nezha-config.yaml
sed -i "s/nz_admin_logins/${nz_admin_logins}/" /tmp/nezha-config.yaml
sed -i "s/nz_grpc_port/${nz_grpc_port}/" /tmp/nezha-config.yaml
sed -i "s/nz_github_oauth_client_id/${nz_github_oauth_client_id}/" /tmp/nezha-config.yaml
sed -i "s/nz_github_oauth_client_secret/${nz_github_oauth_client_secret}/" /tmp/nezha-config.yaml
sed -i "s/nz_language/zh-CN/" /tmp/nezha-config.yaml
sed -i "s/nz_site_title/${nz_site_title}/" /tmp/nezha-config.yaml
sed -i "s/80/${nz_site_port}/" /tmp/nezha-config.yaml
mkdir -p $NZ_DASHBOARD_PATH/data
mv -f /tmp/nezha-config.yaml ${NZ_DASHBOARD_PATH}/data/config.yaml
echo -e "正在下载服务文件"
if [ "$os_alpine" != 1 ]; then
wget -t 2 -T 10 -O $NZ_DASHBOARD_SERVICE https://${GITHUB_RAW_URL}/script/nezha-dashboard.service >/dev/null 2>&1
else
wget -t 2 -T 10 -O $NZ_DASHBOARD_SERVICERC https://${GITHUB_RAW_URL}/script/nezha-dashboard >/dev/null 2>&1
chmod +x $NZ_DASHBOARD_SERVICERC
if [[ $? != 0 ]]; then
echo -e "${red}文件下载失败,请检查本机能否连接 ${GITHUB_RAW_URL}${plain}"
return 0
fi
fi
echo -e "面板配置 ${green}修改成功,请稍等重启生效${plain}"
restart_and_update_standalone
if [[ $# == 0 ]]; then
before_show_menu
fi
}
restart_and_update() {
echo -e "> 重启并更新面板"
@@ -421,6 +566,40 @@ restart_and_update() {
fi
}
restart_and_update_standalone() {
echo -e "> 重启并更新面板"
cd $NZ_DASHBOARD_PATH
if [ "$os_alpine" != 1 ]; then
systemctl stop nezha-dashboard
else
rc-service nezha-dashboard stop
fi
wget -qO app.zip https://${GITHUB_URL}/naiba/nezha/releases/latest/download/dashboard-linux-$os_arch.zip >/dev/null 2>&1 && unzip -qq app.zip && mv dist/dashboard-linux-$os_arch app && rm -r app.zip dist
if [ "$os_alpine" != 1 ]; then
systemctl daemon-reload
systemctl enable nezha-dashboard
systemctl restart nezha-dashboard
else
rc-update add nezha-dashboard
rc-service nezha-dashboard restart
fi
if [[ $? == 0 ]]; then
echo -e "${green}哪吒监控 重启成功${plain}"
echo -e "默认管理面板地址:${yellow}域名:站点访问端口${plain}"
else
echo -e "${red}重启失败,可能是因为启动时间超过了两秒,请稍后查看日志信息${plain}"
fi
if [[ $# == 0 ]]; then
before_show_menu
fi
}
start_dashboard() {
echo -e "> 启动面板"
@@ -442,6 +621,26 @@ start_dashboard() {
fi
}
start_dashboard_standalone() {
echo -e "> 启动面板"
if [ "$os_alpine" != 1 ]; then
systemctl start nezha-dashboard
else
rc-service nezha-dashboard start
fi
if [[ $? == 0 ]]; then
echo -e "${green}哪吒监控 启动成功${plain}"
else
echo -e "${red}启动失败,请稍后查看日志信息${plain}"
fi
if [[ $# == 0 ]]; then
before_show_menu
fi
}
stop_dashboard() {
echo -e "> 停止面板"
@@ -463,6 +662,26 @@ stop_dashboard() {
fi
}
stop_dashboard_standalone() {
echo -e "> 停止面板"
if [ "$os_alpine" != 1 ]; then
systemctl stop nezha-dashboard
else
rc-service nezha-dashboard stop
fi
if [[ $? == 0 ]]; then
echo -e "${green}哪吒监控 停止成功${plain}"
else
echo -e "${red}停止失败,请稍后查看日志信息${plain}"
fi
if [[ $# == 0 ]]; then
before_show_menu
fi
}
show_dashboard_log() {
echo -e "> 获取面板日志"
@@ -478,6 +697,20 @@ show_dashboard_log() {
fi
}
show_dashboard_log_standalone() {
echo -e "> 获取面板日志"
if [ "$os_alpine" != 1 ]; then
journalctl -xf -u nezha-dashboard.service
else
echo "很抱歉,OpenRC 无此功能"
fi
if [[ $# == 0 ]]; then
before_show_menu
fi
}
uninstall_dashboard() {
echo -e "> 卸载管理面板"
@@ -489,8 +722,32 @@ uninstall_dashboard() {
fi
rm -rf $NZ_DASHBOARD_PATH
docker rmi -f ghcr.io/naiba/nezha-dashboard > /dev/null 2>&1
docker rmi -f registry.cn-shanghai.aliyuncs.com/naibahq/nezha-dashboard > /dev/null 2>&1
docker rmi -f ghcr.io/naiba/nezha-dashboard >/dev/null 2>&1
docker rmi -f registry.cn-shanghai.aliyuncs.com/naibahq/nezha-dashboard >/dev/null 2>&1
clean_all
if [[ $# == 0 ]]; then
before_show_menu
fi
}
uninstall_dashboard_standalone() {
echo -e "> 卸载管理面板"
rm -rf $NZ_DASHBOARD_PATH
if [ "$os_alpine" != 1 ]; then
systemctl stop nezha-dashboard
else
rc-service nezha-dashboard stop
fi
if [ "$os_alpine" != 1 ]; then
rm $NZ_DASHBOARD_SERVICE
else
rm $NZ_DASHBOARD_SERVICERC
fi
clean_all
if [[ $# == 0 ]]; then
@@ -501,7 +758,11 @@ uninstall_dashboard() {
show_agent_log() {
echo -e "> 获取Agent日志"
if [ "$os_alpine" != 1 ]; then
journalctl -xf -u nezha-agent.service
else
echo "很抱歉,OpenRC 无此功能"
fi
if [[ $# == 0 ]]; then
before_show_menu
@@ -511,14 +772,15 @@ show_agent_log() {
uninstall_agent() {
echo -e "> 卸载Agent"
if [ "$os_alpine" != 1 ];then
if [ "$os_alpine" != 1 ]; then
systemctl disable nezha-agent.service
systemctl stop nezha-agent.service
rm -rf $NZ_AGENT_SERVICE
systemctl daemon-reload
else
grep -q 'nezha-agent' /var/spool/cron/crontabs/root && sed -i "/nezha-agent/d" /var/spool/cron/crontabs/root
pkill nezha-agent
rc-update del nezha-agent
rc-service nezha-agent stop
rm -rf $NZ_AGENT_SERVICERC
fi
rm -rf $NZ_AGENT_PATH
@@ -532,7 +794,11 @@ uninstall_agent() {
restart_agent() {
echo -e "> 重启Agent"
if [ "$os_alpine" != 1 ]; then
systemctl restart nezha-agent.service
else
rc-service nezha-agent restart
fi
if [[ $# == 0 ]]; then
before_show_menu
@@ -545,6 +811,59 @@ clean_all() {
fi
}
select_version() {
DOCKER_COMPOSE_COMMAND=""
if docker compose version >/dev/null 2>&1; then
DOCKER_COMPOSE_COMMAND="docker compose"
if $DOCKER_COMPOSE_COMMAND ls | grep -qw "$NZ_DASHBOARD_PATH/docker-compose.yaml" >/dev/null 2>&1; then
NEZHA_IMAGES=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep -w "nezha-dashboard")
if [ -n "$NEZHA_IMAGES" ]; then
echo "存在带有 nezha-dashboard 仓库的 Docker 镜像:"
echo "$NEZHA_IMAGES"
IS_DOCKER_NEZHA=1
return
else
echo "未找到带有 nezha-dashboard 仓库的 Docker 镜像。"
fi
fi
elif command -v docker-compose >/dev/null 2>&1; then
DOCKER_COMPOSE_COMMAND="docker-compose"
if $DOCKER_COMPOSE_COMMAND -f "$NZ_DASHBOARD_PATH/docker-compose.yaml" config >/dev/null 2>&1; then
NEZHA_IMAGES=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep -w "nezha-dashboard")
if [ -n "$NEZHA_IMAGES" ]; then
echo "存在带有 nezha-dashboard 仓库的 Docker 镜像:"
echo "$NEZHA_IMAGES"
IS_DOCKER_NEZHA=1
return
else
echo "未找到带有 nezha-dashboard 仓库的 Docker 镜像。"
fi
fi
fi
if [[ -f $NZ_DASHBOARD_PATH/app ]]; then
IS_DOCKER_NEZHA=0
else
echo -e "${yellow}请自行选择您的安装方式(如果你是安装Agent,输入哪个都是一样的):\n1. Docker\n2. 独立安装${plain}"
while true; do
read -e -r -p "请输入数字 [1-2]:" option
case "${option}" in
1)
IS_DOCKER_NEZHA=1
break
;;
2)
IS_DOCKER_NEZHA=0
break
;;
*)
echo "输入有误,请重新输入"
;;
esac
done
fi
}
show_usage() {
echo "哪吒监控 管理脚本使用方法: "
echo "--------------------------------------------------------"
@@ -589,7 +908,7 @@ show_menu() {
${green}0.${plain} 退出脚本
"
echo && read -ep "请输入选择 [0-13]: " num
if [[ $IS_DOCKER_NEZHA == 1 ]]; then
case "${num}" in
0)
exit 0
@@ -637,11 +956,63 @@ show_menu() {
echo -e "${red}请输入正确的数字 [0-13]${plain}"
;;
esac
elif [[ $IS_DOCKER_NEZHA == 0 ]]; then
case "${num}" in
0)
exit 0
;;
1)
install_dashboard_standalone
;;
2)
modify_dashboard_config_standalone
;;
3)
start_dashboard_standalone
;;
4)
stop_dashboard_standalone
;;
5)
restart_and_update_standalone
;;
6)
show_dashboard_log_standalone
;;
7)
uninstall_dashboard_standalone
;;
8)
install_agent
;;
9)
modify_agent_config
;;
10)
show_agent_log
;;
11)
uninstall_agent
;;
12)
restart_agent
;;
13)
update_script
;;
*)
echo -e "${red}请输入正确的数字 [0-13]${plain}"
;;
esac
else
select_version
fi
}
pre_check
if [[ $# > 0 ]]; then
if [[ $IS_DOCKER_NEZHA == 1 ]]; then
case $1 in
"install_dashboard")
install_dashboard 0
@@ -689,6 +1060,83 @@ if [[ $# > 0 ]]; then
;;
*) show_usage ;;
esac
elif [[ $IS_DOCKER_NEZHA == 0 ]]; then
case $1 in
"install_dashboard")
install_dashboard_standalone 0
;;
"modify_dashboard_config")
modify_dashboard_config_standalone 0
;;
"start_dashboard")
start_dashboard_standalone 0
;;
"stop_dashboard")
stop_dashboard_standalone 0
;;
"restart_and_update")
restart_and_update_standalone 0
;;
"show_dashboard_log")
show_dashboard_log_standalone 0
;;
"uninstall_dashboard")
uninstall_dashboard_standalone 0
;;
"install_agent")
shift
if [ $# -ge 3 ]; then
install_agent "$@"
else
install_agent 0
fi
;;
"modify_agent_config")
modify_agent_config 0
;;
"show_agent_log")
show_agent_log 0
;;
"uninstall_agent")
uninstall_agent 0
;;
"restart_agent")
restart_agent 0
;;
"update_script")
update_script 0
;;
*) show_usage ;;
esac
else
case $1 in
"install_agent")
shift
if [ $# -ge 3 ]; then
install_agent "$@"
else
install_agent 0
fi
;;
"modify_agent_config")
modify_agent_config 0
;;
"show_agent_log")
show_agent_log 0
;;
"uninstall_agent")
uninstall_agent 0
;;
"restart_agent")
restart_agent 0
;;
"update_script")
update_script 0
;;
*) show_usage ;;
esac
fi
else
select_version
show_menu
fi
+475 -30
View File
@@ -11,7 +11,10 @@ NZ_BASE_PATH="/opt/nezha"
NZ_DASHBOARD_PATH="${NZ_BASE_PATH}/dashboard"
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
NZ_AGENT_SERVICE="/etc/systemd/system/nezha-agent.service"
NZ_VERSION="v0.15.0"
NZ_AGENT_SERVICERC="/etc/init.d/nezha-agent"
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
NZ_VERSION="v0.15.9"
red='\033[0;31m'
green='\033[0;32m'
@@ -47,7 +50,7 @@ pre_check() {
if [[ -z "${CN}" ]]; then
if [[ $(curl -m 10 -s https://ipapi.co/json | grep 'China') != "" ]]; then
echo "According to the information provided by ipapi.co, the current IP may be in China"
read -e -r -p "Is the installation done with a Chinese Mirror? [Y/n] " input
read -e -r -p "Is the installation done with a Chinese Mirror? [Y/n] (Custom Mirror Input 3):" input
case $input in
[yY][eE][sS] | [yY])
echo "Use Chinese Mirror"
@@ -57,6 +60,17 @@ pre_check() {
[nN][oO] | [nN])
echo "No Use Chinese Mirror"
;;
[3])
echo "Use Custom Mirror"
read -e -r -p "Please enter a custom image (e.g. :dn-dao-github-mirror.daocloud.io), leave blank to nouse: " input
case $input in
*)
CUSTOM_MIRROR=$input
;;
esac
;;
*)
echo "No Use Chinese Mirror"
;;
@@ -64,6 +78,13 @@ pre_check() {
fi
fi
if [[ -n "${CUSTOM_MIRROR}" ]]; then
GITHUB_RAW_URL="gitee.com/naibahq/nezha/raw/master"
GITHUB_URL=$CUSTOM_MIRROR
Get_Docker_URL="get.docker.com"
Get_Docker_Argu=" -s docker --mirror Aliyun"
Docker_IMG="registry.cn-shanghai.aliyuncs.com\/naibahq\/nezha-dashboard"
else
if [[ -z "${CN}" ]]; then
GITHUB_RAW_URL="raw.githubusercontent.com/naiba/nezha/master"
GITHUB_URL="github.com"
@@ -71,17 +92,18 @@ pre_check() {
Get_Docker_Argu=" "
Docker_IMG="ghcr.io\/naiba\/nezha-dashboard"
else
GITHUB_RAW_URL="jihulab.com/nezha/dashboard/-/raw/master"
GITHUB_RAW_URL="gitee.com/naibahq/nezha/raw/master"
GITHUB_URL="dn-dao-github-mirror.daocloud.io"
Get_Docker_URL="get.daocloud.io/docker"
Get_Docker_URL="get.docker.com"
Get_Docker_Argu=" -s docker --mirror Aliyun"
Docker_IMG="registry.cn-shanghai.aliyuncs.com\/naibahq\/nezha-dashboard"
fi
fi
}
confirm() {
if [[ $# > 1 ]]; then
echo && read -e -p "$1 [Default$2]: " temp
echo && read -e -p "$1 [默认$2]: " temp
if [[ x"${temp}" == x"" ]]; then
temp=$2
fi
@@ -101,7 +123,7 @@ update_script() {
curl -sL https://${GITHUB_RAW_URL}/script/install_en.sh -o /tmp/nezha.sh
new_version=$(cat /tmp/nezha.sh | grep "NZ_VERSION" | head -n 1 | awk -F "=" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
if [ ! -n "$new_version" ]; then
echo -e "Script failed to get, please check if the network can link https://${GITHUB_RAW_URL}/script/install_en.sh"
echo -e "Script failed to get, please check if the network can link https://${GITHUB_RAW_URL}/script/install.sh"
return 1
fi
echo -e "The current latest version is: ${new_version}"
@@ -121,9 +143,10 @@ before_show_menu() {
install_base() {
(command -v git >/dev/null 2>&1 && command -v curl >/dev/null 2>&1 && command -v wget >/dev/null 2>&1 && command -v unzip >/dev/null 2>&1 && command -v getenforce >/dev/null 2>&1) ||
(install_soft curl wget git unzip)
(install_soft curl wget git unzip tzdata)
}
install_arch(){
install_arch() {
echo -e "${green}Info: ${plain} Archlinux needs to add nezha-agent user to install libselinux. It will be deleted automatically after installation. It is recommended to check manually\n"
read -e -r -p "Do you need to install libselinux? [Y/n] " input
case $input in
@@ -134,7 +157,7 @@ install_arch(){
cd /tmp; git clone https://aur.archlinux.org/libsepol.git; cd libsepol; makepkg -si --noconfirm --asdeps; cd ..;
git clone https://aur.archlinux.org/libselinux.git; cd libselinux; makepkg -si --noconfirm; cd ..;
rm -rf libsepol libselinux'
sed -i '/nezha-agent/d' /etc/sudoers && sleep 30s && killall -u nezha-agent&&userdel nezha-agent
sed -i '/nezha-agent/d' /etc/sudoers && sleep 30s && killall -u nezha-agent && userdel -r nezha-agent
echo -e "${red}Info: ${plain}user nezha-agent has been deleted, Be sure to check it manually!\n"
;;
[nN][oO] | [nN])
@@ -147,7 +170,6 @@ install_arch(){
esac
}
install_soft() {
(command -v yum >/dev/null 2>&1 && yum makecache && yum install $* selinux-policy -y) ||
(command -v apt >/dev/null 2>&1 && apt update && apt install $* selinux-utils -y) ||
@@ -162,7 +184,7 @@ install_dashboard() {
echo -e "> Install Panel"
# Nezha Monitoring Folder
if [ ! -d "${NZ_DASHBOARD_PATH}" ]; then
if [ ! -d "${NZ_DASHBOARD_PATH}/docker-compose.yaml" ]; then
mkdir -p $NZ_DASHBOARD_PATH
else
echo "You may have already installed the dashboard, repeated installation will overwrite the data, please pay attention to backup."
@@ -203,11 +225,51 @@ install_dashboard() {
fi
}
selinux(){
install_dashboard_standalone() {
install_base
echo -e "> Install Panel"
# Nezha Monitoring Folder
if [ ! -d "${NZ_DASHBOARD_PATH}/app" ]; then
mkdir -p $NZ_DASHBOARD_PATH
else
echo "You may have already installed the dashboard, repeated installation will overwrite the data, please pay attention to backup."
read -e -r -p "Exit the installation? [Y/n] " input
case $input in
[yY][eE][sS] | [yY])
echo "Exit the installation."
exit 0
;;
[nN][oO] | [nN])
echo "Continue."
;;
*)
echo "Exit the installation."
exit 0
;;
esac
fi
if [ ! -d "${NZ_DASHBOARD_PATH}/resource/template/theme-custom" ] || [ ! -d "${NZ_DASHBOARD_PATH}/resource/static/custom" ]; then
mkdir -p "${NZ_DASHBOARD_PATH}/resource/template/theme-custom" "${NZ_DASHBOARD_PATH}/resource/static/custom" >/dev/null 2>&1
fi
chmod 777 -R $NZ_DASHBOARD_PATH
modify_dashboard_config_standalone 0
if [[ $# == 0 ]]; then
before_show_menu
fi
}
selinux() {
#Check SELinux
if [ "$os_alpine" != 1 ];then
command -v getenforce >/dev/null 2>&1
if [ $? -eq 0 ]; then
getenforce | grep '[Ee]nfor'
if [ $? -eq 0 ];then
if [ $? -eq 0 ]; then
echo -e "SELinux running,closing now!"
setenforce 0 &>/dev/null
find_key="SELINUX="
@@ -268,12 +330,19 @@ install_agent() {
modify_agent_config() {
echo -e "> Modify Agent Configuration"
if [ "$os_alpine" != 1 ];then
if [ "$os_alpine" != 1 ]; then
wget -t 2 -T 10 -O $NZ_AGENT_SERVICE https://${GITHUB_RAW_URL}/script/nezha-agent.service >/dev/null 2>&1
if [[ $? != 0 ]]; then
echo -e "${red}Fail to download service, please check if the network can link ${GITHUB_RAW_URL}${plain}"
return 0
fi
else
wget -t 2 -T 10 -O $NZ_AGENT_SERVICERC https://${GITHUB_RAW_URL}/script/nezha-agent >/dev/null 2>&1
chmod +x $NZ_AGENT_SERVICERC
if [[ $? != 0 ]]; then
echo -e "${red}Fail to download service, please check if the network can link ${GITHUB_RAW_URL}${plain}"
return 0
fi
fi
if [ $# -lt 3 ]; then
@@ -282,7 +351,7 @@ modify_agent_config() {
read -ep "Please enter the panel RPC port (default 5555): " nz_grpc_port &&
read -ep "Please enter the Agent secret: " nz_client_secret &&
read -ep "Do you want to enable SSL/TLS encryption for the gRPC port (--tls)? Press [y] if yes, the default is not required, and users can press Enter to skip if you don't understand: " nz_grpc_proxy
grep -qiw 'Y' <<< "${nz_grpc_proxy}" && args='--tls'
grep -qiw 'Y' <<<"${nz_grpc_proxy}" && args='--tls'
if [[ -z "${nz_grpc_host}" || -z "${nz_client_secret}" ]]; then
echo -e "${red}All options cannot be empty${plain}"
before_show_menu
@@ -301,29 +370,32 @@ modify_agent_config() {
fi
fi
if [ "$os_alpine" != 1 ];then
if [ "$os_alpine" != 1 ]; then
sed -i "s/nz_grpc_host/${nz_grpc_host}/" ${NZ_AGENT_SERVICE}
sed -i "s/nz_grpc_port/${nz_grpc_port}/" ${NZ_AGENT_SERVICE}
sed -i "s/nz_client_secret/${nz_client_secret}/" ${NZ_AGENT_SERVICE}
[ -n "${args}" ] && sed -i "/ExecStart/ s/$/ ${args}/" ${NZ_AGENT_SERVICE}
else
echo "@reboot ${NZ_AGENT_PATH}/nezha-agent -s ${nz_grpc_host}:${nz_grpc_port} -p ${nz_client_secret} ${args}" >> /etc/crontabs/root
crond
sed -i "s/nz_grpc_host/${nz_grpc_host}/" ${NZ_AGENT_SERVICERC}
sed -i "s/nz_grpc_port/${nz_grpc_port}/" ${NZ_AGENT_SERVICERC}
sed -i "s/nz_client_secret/${nz_client_secret}/" ${NZ_AGENT_SERVICERC}
[ -n "${args}" ] && sed -i "/command_args/ s/\"$/ ${args}\"/" ${NZ_AGENT_SERVICERC}
fi
echo -e "Agent configuration ${green} modified successfully, please wait for agent self-restart to take effect${plain}"
if [ "$os_alpine" != 1 ];then
if [ "$os_alpine" != 1 ]; then
systemctl daemon-reload
systemctl enable nezha-agent
systemctl restart nezha-agent
else
nohup ${NZ_AGENT_PATH}/nezha-agent -s ${nz_grpc_host}:${nz_grpc_port} -p ${nz_client_secret} ${args} >/dev/null 2>&1 &
rc-update add nezha-agent
rc-service nezha-agent restart
fi
if [[ $# == 0 ]]; then
before_show_menu
fi
#if [[ $# == 0 ]]; then
# before_show_menu
#fi
}
modify_dashboard_config() {
@@ -392,6 +464,77 @@ modify_dashboard_config() {
fi
}
modify_dashboard_config_standalone() {
echo -e "> Modify Panel Configuration"
echo -e "Download configuration template"
wget -t 2 -T 10 -O /tmp/nezha-config.yaml https://${GITHUB_RAW_URL}/script/config.yaml >/dev/null 2>&1
if [[ $? != 0 ]]; then
echo -e "${red}File failed to get, please check if the network can link ${GITHUB_RAW_URL}${plain}"
return 0
fi
echo "About the GitHub Oauth2 application: create it at https://github.com/settings/developers, no review required, and fill in the http(s)://domain_or_IP/oauth2/callback" &&
echo "(Not recommended) About the Gitee Oauth2 application: create it at https://gitee.com/oauth/applications, no auditing required, and fill in the http(s)://domain_or_IP/oauth2/callback" &&
read -ep "Please enter the OAuth2 provider (github/gitlab/jihulab/gitee, default github): " nz_oauth2_type &&
read -ep "Please enter the Client ID of the Oauth2 application: " nz_github_oauth_client_id &&
read -ep "Please enter the Client Secret of the Oauth2 application: " nz_github_oauth_client_secret &&
read -ep "Please enter your GitHub/Gitee login name as the administrator, separated by commas: " nz_admin_logins &&
read -ep "Please enter the site title: " nz_site_title &&
read -ep "Please enter the site access port: (default 8008)" nz_site_port &&
read -ep "Please enter the RPC port to be used for Agent access: (default 5555)" nz_grpc_port
if [[ -z "${nz_admin_logins}" || -z "${nz_github_oauth_client_id}" || -z "${nz_github_oauth_client_secret}" || -z "${nz_site_title}" ]]; then
echo -e "${red}All options cannot be empty${plain}"
before_show_menu
return 1
fi
if [[ -z "${nz_site_port}" ]]; then
nz_site_port=8008
fi
if [[ -z "${nz_grpc_port}" ]]; then
nz_grpc_port=5555
fi
if [[ -z "${nz_oauth2_type}" ]]; then
nz_oauth2_type=github
fi
sed -i "s/nz_oauth2_type/${nz_oauth2_type}/" /tmp/nezha-config.yaml
sed -i "s/nz_admin_logins/${nz_admin_logins}/" /tmp/nezha-config.yaml
sed -i "s/nz_grpc_port/${nz_grpc_port}/" /tmp/nezha-config.yaml
sed -i "s/nz_github_oauth_client_id/${nz_github_oauth_client_id}/" /tmp/nezha-config.yaml
sed -i "s/nz_github_oauth_client_secret/${nz_github_oauth_client_secret}/" /tmp/nezha-config.yaml
sed -i "s/nz_language/zh-CN/" /tmp/nezha-config.yaml
sed -i "s/nz_site_title/${nz_site_title}/" /tmp/nezha-config.yaml
sed -i "s/80/${nz_site_port}/" /tmp/nezha-config.yaml
mkdir -p $NZ_DASHBOARD_PATH/data
mv -f /tmp/nezha-config.yaml ${NZ_DASHBOARD_PATH}/data/config.yaml
echo -e "Downloading service file"
if [ "$os_alpine" != 1 ]; then
wget -t 2 -T 10 -O $NZ_DASHBOARD_SERVICE https://${GITHUB_RAW_URL}/script/nezha-dashboard.service >/dev/null 2>&1
else
wget -t 2 -T 10 -O $NZ_DASHBOARD_SERVICERC https://${GITHUB_RAW_URL}/script/nezha-dashboard >/dev/null 2>&1
chmod +x $NZ_DASHBOARD_SERVICERC
if [[ $? != 0 ]]; then
echo -e "${red}File failed to get, please check if the network can link ${GITHUB_RAW_URL}${plain}"
return 0
fi
fi
echo -e "Dashboard configuration ${green} modified successfully, please wait for Dashboard self-restart to take effect${plain}"
restart_and_update_standalone
if [[ $# == 0 ]]; then
before_show_menu
fi
}
restart_and_update() {
echo -e "> Restart and Update the Panel"
@@ -420,6 +563,40 @@ restart_and_update() {
fi
}
restart_and_update_standalone() {
echo -e "> Restart and Update the Panel"
cd $NZ_DASHBOARD_PATH
if [ "$os_alpine" != 1 ]; then
systemctl stop nezha-dashboard
else
rc-service nezha-dashboard stop
fi
wget -qO app.zip https://${GITHUB_URL}/naiba/nezha/releases/latest/download/dashboard-linux-$os_arch.zip >/dev/null 2>&1 && unzip -qq app.zip && mv dist/dashboard-linux-$os_arch app && rm -r app.zip dist
if [ "$os_alpine" != 1 ]; then
systemctl daemon-reload
systemctl enable nezha-dashboard
systemctl restart nezha-dashboard
else
rc-update add nezha-dashboard
rc-service nezha-dashboard restart
fi
if [[ $? == 0 ]]; then
echo -e "${green}Nezha Monitoring Restart Successful${plain}"
echo -e "Default panel address: ${yellow}domain:Site_access_port${plain}"
else
echo -e "${red}The restart failed, probably because the boot time exceeded two seconds, please check the log information later${plain}"
fi
if [[ $# == 0 ]]; then
before_show_menu
fi
}
start_dashboard() {
echo -e "> Start Panel"
@@ -441,6 +618,26 @@ start_dashboard() {
fi
}
start_dashboard_standalone() {
echo -e "> Start Panel"
if [ "$os_alpine" != 1 ]; then
systemctl start nezha-dashboard
else
rc-service nezha-dashboard start
fi
if [[ $? == 0 ]]; then
echo -e "${green}Nezha Monitoring Start Successful${plain}"
else
echo -e "${red}Failed to start, please check the log message later${plain}"
fi
if [[ $# == 0 ]]; then
before_show_menu
fi
}
stop_dashboard() {
echo -e "> Stop Panel"
@@ -462,6 +659,26 @@ stop_dashboard() {
fi
}
stop_dashboard_standalone() {
echo -e "> Stop Panel"
if [ "$os_alpine" != 1 ]; then
systemctl stop nezha-dashboard
else
rc-service nezha-dashboard stop
fi
if [[ $? == 0 ]]; then
echo -e "${green}Nezha Monitoring Stop Successful${plain}"
else
echo -e "${red}Failed to stop, please check the log message later${plain}"
fi
if [[ $# == 0 ]]; then
before_show_menu
fi
}
show_dashboard_log() {
echo -e "> View Panel Log"
@@ -477,6 +694,20 @@ show_dashboard_log() {
fi
}
show_dashboard_log_standalone() {
echo -e "> View Panel Log"
if [ "$os_alpine" != 1 ]; then
journalctl -xf -u nezha-dashboard.service
else
echo "Sorry, OpenRC do not have this feature yet."
fi
if [[ $# == 0 ]]; then
before_show_menu
fi
}
uninstall_dashboard() {
echo -e "> Uninstall Panel"
@@ -488,7 +719,32 @@ uninstall_dashboard() {
fi
rm -rf $NZ_DASHBOARD_PATH
docker rmi -f ghcr.io/naiba/nezha-dashboard > /dev/null 2>&1
docker rmi -f ghcr.io/naiba/nezha-dashboard >/dev/null 2>&1
docker rmi -f registry.cn-shanghai.aliyuncs.com/naibahq/nezha-dashboard >/dev/null 2>&1
clean_all
if [[ $# == 0 ]]; then
before_show_menu
fi
}
uninstall_dashboard_standalone() {
echo -e "> Uninstall Panel"
rm -rf $NZ_DASHBOARD_PATH
if [ "$os_alpine" != 1 ]; then
systemctl stop nezha-dashboard
else
rc-service nezha-dashboard stop
fi
if [ "$os_alpine" != 1 ]; then
rm $NZ_DASHBOARD_SERVICE
else
rm $NZ_DASHBOARD_SERVICERC
fi
clean_all
if [[ $# == 0 ]]; then
@@ -497,9 +753,13 @@ uninstall_dashboard() {
}
show_agent_log() {
echo -e "> View Agent Log"
echo -e "> > View Agent Log"
if [ "$os_alpine" != 1 ]; then
journalctl -xf -u nezha-agent.service
else
echo "Sorry, OpenRC do not have this feature yet."
fi
if [[ $# == 0 ]]; then
before_show_menu
@@ -509,14 +769,15 @@ show_agent_log() {
uninstall_agent() {
echo -e "> Uninstall Agent"
if [ "$os_alpine" != 1 ];then
if [ "$os_alpine" != 1 ]; then
systemctl disable nezha-agent.service
systemctl stop nezha-agent.service
rm -rf $NZ_AGENT_SERVICE
systemctl daemon-reload
else
grep -q 'nezha-agent' /var/spool/cron/crontabs/root && sed -i "/nezha-agent/d" /var/spool/cron/crontabs/root
pkill nezha-agent
rc-update del nezha-agent
rc-service nezha-agent stop
rm -rf $NZ_AGENT_SERVICERC
fi
rm -rf $NZ_AGENT_PATH
@@ -530,7 +791,11 @@ uninstall_agent() {
restart_agent() {
echo -e "> Restart Agent"
if [ "$os_alpine" != 1 ]; then
systemctl restart nezha-agent.service
else
rc-service nezha-agent restart
fi
if [[ $# == 0 ]]; then
before_show_menu
@@ -543,6 +808,59 @@ clean_all() {
fi
}
select_version() {
DOCKER_COMPOSE_COMMAND=""
if docker compose version >/dev/null 2>&1; then
DOCKER_COMPOSE_COMMAND="docker compose"
if $DOCKER_COMPOSE_COMMAND ls | grep -qw "$NZ_DASHBOARD_PATH/docker-compose.yaml" >/dev/null 2>&1; then
NEZHA_IMAGES=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep -w "nezha-dashboard")
if [ -n "$NEZHA_IMAGES" ]; then
echo "Docker image with nezha-dashboard repository exists:"
echo "$NEZHA_IMAGES"
IS_DOCKER_NEZHA=1
return
else
echo "No Docker images with the nezha-dashboard repository were found."
fi
fi
elif command -v docker-compose >/dev/null 2>&1; then
DOCKER_COMPOSE_COMMAND="docker-compose"
if $DOCKER_COMPOSE_COMMAND -f "$NZ_DASHBOARD_PATH/docker-compose.yaml" config >/dev/null 2>&1; then
NEZHA_IMAGES=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep -w "nezha-dashboard")
if [ -n "$NEZHA_IMAGES" ]; then
echo "Docker image with nezha-dashboard repository exists:"
echo "$NEZHA_IMAGES"
IS_DOCKER_NEZHA=1
return
else
echo "No Docker images with the nezha-dashboard repository were found."
fi
fi
fi
if [[ -f $NZ_DASHBOARD_PATH/app ]]; then
IS_DOCKER_NEZHA=0
else
echo -e "${yellow}Select your installation method(Input anything is ok if you are installing agent):\n1. Docker\n2. Standalone${plain}"
while true; do
read -e -r -p "Input a number [1-2]: " option
case "${option}" in
1)
IS_DOCKER_NEZHA=1
break
;;
2)
IS_DOCKER_NEZHA=0
break
;;
*)
echo "Wrong input, try again"
;;
esac
done
fi
}
show_usage() {
echo "Nezha Monitor Management Script Usage: "
echo "--------------------------------------------------------"
@@ -587,7 +905,7 @@ show_menu() {
${green}0.${plain} Exit Script
"
echo && read -ep "Please enter [0-13]: " num
if [[ $IS_DOCKER_NEZHA == 1 ]]; then
case "${num}" in
0)
exit 0
@@ -635,11 +953,61 @@ show_menu() {
echo -e "${red}Please enter the correct number [0-13]${plain}"
;;
esac
elif [[ $IS_DOCKER_NEZHA == 0 ]]; then
case "${num}" in
0)
exit 0
;;
1)
install_dashboard_standalone
;;
2)
modify_dashboard_config_standalone
;;
3)
start_dashboard_standalone
;;
4)
stop_dashboard_standalone
;;
5)
restart_and_update_standalone
;;
6)
show_dashboard_log_standalone
;;
7)
uninstall_dashboard_standalone
;;
8)
install_agent
;;
9)
modify_agent_config
;;
10)
show_agent_log
;;
11)
uninstall_agent
;;
12)
restart_agent
;;
13)
update_script
;;
*)
echo -e "${red}Please enter the correct number [0-13]${plain}"
;;
esac
fi
}
pre_check
if [[ $# > 0 ]]; then
if [[ $IS_DOCKER_NEZHA == 1 ]]; then
case $1 in
"install_dashboard")
install_dashboard 0
@@ -687,6 +1055,83 @@ if [[ $# > 0 ]]; then
;;
*) show_usage ;;
esac
elif [[ $IS_DOCKER_NEZHA == 0 ]]; then
case $1 in
"install_dashboard")
install_dashboard_standalone 0
;;
"modify_dashboard_config")
modify_dashboard_config_standalone 0
;;
"start_dashboard")
start_dashboard_standalone 0
;;
"stop_dashboard")
stop_dashboard_standalone 0
;;
"restart_and_update")
restart_and_update_standalone 0
;;
"show_dashboard_log")
show_dashboard_log_standalone 0
;;
"uninstall_dashboard")
uninstall_dashboard_standalone 0
;;
"install_agent")
shift
if [ $# -ge 3 ]; then
install_agent "$@"
else
install_agent 0
fi
;;
"modify_agent_config")
modify_agent_config 0
;;
"show_agent_log")
show_agent_log 0
;;
"uninstall_agent")
uninstall_agent 0
;;
"restart_agent")
restart_agent 0
;;
"update_script")
update_script 0
;;
*) show_usage ;;
esac
else
case $1 in
"install_agent")
shift
if [ $# -ge 3 ]; then
install_agent "$@"
else
install_agent 0
fi
;;
"modify_agent_config")
modify_agent_config 0
;;
"show_agent_log")
show_agent_log 0
;;
"uninstall_agent")
uninstall_agent 0
;;
"restart_agent")
restart_agent 0
;;
"update_script")
update_script 0
;;
*) show_usage ;;
esac
fi
else
select_version
show_menu
fi
+62
View File
@@ -0,0 +1,62 @@
#!/sbin/openrc-run
supervisor=supervise-daemon
name=nezha-agent
supervise_daemon_args="--stdout /var/log/${name}.log --stderr /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
}
+16
View File
@@ -0,0 +1,16 @@
#!/sbin/openrc-run
pidfile="/run/${RC_SVCNAME}.pid"
command="/opt/nezha/dashboard/app"
command_args=""
command_background=true
directory="/opt/nezha/dashboard"
depend() {
need net
}
reload() {
ebegin "Reloading ${RC_SVCNAME}"
start-stop-daemon --signal HUP --pidfile "${pidfile}"
eend $?
}
+3 -3
View File
@@ -1,5 +1,5 @@
[Unit]
Description=Nezha Dashborad
Description=Nezha Dashboard
After=syslog.target
After=network.target
After=mariadb.service mysqld.service postgresql.service memcached.service redis.service
@@ -14,8 +14,8 @@ After=mariadb.service mysqld.service postgresql.service memcached.service redis.
Type=simple
#User=root
#Group=root
WorkingDirectory=/root/nezha/dashboard/
ExecStart=/root/nezha/dashboard/nezha-dashboard-linux-amd64
WorkingDirectory=/opt/nezha/dashboard/
ExecStart=/opt/nezha/dashboard/app
Restart=always
#Environment=DEBUG=true
+33
View File
@@ -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: true,
EnableIpv6: true,
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])) &&
+65 -1
View File
@@ -1,9 +1,11 @@
package singleton
import (
"sync"
"time"
"github.com/naiba/nezha/model"
"github.com/naiba/nezha/pkg/utils"
"sync"
)
var (
@@ -12,6 +14,9 @@ var (
ApiLock sync.RWMutex
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
}
Loaded 100 of 103 files, more files were not shown because too many files have changed in this diff. Show more