Compare commits

...
73 Commits
Author SHA1 Message Date
naiba fe234f4f38 improve: Handle case when host is not found in LookupGeoIP 2024-07-28 15:11:50 +08:00
naiba df9487b5fc fix: CountryCode 被冲掉 2024-07-28 14:39:14 +08:00
UUBulbandnaiba 26d81f6d7a feat: use embed geoip database (#396)
* feat: use embed geoip database

* mention ipinfo

* only read once

* comments

comments

* chore: installer version

---------

Co-authored-by: naiba <[email protected]>
2024-07-28 13:59:58 +08:00
UUBulb 9d96c58ff9 api: add DisPlayIndex info (#397) 2024-07-28 13:43:28 +08:00
nap0o 5bb7efdeb9 🎉 通过自定义代码实现server-status主题深色模式半透明样式的前置准备 (#395) 2024-07-24 23:15:17 +08:00
github-actions[bot] 519882c4a9 update contributors[no ci] 2024-07-23 10:27:07 +00:00
MadDogOwner ed96432b79 installer: improve region detection reliability using multiple APIs (#394) 2024-07-23 18:26:45 +08:00
github-actions[bot] 1c057d93bd update contributors[no ci] 2024-07-19 22:49:08 +00:00
UUBulb 6005004681 installer: use multiple geoip api to determine location (#393) 2024-07-20 06:48:53 +08:00
Spetrum d8f4236404 Switch IP checking service to ip-api.com (#391) 2024-07-17 21:21:04 +08:00
UUBulb f95191c8af fix: concurrent write to single WebSocket connection (#392) 2024-07-17 10:06:23 +08:00
naiba 34fedd91d2 fix: dashboard standalon-alone install 2024-07-15 16:01:24 +08:00
naiba f675b884f5 feat: fetch version from api for dashboard standalone install 2024-07-14 22:52:53 +08:00
naiba 0b13fec029 fix: workflow branch name 2024-07-14 19:52:04 +08:00
naiba 67b788a969 内置HTTP内网穿透 2024-07-14 19:41:50 +08:00
naiba b63f693661 fix: branch name 2024-07-14 14:46:08 +08:00
github-actions[bot] ac64aa7355 update contributors[no ci] 2024-07-14 06:38:29 +00:00
UUBulb 05d69d5c07 ci: sync release to gitee (#390) 2024-07-14 14:38:12 +08:00
naiba ce624a0ca6 fix: loop delay 2024-07-14 13:04:35 +08:00
naiba 1c91fcffac feat: 去除 webTerminal 的 websocket 依赖 2024-07-14 12:47:36 +08:00
UUBulb 417f972659 installer: restore the China mirror of agent (#389) 2024-07-14 07:34:45 +08:00
naiba ada344cd6b chore: go 1.21.x 2024-07-13 13:11:40 +08:00
naiba 9d6682fd58 fix: build 2024-07-13 13:00:48 +08:00
github-actions[bot] 22c687c044 update contributors[no ci] 2024-07-13 04:52:14 +00:00
Tao Chen e1513203b4 添加OIDC支持 (#387)
* add general OIDC

* use "github.com/coreos/go-oidc/v3/oidc" to simplify oidc config

* fix: check if https by X-Forwarded-Proto

* recovery config.yaml
2024-07-13 12:51:59 +08:00
nap0o 17373e2dee 🐛 fixbug (#388)
* 修复bug:在server-status主题无法切换

* 替换哪吒面板后台cdn提供商为unpkg

* 替换哪吒面板后台cdn提供商为unpkg

* 加快Agent详情下拉展示速度
2024-07-12 22:29:11 +08:00
github-actions[bot] e7c6984c72 update contributors[no ci] 2024-07-10 15:14:11 +00:00
nap0o f4c6f4c57d ServerStatus主题优化 (#386)
* ServerStatus主题优化
1.更新世界地图基础geo文件,中国部分包含南海10段线,藏南部分划线更准
geo在大佬仓库https://github.com/Surbowl/world-geo-json-zh的基础上加工
2.世界地图可以在后台设置自定义代码切换是否包含南极洲,中国港澳台是否单独统计vps数
```<script>
  localStorage.setItem('countryMapGeoFile', 'nezha.world.geo.json'); //默认
  // localStorage.setItem('countryMapGeoFile', 'nezha.world.plus.geo.json'); //单独绘制香港,澳门,台湾
  // localStorage.setItem('countryMapGeoFile', 'nezha.world.antarctica.geo.json'); //有南极洲
  // localStorage.setItem('countryMapGeoFile', 'nezha.world.plus.antarctica.geo.json'); //有南极洲,单独绘制香港,澳门,台湾
</script>
```
3. mixin.js文件删除冗余代码
4. 首页agent下拉详情,控制网络折线图tooltip数字最多显示小数点后两位

* 修正geo文件路径

* 修复世界地图中国港澳台单独绘制时,vps数量计算逻辑

* 删除header中的无用的地图基础数据引用

* 增加温度控制cpu型号识别
2024-07-10 23:13:53 +08:00
UUBulb 4df60c6955 ddns: improve performance (#385) 2024-07-07 10:19:58 +08:00
UUBulb 0501e120f6 fix: timestamp conversion (#383) 2024-07-04 14:32:19 +08:00
UUBulb cfa425bb33 feat: notifications support for GPU & Temperature (#381) 2024-07-03 12:34:45 +08:00
nap0o 0613b0022d 优化server-status主题服务页 (#382) 2024-07-02 22:11:20 +08:00
github-actions[bot] 740d15bf79 update contributors[no ci] 2024-06-28 02:32:48 +00:00
Kris 7ace5d04fa Update README.md (#380) 2024-06-28 10:32:31 +08:00
github-actions[bot] a38d7bb51d update contributors[no ci] 2024-06-25 15:00:50 +00:00
UUBulb 61e16f96c5 fix: EUID is a readonly variable on some shells (#379) 2024-06-25 23:00:30 +08:00
nap0o 61127b3435 优化server-status主题首页网络图表 (#378) 2024-06-25 22:16:32 +08:00
UUBulb 8805dffe85 refactor: make the installer script POSIX compliant (#376) 2024-06-25 22:15:49 +08:00
dependabot[bot] aeb7c52565 Bump github.com/hashicorp/go-retryablehttp from 0.7.2 to 0.7.7 (#377) 2024-06-25 22:11:17 +08:00
nap0o 877b4cd666 优化gpu前端展示代码 (#375) 2024-06-23 22:20:04 +08:00
naiba f75847d3d7 refactor: imporve code style 2024-06-23 16:15:08 +08:00
github-actions[bot] eee3225f3a update contributors[no ci] 2024-06-23 07:59:19 +00:00
UUBulb aa9eb533ad Temporary fix for DDNSDomain leaks (#374) 2024-06-23 15:59:03 +08:00
UUBulb cf9bbb5ff4 feat: Add GPU inspection support (#373) 2024-06-23 10:27:33 +08:00
github-actions[bot] 65dee23137 update contributors[no ci] 2024-06-22 14:52:43 +00:00
UUBulb feb4659cab Hide DDNS domain info for guests (#371) 2024-06-22 22:52:29 +08:00
github-actions[bot] bc7791efd2 update contributors[no ci] 2024-06-22 14:51:57 +00:00
nap0o 6e6ec3256f default和serverstatus主题温度展示相关 (#372) 2024-06-22 22:51:42 +08:00
github-actions[bot] ce66f72cf9 update contributors[no ci] 2024-06-16 10:13:06 +00:00
UUBulb 0ed7436176 Add option to reduct temperature information (#369) 2024-06-16 18:12:45 +08:00
UUBulb 2aabe1bd61 chore: fix some typos (#370) 2024-06-16 18:04:27 +08:00
github-actions[bot] db5e94d0de update contributors[no ci] 2024-06-15 01:43:49 +00:00
nap0o 3eecdc2b3f 修复一些bug (#367)
* bug修复:
1. serverstatus主题agent详情不显示内存信息
2. windows系统icon错误
小优化
1. echarts绘制世界地图所需的world.js坐标信息从本地加载切换为从cdn加载
2. font-logos切换为0.17.0版本,使得图标间距更合适

* 优化agent离线保留信息逻辑,从判断“实时在线”调整为判断“曾经在线”。

* 优化agent离线保留信息逻辑,从判断“实时在线”调整为判断“曾经在线”。
2024-06-15 09:43:30 +08:00
github-actions[bot] b2740e5f3c update contributors[no ci] 2024-06-14 10:03:18 +00:00
nap0o c3fa4cbbee 优化default和server status主题 (#363) 2024-06-14 18:02:55 +08:00
UUBulb a9da084711 ci: using time/tzdata package to provide zoneinfo (#365) 2024-06-14 10:24:58 +08:00
github-actions[bot] f30969e946 update contributors[no ci] 2024-06-01 11:12:04 +00:00
Leon e0e2b8c3c2 Add support for sensor temperature in host state (#359)
* feat: Add support for sensor temperature in host state

* chore: Update file permissions for saving config data
2024-06-01 19:11:48 +08:00
github-actions[bot] 3782c0dcaf update contributors[no ci] 2024-05-31 14:05:21 +00:00
Coming 0331db2c12 Update GPG key for libsepol (#360) 2024-05-31 22:05:05 +08:00
github-actions[bot] 1749247aeb update contributors[no ci] 2024-05-21 15:12:02 +00:00
UUBulb eaba7abd35 Use agent's builtin support for services instead (#357) 2024-05-21 23:11:43 +08:00
github-actions[bot] f5ca66d924 update contributors[no ci] 2024-05-19 14:35:11 +00:00
eya46 d5e7b22fc6 删除多余空格 (#358) 2024-05-19 22:34:57 +08:00
nap0o 5122d48dd8 Update service.html (#356) 2024-05-07 09:09:53 +08:00
github-actions[bot] 3614ef381e update contributors[no ci] 2024-05-05 12:13:15 +00:00
UUBulb 8475674eb7 chore: fix a typo (#355) 2024-05-05 20:12:54 +08:00
Akkia 25e7f8a912 支持cloudflare access OIDC认证 (#354) 2024-05-03 08:47:53 +08:00
UUBulb 5c7652f047 Add DDNS Profiles, use publicsuffixlist domain parser (#350)
* Add DDNS Profiles, use publicsuffixlist domain parser

* Add Tencent Cloud DNS Provider

* Restore validate DDNS provider function

* chore: fmt & upgrade dependencies
2024-04-27 13:36:36 +08:00
naiba 890616f52a daocloud github 镜像已经被打废了 2024-04-18 21:42:04 +08:00
github-actions[bot] c92ec996e0 update contributors[no ci] 2024-04-13 03:15:05 +00:00
Nanjing Hopefun Network Technology Co. Ltd 7fcd4398ea Update install.sh (#347)
修改自动安装超时时间-T 10为-T 60,10秒超时在wget那一步经常连接探针下载失败。
2024-04-13 11:14:46 +08:00
github-actions[bot] 7faba40a1c update contributors[no ci] 2024-04-09 12:43:54 +00:00
96 changed files with 6596 additions and 2099 deletions
+90
View File
@@ -0,0 +1,90 @@
import os
import time
import requests
import hashlib
from github import Github
def get_github_latest_release():
g = Github()
repo = g.get_repo("naiba/nezha")
release = repo.get_latest_release()
if release:
print(f"Latest release tag is: {release.tag_name}")
print(f"Latest release info is: {release.body}")
files = []
for asset in release.get_assets():
url = asset.browser_download_url
name = asset.name
response = requests.get(url)
if response.status_code == 200:
with open(name, 'wb') as f:
f.write(response.content)
print(f"Downloaded {name}")
else:
print(f"Failed to download {name}")
file_abs_path = get_abs_path(asset.name)
files.append(file_abs_path)
sync_to_gitee(release.tag_name, release.body, files)
else:
print("No releases found.")
def sync_to_gitee(tag: str, body: str, files: slice):
release_id = ""
owner = "naibahq"
repo = "nezha"
release_api_uri = f"https://gitee.com/api/v5/repos/{owner}/{repo}/releases"
api_client = requests.Session()
api_client.headers.update({
'Accept': 'application/json',
'Content-Type': 'application/json'
})
access_token = os.environ['GITEE_TOKEN']
release_data = {
'access_token': access_token,
'tag_name': tag,
'name': tag,
'body': body,
'prerelease': False,
'target_commitish': 'master'
}
release_api_response = api_client.post(release_api_uri, json=release_data)
if release_api_response.status_code == 201:
release_info = release_api_response.json()
release_id = release_info.get('id')
else:
print(
f"Request failed with status code {release_api_response.status_code}")
print(f"Gitee release id: {release_id}")
asset_api_uri = f"{release_api_uri}/{release_id}/attach_files"
for file_path in files:
files = {
'file': open(file_path, 'rb')
}
asset_api_response = requests.post(
asset_api_uri, params={'access_token': access_token}, files=files)
if asset_api_response.status_code == 201:
asset_info = asset_api_response.json()
asset_name = asset_info.get('name')
print(f"Successfully uploaded {asset_name}!")
else:
print(
f"Request failed with status code {asset_api_response.status_code}")
api_client.close()
print("Sync is completed!")
def get_abs_path(path: str):
wd = os.getcwd()
return os.path.join(wd, path)
get_github_latest_release()
+23 -1
View File
@@ -12,6 +12,13 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Fetch IPInfo GeoIP Database
env:
IPINFO_TOKEN: ${{ secrets.IPINFO_TOKEN }}
run: |
rm pkg/geoip/geoip.db
wget -O pkg/geoip/geoip.db https://ipinfo.io/data/free/country.mmdb?token=${IPINFO_TOKEN}
- name: Extract branch name
run: |
export TAG_NAME=$(echo ${GITHUB_REF#refs/tags/})
@@ -22,7 +29,7 @@ jobs:
uses: crazy-max/ghaction-xgo@v2
with:
xgo_version: latest
go_version: 1.20.x
go_version: 1.21.x
dest: dist
pkg: cmd/dashboard
prefix: dashboard
@@ -30,11 +37,13 @@ jobs:
v: true
x: false
race: false
tags: timetzdata
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: |
ls -al dist/
mv dist/dashboard-linux-arm-7 dist/dashboard-linux-arm
- name: Log in to the GHCR
@@ -106,3 +115,16 @@ jobs:
curl -s https://purge.jsdelivr.net/gh/$LOWER_USERNAME/nezha@master/script/nezha-agent.service
curl -s https://purge.jsdelivr.net/gh/$LOWER_USERNAME/nezha@master/script/docker-compose.yaml
curl -s https://purge.jsdelivr.net/gh/$LOWER_USERNAME/nezha@master/script/config.yaml
- name: Trigger sync
if: ${{ env.SYNCED == 0 }}
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
await github.rest.actions.createWorkflowDispatch({
owner: 'naiba',
repo: 'nezha',
workflow_id: 'sync-release.yml',
ref: 'master'
})
+16
View File
@@ -0,0 +1,16 @@
name: Sync Release to Gitee
on:
workflow_dispatch:
jobs:
sync-release-to-gitee:
runs-on: ubuntu-latest
env:
GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }}
steps:
- uses: actions/checkout@v4
- name: Sync to Gitee
run: |
pip3 install PyGitHub
python3 .github/sync.py
+6 -1
View File
@@ -25,7 +25,7 @@ jobs:
uses: crazy-max/ghaction-xgo@v2
with:
xgo_version: latest
go_version: 1.20.x
go_version: 1.21.x
dest: dist
pkg: cmd/dashboard
prefix: dashboard
@@ -33,6 +33,11 @@ jobs:
v: true
x: false
race: false
tags: timetzdata
ldflags: -s -w --extldflags '-static -fpic' -X github.com/naiba/nezha/service/singleton.Version=test
buildmode: default
- name: fix dist
run: |
ls -al dist/
mv dist/dashboard-linux-arm-7 dist/dashboard-linux-arm
+6 -1
View File
@@ -32,7 +32,7 @@ jobs:
uses: crazy-max/ghaction-xgo@v2
with:
xgo_version: latest
go_version: 1.20.x
go_version: 1.21.x
dest: dist
pkg: cmd/dashboard
prefix: dashboard
@@ -40,5 +40,10 @@ jobs:
v: true
x: false
race: false
tags: timetzdata
ldflags: -s -w --extldflags '-static -fpic' -X github.com/naiba/nezha/service/singleton.Version=test
buildmode: default
- name: fix dist
run: |
ls -al dist/
mv dist/dashboard-linux-arm-7 dist/dashboard-linux-arm
+38 -26
View File
@@ -4,13 +4,15 @@
<br>
<small><i>LOGO designed by <a href="https://xio.ng" target="_blank">熊大</a> .</i></small>
<br><br>
<img alt="GitHub release (with filter)" src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&style=for-the-badge&logo=github&label=Dashboard">&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">
<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.18.2-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>
<p>Supports <b>monitoring</b> system status, HTTP (SSL certificate change, upcoming expiration, expired), TCP, Ping and supports <b>push alerts</b>, run scheduled tasks and <b>web terminal</b>.</p>
</div>
\>> Telegram Channel: [哪吒监控(中文通知频道)](https://t.me/nezhanews)
\>> Telegram Group: [Nezha Monitoring Global (English Only)](https://t.me/nezhamonitoring_global), [哪吒监控(中文群组)](https://t.me/nezhamonitoring)
\>> [Use Cases | 我们的用户](https://www.google.com/search?q=%22powered+by+Nezha+Monitoring%22+OR+%22powered+by+%E5%93%AA%E5%90%92%E7%9B%91%E6%8E%A7%22) (Google)
@@ -41,47 +43,57 @@ You can change the dashboard language in the settings page (`/setting`) after th
<!--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/uubulb" title="UUBulb"><img src="https://avatars.githubusercontent.com/u/35923940?v=4" width="50;" alt="UUBulb"/></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/nap0o" title="nap0o"><img src="https://avatars.githubusercontent.com/u/144927971?v=4" width="50;" alt="nap0o"/></a>
<a href="https://github.com/xykt" title="xykt"><img src="https://avatars.githubusercontent.com/u/152045469?v=4" width="50;" alt="xykt"/></a>
<a href="https://github.com/MikoyChinese" title="MikoyChinese"><img src="https://avatars.githubusercontent.com/u/22676744?v=4" width="50;" alt="MikoyChinese"/></a>
<a href="https://github.com/JackieSung4ev" title="JackieSung4ev"><img src="https://avatars.githubusercontent.com/u/24974735?v=4" width="50;" alt="JackieSung4ev"/></a>
<a href="https://github.com/lemoeo" title="Lemoe"><img src="https://avatars.githubusercontent.com/u/18618627?v=4" width="50;" alt="Lemoe"/></a>
<a href="https://github.com/nap0o" title="nap0o"><img src="https://avatars.githubusercontent.com/u/144927971?v=4" width="50;" alt="nap0o"/></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/uubulb" title="UUBulb"><img src="https://avatars.githubusercontent.com/u/35923940?v=4" width="50;" alt="UUBulb"/></a>
<a href="https://github.com/spiritLHLS" title="spiritlhl"><img src="https://avatars.githubusercontent.com/u/103393591?v=4" width="50;" alt="spiritlhl"/></a>
<a href="https://github.com/liuyanxi975" title="刘颜溪"><img src="https://avatars.githubusercontent.com/u/24417037?v=4" width="50;" alt="刘颜溪"/></a>
<a href="https://github.com/lemoeo" title="Lemoe"><img src="https://avatars.githubusercontent.com/u/18618627?v=4" width="50;" alt="Lemoe"/></a>
<a href="https://github.com/CosmosZ-code" title="CosmosZ-code"><img src="https://avatars.githubusercontent.com/u/81398224?v=4" width="50;" alt="CosmosZ-code"/></a>
<a href="https://github.com/liuyanxi975" title="刘颜溪"><img src="https://avatars.githubusercontent.com/u/24417037?v=4" width="50;" alt="刘颜溪"/></a>
<a href="https://github.com/spiritLHLS" title="spiritlhl"><img src="https://avatars.githubusercontent.com/u/103393591?v=4" width="50;" alt="spiritlhl"/></a>
<a href="https://github.com/1ridic" title="1ridic"><img src="https://avatars.githubusercontent.com/u/88495501?v=4" width="50;" alt="1ridic"/></a>
<a href="https://github.com/lvgj-stack" title="Ko no dio"><img src="https://avatars.githubusercontent.com/u/38449861?v=4" width="50;" alt="Ko no dio"/></a>
<a href="https://github.com/hhhkkk520" title="Kris"><img src="https://avatars.githubusercontent.com/u/52115472?v=4" width="50;" alt="Kris"/></a>
<a href="https://github.com/1ridic" title="1ridic"><img src="https://avatars.githubusercontent.com/u/88495501?v=4" width="50;" alt="1ridic"/></a>
<a href="https://github.com/Mmx233" title="Mmx"><img src="https://avatars.githubusercontent.com/u/36563672?v=4" width="50;" alt="Mmx"/></a>
<a href="https://github.com/rootmelo92118" title="rootmelo92118"><img src="https://avatars.githubusercontent.com/u/32770959?v=4" width="50;" alt="rootmelo92118"/></a>
<a href="https://github.com/iilemon" title="Sean"><img src="https://avatars.githubusercontent.com/u/33201711?v=4" width="50;" alt="Sean"/></a>
<a href="https://github.com/fscarmen" title="fscarmen"><img src="https://avatars.githubusercontent.com/u/62703343?v=4" width="50;" alt="fscarmen"/></a>
<a href="https://github.com/ch8o" title="no-name-now"><img src="https://avatars.githubusercontent.com/u/9103372?v=4" width="50;" alt="no-name-now"/></a>
<a href="https://github.com/HsukqiLee" title="HsukqiLee"><img src="https://avatars.githubusercontent.com/u/79034142?v=4" width="50;" alt="HsukqiLee"/></a>
<a href="https://github.com/DarcJC" title="Darc Z."><img src="https://avatars.githubusercontent.com/u/53445798?v=4" width="50;" alt="Darc Z."/></a>
<a href="https://github.com/Creling" title="Creling"><img src="https://avatars.githubusercontent.com/u/43109504?v=4" width="50;" alt="Creling"/></a>
<a href="https://github.com/Mmx233" title="Mmx"><img src="https://avatars.githubusercontent.com/u/36563672?v=4" width="50;" alt="Mmx"/></a>
<a href="https://github.com/coreff" title="Core F"><img src="https://avatars.githubusercontent.com/u/38347122?v=4" width="50;" alt="Core F"/></a>
<a href="https://github.com/colour93" title="玖叁"><img src="https://avatars.githubusercontent.com/u/64313711?v=4" width="50;" alt="玖叁"/></a>
<a href="https://github.com/arkylin" title=""><img src="https://avatars.githubusercontent.com/u/35104502?v=4" width="50;" alt=""/></a>
<a href="https://github.com/zhucaidan" title="zhucaidan"><img src="https://avatars.githubusercontent.com/u/47970938?v=4" width="50;" alt="zhucaidan"/></a>
<a href="https://github.com/ysicing" title="缘生"><img src="https://avatars.githubusercontent.com/u/8605565?v=4" width="50;" alt="缘生"/></a>
<a href="https://github.com/unclezs" title="unclezs"><img src="https://avatars.githubusercontent.com/u/42318775?v=4" width="50;" alt="unclezs"/></a>
<a href="https://github.com/lvyaoting" title="lvyaoting"><img src="https://avatars.githubusercontent.com/u/166296299?v=4" width="50;" alt="lvyaoting"/></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/Creling" title="Creling"><img src="https://avatars.githubusercontent.com/u/43109504?v=4" width="50;" alt="Creling"/></a>
<a href="https://github.com/DarcJC" title="Darc Z."><img src="https://avatars.githubusercontent.com/u/53445798?v=4" width="50;" alt="Darc Z."/></a>
<a href="https://github.com/HsukqiLee" title="HsukqiLee"><img src="https://avatars.githubusercontent.com/u/79034142?v=4" width="50;" alt="HsukqiLee"/></a>
<a href="https://github.com/ch8o" title="no-name-now"><img src="https://avatars.githubusercontent.com/u/9103372?v=4" width="50;" alt="no-name-now"/></a>
<a href="https://github.com/fscarmen" title="fscarmen"><img src="https://avatars.githubusercontent.com/u/62703343?v=4" width="50;" alt="fscarmen"/></a>
<a href="https://github.com/iilemon" title="Sean"><img src="https://avatars.githubusercontent.com/u/33201711?v=4" width="50;" alt="Sean"/></a>
<a href="https://github.com/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/eya46" title="eya46"><img src="https://avatars.githubusercontent.com/u/61458340?v=4" width="50;" alt="eya46"/></a>
<a href="https://github.com/guoyongchang" title="guoyongchang"><img src="https://avatars.githubusercontent.com/u/10484506?v=4" width="50;" alt="guoyongchang"/></a>
<a href="https://github.com/yuanweize" title="I"><img src="https://avatars.githubusercontent.com/u/30067203?v=4" width="50;" alt="I"/></a>
<a href="https://github.com/lvyaoting" title="lvyaoting"><img src="https://avatars.githubusercontent.com/u/166296299?v=4" width="50;" alt="lvyaoting"/></a>
<a href="https://github.com/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/zhucaidan" title="zhucaidan"><img src="https://avatars.githubusercontent.com/u/47970938?v=4" width="50;" alt="zhucaidan"/></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/colour93" title="玖叁"><img src="https://avatars.githubusercontent.com/u/64313711?v=4" width="50;" alt="玖叁"/></a>
<a href="https://github.com/IamTaoChen" title="Tao Chen"><img src="https://avatars.githubusercontent.com/u/42793494?v=4" width="50;" alt="Tao Chen"/></a>
<a href="https://github.com/Septrum101" title="Spetrum"><img src="https://avatars.githubusercontent.com/u/11692994?v=4" width="50;" alt="Spetrum"/></a>
<a href="https://github.com/dreamingsleeping" title="Nanjing Hopefun Network Technology Co. Ltd."><img src="https://avatars.githubusercontent.com/u/13828658?v=4" width="50;" alt="Nanjing Hopefun Network Technology Co. Ltd."/></a>
<a href="https://github.com/MartijnLindeman" title="Martijn Lindeman"><img src="https://avatars.githubusercontent.com/u/78365708?v=4" width="50;" alt="Martijn Lindeman"/></a>
<a href="https://github.com/xrgzs" title="MadDogOwner"><img src="https://avatars.githubusercontent.com/u/26499123?v=4" width="50;" alt="MadDogOwner"/></a>
<a href="https://github.com/funnyzak" title="Leon"><img src="https://avatars.githubusercontent.com/u/2562087?v=4" width="50;" alt="Leon"/></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-->
<a href="https://github.com/wwng2333" title="Crazy"><img src="https://avatars.githubusercontent.com/u/17147265?v=4" width="50;" alt="Crazy"/></a>
<a href="https://github.com/wellcoming" title="Coming"><img src="https://avatars.githubusercontent.com/u/74850890?v=4" width="50;" alt="Coming"/></a><!--GAMFC_DELIMITER_END-->
## Special Thanks
- [IPInfo](https://ipinfo.io/) for providing an accurate GeoIP Database.
## Star History
+44 -218
View File
@@ -2,12 +2,9 @@ package controller
import (
"errors"
"log"
"fmt"
"net/http"
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/gin-gonic/gin"
@@ -23,22 +20,13 @@ import (
"github.com/naiba/nezha/pkg/utils"
"github.com/naiba/nezha/pkg/websocketx"
"github.com/naiba/nezha/proto"
"github.com/naiba/nezha/service/rpc"
"github.com/naiba/nezha/service/singleton"
)
type terminalContext struct {
agentConn *websocketx.Conn
userConn *websocketx.Conn
serverID uint64
host string
useSSL bool
}
type commonPage struct {
r *gin.Engine
terminals map[string]*terminalContext
terminalsLock *sync.Mutex
requestGroup singleflight.Group
r *gin.Engine
requestGroup singleflight.Group
}
func (cp *commonPage) serve() {
@@ -67,7 +55,6 @@ 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"}))
@@ -223,19 +210,25 @@ func (cp *commonPage) network(c *gin.Context) {
}
func (cp *commonPage) getServerStat(c *gin.Context) ([]byte, error) {
v, err, _ := cp.requestGroup.Do("serverStats", func() (any, error) {
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
_, isViewPasswordVerfied := c.Get(model.CtxKeyViewPasswordVerified)
authorized := isMember || isViewPasswordVerfied
v, err, _ := cp.requestGroup.Do(fmt.Sprintf("serverStats::%t", authorized), func() (interface{}, error) {
singleton.SortedServerLock.RLock()
defer singleton.SortedServerLock.RUnlock()
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
_, isViewPasswordVerfied := c.Get(model.CtxKeyViewPasswordVerified)
var servers []*model.Server
if isMember || isViewPasswordVerfied {
if authorized {
servers = singleton.SortedServerList
} else {
servers = singleton.SortedServerListForGuest
filteredServers := make([]*model.Server, len(singleton.SortedServerListForGuest))
for i, server := range singleton.SortedServerListForGuest {
filteredServer := *server
filteredServer.DDNSDomain = "redacted"
filteredServers[i] = &filteredServer
}
servers = filteredServers
}
return utils.Json.Marshal(Data{
@@ -267,8 +260,8 @@ func (cp *commonPage) home(c *gin.Context) {
}
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
ReadBufferSize: 10240,
WriteBufferSize: 10240,
}
type Data struct {
@@ -276,8 +269,6 @@ type Data struct {
Servers []*model.Server `json:"servers,omitempty"`
}
var cloudflareCookiesValidator = regexp.MustCompile("^[A-Za-z0-9-_]+$")
func (cp *commonPage) ws(c *gin.Context) {
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
@@ -314,10 +305,8 @@ func (cp *commonPage) ws(c *gin.Context) {
}
func (cp *commonPage) terminal(c *gin.Context) {
terminalID := c.Param("id")
cp.terminalsLock.Lock()
if terminalID == "" || cp.terminals[terminalID] == nil {
cp.terminalsLock.Unlock()
streamId := c.Param("id")
if _, err := rpc.NezhaHandlerSingleton.GetStream(streamId); err != nil {
mygin.ShowErrorPage(c, mygin.ErrInfo{
Code: http.StatusForbidden,
Title: "无权访问",
@@ -327,104 +316,7 @@ func (cp *commonPage) terminal(c *gin.Context) {
}, true)
return
}
terminal := cp.terminals[terminalID]
cp.terminalsLock.Unlock()
defer func() {
// 清理 context
cp.terminalsLock.Lock()
defer cp.terminalsLock.Unlock()
delete(cp.terminals, terminalID)
}()
var isAgent bool
if _, authorized := c.Get(model.CtxKeyAuthorizedUser); !authorized {
singleton.ServerLock.RLock()
_, hasID := singleton.SecretToID[c.Request.Header.Get("Secret")]
singleton.ServerLock.RUnlock()
if !hasID {
mygin.ShowErrorPage(c, mygin.ErrInfo{
Code: http.StatusForbidden,
Title: "无权访问",
Msg: "用户未登录或非法终端",
Link: "/",
Btn: "返回首页",
}, true)
return
}
if terminal.userConn == nil {
mygin.ShowErrorPage(c, mygin.ErrInfo{
Code: http.StatusForbidden,
Title: "无权访问",
Msg: "用户不在线",
Link: "/",
Btn: "返回首页",
}, true)
return
}
if terminal.agentConn != nil {
mygin.ShowErrorPage(c, mygin.ErrInfo{
Code: http.StatusInternalServerError,
Title: "连接已存在",
Msg: "Websocket协议切换失败",
Link: "/",
Btn: "返回首页",
}, true)
return
}
isAgent = true
} else {
singleton.ServerLock.RLock()
server := singleton.ServerList[terminal.serverID]
singleton.ServerLock.RUnlock()
if server == nil || server.TaskStream == nil {
mygin.ShowErrorPage(c, mygin.ErrInfo{
Code: http.StatusForbidden,
Title: "请求失败",
Msg: "服务器不存在或处于离线状态",
Link: "/server",
Btn: "返回重试",
}, true)
return
}
cloudflareCookies, _ := c.Cookie("CF_Authorization")
// Cloudflare Cookies 合法性验证
// 其应该包含.分隔的三组BASE64-URL编码
if cloudflareCookies != "" {
encodedCookies := strings.Split(cloudflareCookies, ".")
if len(encodedCookies) == 3 {
for i := 0; i < 3; i++ {
if !cloudflareCookiesValidator.MatchString(encodedCookies[i]) {
cloudflareCookies = ""
break
}
}
} else {
cloudflareCookies = ""
}
}
terminalData, _ := utils.Json.Marshal(&model.TerminalTask{
Host: terminal.host,
UseSSL: terminal.useSSL,
Session: terminalID,
Cookie: cloudflareCookies,
})
if err := server.TaskStream.Send(&proto.Task{
Type: model.TaskTypeTerminal,
Data: string(terminalData),
}); err != nil {
mygin.ShowErrorPage(c, mygin.ErrInfo{
Code: http.StatusForbidden,
Title: "请求失败",
Msg: "Agent信令下发失败",
Link: "/server",
Btn: "返回重试",
}, true)
return
}
}
defer rpc.NezhaHandlerSingleton.CloseStream(streamId)
wsConn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
@@ -440,36 +332,7 @@ func (cp *commonPage) terminal(c *gin.Context) {
return
}
defer wsConn.Close()
conn := &websocketx.Conn{Conn: wsConn}
log.Printf("NEZHA>> terminal connected %t %q", isAgent, c.Request.URL)
defer log.Printf("NEZHA>> terminal disconnected %t %q", isAgent, c.Request.URL)
if isAgent {
terminal.agentConn = conn
defer func() {
// Agent断开链接时断开用户连接
if terminal.userConn != nil {
terminal.userConn.Close()
}
}()
} else {
terminal.userConn = conn
defer func() {
// 用户断开链接时断开 Agent 连接
if terminal.agentConn != nil {
terminal.agentConn.Close()
}
}()
}
deadlineCh := make(chan interface{})
go func() {
// 对方连接超时
connectDeadline := time.NewTimer(time.Second * 15)
<-connectDeadline.C
deadlineCh <- struct{}{}
}()
conn := websocketx.NewConn(wsConn)
go func() {
// PING 保活
@@ -481,58 +344,11 @@ func (cp *commonPage) terminal(c *gin.Context) {
}
}()
dataCh := make(chan []byte)
errorCh := make(chan error)
go func() {
for {
msgType, data, err := conn.ReadMessage()
if err != nil {
errorCh <- err
return
}
// 将文本消息转换为命令输入
if msgType == websocket.TextMessage {
data = append([]byte{0}, data...)
}
dataCh <- data
}
}()
var dataBuffer [][]byte
var distConn *websocketx.Conn
checkDistConn := func() {
if distConn == nil {
if isAgent {
distConn = terminal.userConn
} else {
distConn = terminal.agentConn
}
}
if err = rpc.NezhaHandlerSingleton.UserConnected(streamId, conn); err != nil {
return
}
for {
select {
case <-deadlineCh:
checkDistConn()
if distConn == nil {
return
}
case <-errorCh:
return
case data := <-dataCh:
dataBuffer = append(dataBuffer, data)
checkDistConn()
if distConn != nil {
for i := 0; i < len(dataBuffer); i++ {
err = distConn.WriteMessage(websocket.BinaryMessage, dataBuffer[i])
if err != nil {
return
}
}
dataBuffer = dataBuffer[:0]
}
}
}
rpc.NezhaHandlerSingleton.StartStream(streamId, time.Second*10)
}
type createTerminalRequest struct {
@@ -564,7 +380,7 @@ func (cp *commonPage) createTerminal(c *gin.Context) {
return
}
id, err := uuid.GenerateUUID()
streamId, err := uuid.GenerateUUID()
if err != nil {
mygin.ShowErrorPage(c, mygin.ErrInfo{
Code: http.StatusInternalServerError,
@@ -578,6 +394,8 @@ func (cp *commonPage) createTerminal(c *gin.Context) {
return
}
rpc.NezhaHandlerSingleton.CreateStream(streamId)
singleton.ServerLock.RLock()
server := singleton.ServerList[createTerminalReq.ID]
singleton.ServerLock.RUnlock()
@@ -592,17 +410,25 @@ func (cp *commonPage) createTerminal(c *gin.Context) {
return
}
cp.terminalsLock.Lock()
defer cp.terminalsLock.Unlock()
cp.terminals[id] = &terminalContext{
serverID: createTerminalReq.ID,
host: createTerminalReq.Host,
useSSL: createTerminalReq.Protocol == "https:",
terminalData, _ := utils.Json.Marshal(&model.TerminalTask{
StreamID: streamId,
})
if err := server.TaskStream.Send(&proto.Task{
Type: model.TaskTypeTerminalGRPC,
Data: string(terminalData),
}); err != nil {
mygin.ShowErrorPage(c, mygin.ErrInfo{
Code: http.StatusForbidden,
Title: "请求失败",
Msg: "Agent信令下发失败",
Link: "/server",
Btn: "返回重试",
}, true)
return
}
c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/terminal", mygin.CommonEnvironment(c, gin.H{
"SessionID": id,
"SessionID": streamId,
"ServerName": server.Name,
}))
}
+73 -7
View File
@@ -1,6 +1,7 @@
package controller
import (
"encoding/json"
"fmt"
"html/template"
"io/fs"
@@ -9,22 +10,31 @@ import (
"os"
"strconv"
"strings"
"sync"
"time"
"code.cloudfoundry.org/bytefmt"
"github.com/gin-contrib/pprof"
"github.com/gin-gonic/gin"
"github.com/hashicorp/go-uuid"
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/naiba/nezha/model"
"github.com/naiba/nezha/pkg/mygin"
"github.com/naiba/nezha/pkg/utils"
"github.com/naiba/nezha/proto"
"github.com/naiba/nezha/resource"
"github.com/naiba/nezha/service/rpc"
"github.com/naiba/nezha/service/singleton"
)
func ServeWeb(port uint) *http.Server {
gin.SetMode(gin.ReleaseMode)
r := gin.Default()
if singleton.Conf.Debug {
gin.SetMode(gin.DebugMode)
pprof.Register(r)
}
r.Use(natGateway)
tmpl := template.New("").Funcs(funcMap)
var err error
tmpl, err = tmpl.ParseFS(resource.TemplateFS, "template/**/*.html")
@@ -33,10 +43,6 @@ func ServeWeb(port uint) *http.Server {
}
tmpl = loadThirdPartyTemplates(tmpl)
r.SetHTMLTemplate(tmpl)
if singleton.Conf.Debug {
gin.SetMode(gin.DebugMode)
pprof.Register(r)
}
r.Use(mygin.RecordPath)
staticFs, err := fs.Sub(resource.StaticFS, "static")
if err != nil {
@@ -45,7 +51,6 @@ func ServeWeb(port uint) *http.Server {
r.StaticFS("/static", http.FS(staticFs))
r.Static("/static-custom", "resource/static/custom")
routers(r)
page404 := func(c *gin.Context) {
mygin.ShowErrorPage(c, mygin.ErrInfo{
Code: http.StatusNotFound,
@@ -68,7 +73,7 @@ func ServeWeb(port uint) *http.Server {
func routers(r *gin.Engine) {
// 通用页面
cp := commonPage{r: r, terminals: make(map[string]*terminalContext), terminalsLock: new(sync.Mutex)}
cp := commonPage{r: r}
cp.serve()
// 游客页面
gp := guestPage{r}
@@ -239,3 +244,64 @@ var funcMap = template.FuncMap{
return singleton.StatusCodeToString(singleton.GetStatusCode(val))
},
}
func natGateway(c *gin.Context) {
natConfig := singleton.GetNATConfigByDomain(c.Request.Host)
if natConfig == nil {
return
}
singleton.ServerLock.RLock()
server := singleton.ServerList[natConfig.ServerID]
singleton.ServerLock.RUnlock()
if server == nil || server.TaskStream == nil {
c.Writer.WriteString("server not found or not connected")
c.Abort()
return
}
streamId, err := uuid.GenerateUUID()
if err != nil {
c.Writer.WriteString(fmt.Sprintf("stream id error: %v", err))
c.Abort()
return
}
rpc.NezhaHandlerSingleton.CreateStream(streamId)
defer rpc.NezhaHandlerSingleton.CloseStream(streamId)
taskData, err := json.Marshal(model.TaskNAT{
StreamID: streamId,
Host: natConfig.Host,
})
if err != nil {
c.Writer.WriteString(fmt.Sprintf("task data error: %v", err))
c.Abort()
return
}
if err := server.TaskStream.Send(&proto.Task{
Type: model.TaskTypeNAT,
Data: string(taskData),
}); err != nil {
c.Writer.WriteString(fmt.Sprintf("send task error: %v", err))
c.Abort()
return
}
w, err := utils.NewRequestWrapper(c.Request, c.Writer)
if err != nil {
c.Writer.WriteString(fmt.Sprintf("request wrapper error: %v", err))
c.Abort()
return
}
if err := rpc.NezhaHandlerSingleton.UserConnected(streamId, w); err != nil {
c.Writer.WriteString(fmt.Sprintf("user connected error: %v", err))
c.Abort()
return
}
rpc.NezhaHandlerSingleton.StartStream(streamId, time.Second*10)
c.Abort()
}
+10
View File
@@ -35,6 +35,10 @@ func (gp *guestPage) serve() {
}
func (gp *guestPage) login(c *gin.Context) {
if singleton.Conf.Oauth2.OidcAutoLogin {
c.Redirect(http.StatusFound, "/oauth2/login")
return
}
LoginType := "GitHub"
RegistrationLink := "https://github.com/join"
if singleton.Conf.Oauth2.Type == model.ConfigTypeGitee {
@@ -49,6 +53,12 @@ func (gp *guestPage) login(c *gin.Context) {
} else if singleton.Conf.Oauth2.Type == model.ConfigTypeGitea {
LoginType = "Gitea"
RegistrationLink = fmt.Sprintf("%s/user/sign_up", singleton.Conf.Oauth2.Endpoint)
} else if singleton.Conf.Oauth2.Type == model.ConfigTypeCloudflare {
LoginType = "Cloudflare"
RegistrationLink = "https://dash.cloudflare.com/sign-up/teams"
} else if singleton.Conf.Oauth2.Type == model.ConfigTypeOidc {
LoginType = singleton.Conf.Oauth2.OidcDisplayName
RegistrationLink = singleton.Conf.Oauth2.OidcRegisterURL
}
c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/login", mygin.CommonEnvironment(c, gin.H{
"Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "Login"}),
+68 -18
View File
@@ -45,6 +45,7 @@ func (ma *memberAPI) serve() {
mr.POST("/batch-update-server-group", ma.batchUpdateServerGroup)
mr.POST("/batch-delete-server", ma.batchDeleteServer)
mr.POST("/notification", ma.addOrEditNotification)
mr.POST("/nat", ma.addOrEditNAT)
mr.POST("/alert-rule", ma.addOrEditAlertRule)
mr.POST("/setting", ma.updateSetting)
mr.DELETE("/:model/:id", ma.delete)
@@ -209,6 +210,11 @@ func (ma *memberAPI) delete(c *gin.Context) {
if err == nil {
singleton.OnDeleteNotification(id)
}
case "nat":
err = singleton.DB.Unscoped().Delete(&model.NAT{}, "id = ?", id).Error
if err == nil {
singleton.OnNATUpdate()
}
case "monitor":
err = singleton.DB.Unscoped().Delete(&model.Monitor{}, "id = ?", id).Error
if err == nil {
@@ -304,6 +310,7 @@ type serverForm struct {
EnableIPv4 string
EnableIpv6 string
DDNSDomain string
DDNSProfile string
}
func (ma *memberAPI) addOrEditServer(c *gin.Context) {
@@ -323,6 +330,7 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) {
s.EnableIPv4 = sf.EnableIPv4 == "on"
s.EnableIpv6 = sf.EnableIpv6 == "on"
s.DDNSDomain = sf.DDNSDomain
s.DDNSProfile = sf.DDNSProfile
if s.ID == 0 {
s.Secret, err = utils.GenerateRandomString(18)
if err == nil {
@@ -435,25 +443,23 @@ func (ma *memberAPI) addOrEditMonitor(c *gin.Context) {
if m.NotificationTag == "" {
m.NotificationTag = "default"
}
if err == nil {
err = utils.Json.Unmarshal([]byte(mf.FailTriggerTasksRaw), &m.FailTriggerTasks)
err = utils.Json.Unmarshal([]byte(mf.FailTriggerTasksRaw), &m.FailTriggerTasks)
}
if err == nil {
err = utils.Json.Unmarshal([]byte(mf.RecoverTriggerTasksRaw), &m.RecoverTriggerTasks)
}
if err == nil {
if m.ID == 0 {
err = singleton.DB.Create(&m).Error
} else {
err = singleton.DB.Save(&m).Error
}
if err == nil {
err = utils.Json.Unmarshal([]byte(mf.RecoverTriggerTasksRaw), &m.RecoverTriggerTasks)
}
if err == nil {
if m.ID == 0 {
err = singleton.DB.Create(&m).Error
} else {
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 {
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 {
@@ -733,6 +739,45 @@ func (ma *memberAPI) addOrEditNotification(c *gin.Context) {
})
}
type natForm struct {
ID uint64
Name string
ServerID uint64
Host string
Domain string
}
func (ma *memberAPI) addOrEditNAT(c *gin.Context) {
var nf natForm
var n model.NAT
err := c.ShouldBindJSON(&nf)
if err == nil {
n.Name = nf.Name
n.ID = nf.ID
n.Domain = nf.Domain
n.Host = nf.Host
n.ServerID = nf.ServerID
}
if err == nil {
if n.ID == 0 {
err = singleton.DB.Create(&n).Error
} else {
err = singleton.DB.Save(&n).Error
}
}
if err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("请求错误:%s", err),
})
return
}
singleton.OnNATUpdate()
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
})
}
type alertRuleForm struct {
ID uint64
Name string
@@ -847,6 +892,11 @@ func (ma *memberAPI) logout(c *gin.Context) {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
})
if oidcLogoutUrl := singleton.Conf.Oauth2.OidcLogoutURL; oidcLogoutUrl != "" {
// 重定向到 OIDC 退出登录地址。不知道为什么,这里的重定向不生效
c.Redirect(http.StatusOK, oidcLogoutUrl)
}
}
type settingForm struct {
+10
View File
@@ -27,6 +27,7 @@ func (mp *memberPage) serve() {
mr.GET("/monitor", mp.monitor)
mr.GET("/cron", mp.cron)
mr.GET("/notification", mp.notification)
mr.GET("/nat", mp.nat)
mr.GET("/setting", mp.setting)
mr.GET("/api", mp.api)
}
@@ -77,6 +78,15 @@ func (mp *memberPage) notification(c *gin.Context) {
}))
}
func (mp *memberPage) nat(c *gin.Context) {
var data []model.NAT
singleton.DB.Find(&data)
c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/nat", mygin.CommonEnvironment(c, gin.H{
"Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "NAT"}),
"NAT": data,
}))
}
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"}),
+90 -15
View File
@@ -2,6 +2,7 @@ package controller
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
@@ -9,23 +10,27 @@ import (
"strings"
"time"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/naiba/nezha/pkg/oidc/cloudflare"
myOidc "github.com/naiba/nezha/pkg/oidc/general"
"code.gitea.io/sdk/gitea"
"github.com/gin-gonic/gin"
GitHubAPI "github.com/google/go-github/v47/github"
"github.com/naiba/nezha/model"
"github.com/naiba/nezha/pkg/mygin"
"github.com/naiba/nezha/pkg/utils"
"github.com/naiba/nezha/service/singleton"
"github.com/patrickmn/go-cache"
"github.com/xanzy/go-gitlab"
"golang.org/x/oauth2"
GitHubOauth2 "golang.org/x/oauth2/github"
GitlabOauth2 "golang.org/x/oauth2/gitlab"
"github.com/naiba/nezha/model"
"github.com/naiba/nezha/pkg/mygin"
"github.com/naiba/nezha/pkg/utils"
"github.com/naiba/nezha/service/singleton"
)
type oauth2controller struct {
r gin.IRoutes
r gin.IRoutes
oidcProvider *oidc.Provider
}
func (oa *oauth2controller) serve() {
@@ -74,6 +79,38 @@ func (oa *oauth2controller) getCommonOauth2Config(c *gin.Context) *oauth2.Config
},
RedirectURL: oa.getRedirectURL(c),
}
} else if singleton.Conf.Oauth2.Type == model.ConfigTypeCloudflare {
return &oauth2.Config{
ClientID: singleton.Conf.Oauth2.ClientID,
ClientSecret: singleton.Conf.Oauth2.ClientSecret,
Scopes: []string{"openid", "email", "profile", "groups"},
Endpoint: oauth2.Endpoint{
AuthURL: fmt.Sprintf("%s/cdn-cgi/access/sso/oidc/%s/authorization", singleton.Conf.Oauth2.Endpoint, singleton.Conf.Oauth2.ClientID),
TokenURL: fmt.Sprintf("%s/cdn-cgi/access/sso/oidc/%s/token", singleton.Conf.Oauth2.Endpoint, singleton.Conf.Oauth2.ClientID),
},
RedirectURL: oa.getRedirectURL(c),
}
} else if singleton.Conf.Oauth2.Type == model.ConfigTypeOidc {
var err error
oa.oidcProvider, err = oidc.NewProvider(c.Request.Context(), singleton.Conf.Oauth2.OidcIssuer)
if err != nil {
mygin.ShowErrorPage(c, mygin.ErrInfo{
Code: http.StatusBadRequest,
Title: fmt.Sprintf("Cannot get OIDC infomaion from issuer from %s", singleton.Conf.Oauth2.OidcIssuer),
Msg: err.Error(),
}, true)
return nil
}
scopes := strings.Split(singleton.Conf.Oauth2.OidcScopes, ",")
scopes = append(scopes, oidc.ScopeOpenID)
uniqueScopes := removeDuplicates(scopes)
return &oauth2.Config{
ClientID: singleton.Conf.Oauth2.ClientID,
ClientSecret: singleton.Conf.Oauth2.ClientSecret,
Scopes: uniqueScopes,
Endpoint: oa.oidcProvider.Endpoint(),
RedirectURL: oa.getRedirectURL(c),
}
} else {
return &oauth2.Config{
ClientID: singleton.Conf.Oauth2.ClientID,
@@ -86,7 +123,8 @@ func (oa *oauth2controller) getCommonOauth2Config(c *gin.Context) *oauth2.Config
func (oa *oauth2controller) getRedirectURL(c *gin.Context) string {
scheme := "http://"
if strings.HasPrefix(c.Request.Referer(), "https://") {
referer := c.Request.Referer()
if forwardedProto := c.Request.Header.Get("X-Forwarded-Proto"); forwardedProto == "https" || strings.HasPrefix(referer, "https://") {
scheme = "https://"
}
return scheme + c.Request.Host + "/oauth2/callback"
@@ -155,6 +193,28 @@ func (oa *oauth2controller) callback(c *gin.Context) {
if err == nil {
user = model.NewUserFromGitea(u)
}
} else if singleton.Conf.Oauth2.Type == model.ConfigTypeCloudflare {
client := oauth2Config.Client(context.Background(), otk)
resp, err := client.Get(fmt.Sprintf("%s/cdn-cgi/access/sso/oidc/%s/userinfo", singleton.Conf.Oauth2.Endpoint, singleton.Conf.Oauth2.ClientID))
if err == nil {
defer resp.Body.Close()
var cloudflareUserInfo *cloudflare.UserInfo
if err := json.NewDecoder(resp.Body).Decode(&cloudflareUserInfo); err == nil {
user = cloudflareUserInfo.MapToNezhaUser()
}
}
} else if singleton.Conf.Oauth2.Type == model.ConfigTypeOidc {
userInfo, err := oa.oidcProvider.UserInfo(c.Request.Context(), oauth2.StaticTokenSource(otk))
if err == nil {
loginClaim := singleton.Conf.Oauth2.OidcLoginClaim
groupClain := singleton.Conf.Oauth2.OidcGroupClaim
adminGroups := strings.Split(singleton.Conf.Oauth2.AdminGroups, ",")
autoCreate := singleton.Conf.Oauth2.OidcAutoCreate
var oidceUserInfo *myOidc.UserInfo
if err := userInfo.Claims(&oidceUserInfo); err == nil {
user = oidceUserInfo.MapToNezhaUser(loginClaim, groupClain, adminGroups, autoCreate)
}
}
} else {
var client *GitHubAPI.Client
oc := oauth2Config.Client(ctx, otk)
@@ -168,15 +228,12 @@ func (oa *oauth2controller) callback(c *gin.Context) {
client = GitHubAPI.NewClient(oc)
}
var gu *GitHubAPI.User
if err == nil {
gu, _, err = client.Users.Get(ctx, "")
}
gu, _, err = client.Users.Get(ctx, "")
if err == nil {
user = model.NewUserFromGitHub(gu)
}
}
}
if err == nil && user.Login == "" {
err = errors.New("获取用户信息失败")
}
@@ -190,10 +247,15 @@ func (oa *oauth2controller) callback(c *gin.Context) {
return
}
var isAdmin bool
for _, admin := range strings.Split(singleton.Conf.Oauth2.Admin, ",") {
if admin != "" && strings.EqualFold(user.Login, admin) {
isAdmin = true
break
if user.SuperAdmin {
isAdmin = true
} else {
for _, admin := range strings.Split(singleton.Conf.Oauth2.Admin, ",") {
if admin != "" && strings.EqualFold(user.Login, admin) {
isAdmin = true
break
}
}
}
if !isAdmin {
@@ -220,3 +282,16 @@ func (oa *oauth2controller) callback(c *gin.Context) {
"URL": "/",
}))
}
func removeDuplicates(elements []string) []string {
encountered := map[string]bool{}
result := []string{}
for _, v := range elements {
if !encountered[v] {
encountered[v] = true
result = append(result, v)
}
}
return result
}
+1
View File
@@ -59,6 +59,7 @@ func main() {
return
}
// TODO 使用 cmux 在同一端口服务 HTTP 和 gRPC
singleton.CleanMonitorHistory()
go rpc.ServeRPC(singleton.Conf.GRPCPort)
serviceSentinelDispatchBus := make(chan model.Monitor) // 用于传递服务监控任务信息的channel
+2 -3
View File
@@ -14,9 +14,8 @@ import (
func ServeRPC(port uint) {
server := grpc.NewServer()
pb.RegisterNezhaServiceServer(server, &rpcService.NezhaHandler{
Auth: &rpcService.AuthHandler{},
})
rpcService.NezhaHandlerSingleton = rpcService.NewNezhaHandler()
pb.RegisterNezhaServiceServer(server, rpcService.NezhaHandlerSingleton)
listen, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
panic(err)
+16 -14
View File
@@ -1,11 +1,12 @@
module github.com/naiba/nezha
go 1.20
go 1.21
require (
code.cloudfoundry.org/bytefmt v0.0.0-20240405144452-ebb2996022ca
code.gitea.io/sdk/gitea v0.17.1
code.cloudfoundry.org/bytefmt v0.0.0-20240425163905-bcdc1ad063ea
code.gitea.io/sdk/gitea v0.18.0
github.com/BurntSushi/toml v1.3.2
github.com/coreos/go-oidc/v3 v3.11.0
github.com/gin-contrib/pprof v1.4.0
github.com/gin-gonic/gin v1.9.1
github.com/google/go-github/v47 v47.1.0
@@ -17,18 +18,18 @@ require (
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.39.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.18.2
github.com/xanzy/go-gitlab v0.102.0
golang.org/x/crypto v0.22.0
golang.org/x/oauth2 v0.19.0
github.com/xanzy/go-gitlab v0.103.0
golang.org/x/crypto v0.25.0
golang.org/x/net v0.27.0
golang.org/x/oauth2 v0.21.0
golang.org/x/sync v0.7.0
golang.org/x/text v0.14.0
golang.org/x/text v0.16.0
google.golang.org/grpc v1.63.0
google.golang.org/protobuf v1.33.0
google.golang.org/protobuf v1.34.2
gorm.io/driver/sqlite v1.5.5
gorm.io/gorm v1.25.9
gorm.io/gorm v1.25.10
sigs.k8s.io/yaml v1.4.0
)
@@ -40,13 +41,14 @@ require (
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
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
@@ -54,11 +56,12 @@ require (
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.17 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/oschwald/maxminddb-golang v1.13.1 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
@@ -73,8 +76,7 @@ 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.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
+53 -62
View File
@@ -1,7 +1,7 @@
code.cloudfoundry.org/bytefmt v0.0.0-20240405144452-ebb2996022ca h1:pyybVFhefCroOyqu71Rrnm0eORYFJMvTDsAsyPLjvEI=
code.cloudfoundry.org/bytefmt v0.0.0-20240405144452-ebb2996022ca/go.mod h1:21qd3QlOUZT1oQ2RccnLvWRu7CZWSN+3ZlUh2mzoE10=
code.gitea.io/sdk/gitea v0.17.1 h1:3jCPOG2ojbl8AcfaUCRYLT5MUcBMFwS0OSK2mA5Zok8=
code.gitea.io/sdk/gitea v0.17.1/go.mod h1:aCnBqhHpoEWA180gMbaCtdX9Pl6BWBAuuP2miadoTNM=
code.cloudfoundry.org/bytefmt v0.0.0-20240425163905-bcdc1ad063ea h1:1tgMNDgo8PjpsHhlaxdibj28C0WyLeOW2SPJ7GGdc9A=
code.cloudfoundry.org/bytefmt v0.0.0-20240425163905-bcdc1ad063ea/go.mod h1:3+xXJBOD8PsGHDqHedtCLalbaVJ+yi1OW+mXx9IcNxI=
code.gitea.io/sdk/gitea v0.18.0 h1:+zZrwVmujIrgobt6wVBWCqITz6bn1aBjnCUHmpZrerI=
code.gitea.io/sdk/gitea v0.18.0/go.mod h1:IG9xZJoltDNeDSW0qiF2Vqx5orMWa7OhVWrjvrd5NpI=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
@@ -10,14 +10,19 @@ github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZX
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/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
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=
@@ -31,9 +36,13 @@ 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-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
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=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
@@ -44,6 +53,7 @@ github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXS
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
@@ -52,20 +62,22 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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/pprof v0.0.0-20240402174815-29b9bb013b0f h1:f00RU+zOX+B3rLAmMMkzHUF2h1z4DeYR9tTCvEq2REY=
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg=
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/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=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0=
github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
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.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
@@ -87,6 +99,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -97,9 +110,10 @@ github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNa
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
@@ -112,9 +126,13 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8=
github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk=
github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
github.com/onsi/gomega v1.33.0 h1:snPCflnZrpMsy94p4lXVEkHo12lmPnc3vY5XBbreexE=
github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY=
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/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
@@ -125,17 +143,17 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/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.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.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.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.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
@@ -149,7 +167,6 @@ github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMV
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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -168,9 +185,8 @@ 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.102.0 h1:ExHuJ1OTQ2yt25zBMMj0G96ChBirGYv8U7HyUiYkZ+4=
github.com/xanzy/go-gitlab v0.102.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/xanzy/go-gitlab v0.103.0 h1:J9pTQoq0GsEFqzd6srCM1QfdfKAxSNz6mT6ntrpNF2w=
github.com/xanzy/go-gitlab v0.103.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI=
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=
@@ -182,27 +198,16 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
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.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -211,37 +216,23 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/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.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/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
@@ -249,8 +240,8 @@ google.golang.org/grpc v1.63.0 h1:WjKe+dnvABXyPJMD7KDNLxtoGk5tgk+YFWN6cBWjZE8=
google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.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=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
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=
@@ -267,8 +258,8 @@ 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.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8=
gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
+48 -19
View File
@@ -22,7 +22,7 @@ var Themes = map[string]string{
"mdui": "Neko Mdui",
"hotaru": "Hotaru",
"angel-kanade": "AngelKanade",
"server-status": "SeverStatus",
"server-status": "ServerStatus",
"custom": "Custom(local)",
}
@@ -32,11 +32,13 @@ var DashboardThemes = map[string]string{
}
const (
ConfigTypeGitHub = "github"
ConfigTypeGitee = "gitee"
ConfigTypeGitlab = "gitlab"
ConfigTypeJihulab = "jihulab"
ConfigTypeGitea = "gitea"
ConfigTypeGitHub = "github"
ConfigTypeGitee = "gitee"
ConfigTypeGitlab = "gitlab"
ConfigTypeJihulab = "jihulab"
ConfigTypeGitea = "gitea"
ConfigTypeCloudflare = "cloudflare"
ConfigTypeOidc = "oidc"
)
const (
@@ -70,7 +72,7 @@ func (c *AgentConfig) Save() error {
if err != nil {
return err
}
return os.WriteFile(c.v.ConfigFileUsed(), data, os.ModePerm)
return os.WriteFile(c.v.ConfigFileUsed(), data, 0600)
}
// Config 站点配置
@@ -86,11 +88,21 @@ type Config struct {
ViewPassword string // 前台查看密码
}
Oauth2 struct {
Type string
Admin string // 管理员用户名列表
ClientID string
ClientSecret string
Endpoint string
Type string
Admin string // 管理员用户名列表
AdminGroups string // 管理员用户组列表
ClientID string
ClientSecret string
Endpoint string
OidcDisplayName string // for OIDC Display Name
OidcIssuer string // for OIDC Issuer
OidcLogoutURL string // for OIDC Logout URL
OidcRegisterURL string // for OIDC Register URL
OidcLoginClaim string // for OIDC Claim
OidcGroupClaim string // for OIDC Group Claim
OidcScopes string // for OIDC Scopes
OidcAutoCreate bool // for OIDC Auto Create
OidcAutoLogin bool // for OIDC Auto Login
}
HTTPPort uint
GRPCPort uint
@@ -125,9 +137,20 @@ type Config struct {
WebhookRequestBody string
WebhookHeaders string
MaxRetries uint32
Profiles map[string]DDNSProfile
}
}
type DDNSProfile struct {
Provider string
AccessID string
AccessSecret string
WebhookURL string
WebhookMethod string
WebhookRequestBody string
WebhookHeaders string
}
// Read 读取配置文件并应用
func (c *Config) Read(path string) error {
c.v = viper.New()
@@ -166,15 +189,21 @@ func (c *Config) Read(path string) error {
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
}
if c.Oauth2.OidcScopes == "" {
c.Oauth2.OidcScopes = "openid,profile,email"
}
if c.Oauth2.OidcLoginClaim == "" {
c.Oauth2.OidcLoginClaim = "sub"
}
if c.Oauth2.OidcDisplayName == "" {
c.Oauth2.OidcDisplayName = "OIDC"
}
if c.Oauth2.OidcGroupClaim == "" {
c.Oauth2.OidcGroupClaim = "groups"
}
c.updateIgnoredIPNotificationID()
return nil
@@ -199,5 +228,5 @@ func (c *Config) Save() error {
if err != nil {
return err
}
return os.WriteFile(c.v.ConfigFileUsed(), data, os.ModePerm)
return os.WriteFile(c.v.ConfigFileUsed(), data, 0600)
}
+30
View File
@@ -10,6 +10,11 @@ const (
MTReportHostState
)
type SensorTemperature struct {
Name string
Temperature float64
}
type HostState struct {
CPU float64
MemUsed uint64
@@ -26,9 +31,19 @@ type HostState struct {
TcpConnCount uint64
UdpConnCount uint64
ProcessCount uint64
Temperatures []SensorTemperature
GPU float64
}
func (s *HostState) PB() *pb.State {
var ts []*pb.State_SensorTemperature
for _, t := range s.Temperatures {
ts = append(ts, &pb.State_SensorTemperature{
Name: t.Name,
Temperature: t.Temperature,
})
}
return &pb.State{
Cpu: s.CPU,
MemUsed: s.MemUsed,
@@ -45,10 +60,20 @@ func (s *HostState) PB() *pb.State {
TcpConnCount: s.TcpConnCount,
UdpConnCount: s.UdpConnCount,
ProcessCount: s.ProcessCount,
Temperatures: ts,
Gpu: s.GPU,
}
}
func PB2State(s *pb.State) HostState {
var ts []SensorTemperature
for _, t := range s.GetTemperatures() {
ts = append(ts, SensorTemperature{
Name: t.GetName(),
Temperature: t.GetTemperature(),
})
}
return HostState{
CPU: s.GetCpu(),
MemUsed: s.GetMemUsed(),
@@ -65,6 +90,8 @@ func PB2State(s *pb.State) HostState {
TcpConnCount: s.GetTcpConnCount(),
UdpConnCount: s.GetUdpConnCount(),
ProcessCount: s.GetProcessCount(),
Temperatures: ts,
GPU: s.GetGpu(),
}
}
@@ -81,6 +108,7 @@ type Host struct {
IP string `json:"-"`
CountryCode string
Version string
GPU []string
}
func (h *Host) PB() *pb.Host {
@@ -97,6 +125,7 @@ func (h *Host) PB() *pb.Host {
Ip: h.IP,
CountryCode: h.CountryCode,
Version: h.Version,
Gpu: h.GPU,
}
}
@@ -114,5 +143,6 @@ func PB2Host(h *pb.Host) Host {
IP: h.GetIp(),
CountryCode: h.GetCountryCode(),
Version: h.GetVersion(),
GPU: h.GetGpu(),
}
}
+9 -9
View File
@@ -20,17 +20,17 @@ const (
TaskTypeTerminal
TaskTypeUpgrade
TaskTypeKeepalive
TaskTypeTerminalGRPC
TaskTypeNAT
)
type TerminalTask struct {
// websocket 主机名
Host string `json:"host,omitempty"`
// 是否启用 SSL
UseSSL bool `json:"use_ssl,omitempty"`
// 会话标识
Session string `json:"session,omitempty"`
// Agent在连接Server时需要的额外Cookie信息
Cookie string `json:"cookie,omitempty"`
StreamID string
}
type TaskNAT struct {
StreamID string
Host string
}
const (
@@ -120,7 +120,7 @@ func (m *Monitor) AfterFind(tx *gorm.DB) error {
// IsServiceSentinelNeeded 判断该任务类型是否需要进行服务监控 需要则返回true
func IsServiceSentinelNeeded(t uint64) bool {
return t != TaskTypeCommand && t != TaskTypeTerminal && t != TaskTypeUpgrade
return t != TaskTypeCommand && t != TaskTypeTerminalGRPC && t != TaskTypeUpgrade
}
func (m *Monitor) InitSkipServers() error {
-1
View File
@@ -19,4 +19,3 @@ type MonitorHistory struct {
Down uint64 // 检查状态异常计数
Data string
}
+9
View File
@@ -0,0 +1,9 @@
package model
type NAT struct {
Common
Name string
ServerID uint64
Host string
Domain string `gorm:"unique"`
}
+28 -3
View File
@@ -42,6 +42,19 @@ func percentage(used, total uint64) float64 {
return float64(used) * 100 / float64(total)
}
func maxSliceValue(slice []float64) float64 {
if len(slice) != 0 {
max := slice[0]
for _, val := range slice {
if max < val {
max = val
}
}
return max
}
return 0
}
// Snapshot 未通过规则返回 struct{}{}, 通过返回 nil
func (u *Rule) Snapshot(cycleTransferStats *CycleTransferStats, server *Server, db *gorm.DB) interface{} {
// 监控全部但是排除了此服务器
@@ -63,6 +76,8 @@ func (u *Rule) Snapshot(cycleTransferStats *CycleTransferStats, server *Server,
switch u.Type {
case "cpu":
src = float64(server.State.CPU)
case "gpu":
src = float64(server.State.GPU)
case "memory":
src = percentage(server.State.MemUsed, server.Host.MemTotal)
case "swap":
@@ -91,21 +106,21 @@ func (u *Rule) Snapshot(cycleTransferStats *CycleTransferStats, server *Server,
src = float64(server.State.NetInTransfer - uint64(server.PrevHourlyTransferIn))
if u.CycleInterval != 0 {
var res NResult
db.Model(&Transfer{}).Select("SUM(`in`) AS n").Where("created_at > ? AND server_id = ?", u.GetTransferDurationStart(), server.ID).Scan(&res)
db.Model(&Transfer{}).Select("SUM(`in`) AS n").Where("datetime(`created_at`) >= datetime(?) AND server_id = ?", u.GetTransferDurationStart().UTC(), server.ID).Scan(&res)
src += float64(res.N)
}
case "transfer_out_cycle":
src = float64(server.State.NetOutTransfer - uint64(server.PrevHourlyTransferOut))
if u.CycleInterval != 0 {
var res NResult
db.Model(&Transfer{}).Select("SUM(`out`) AS n").Where("created_at > ? AND server_id = ?", u.GetTransferDurationStart(), server.ID).Scan(&res)
db.Model(&Transfer{}).Select("SUM(`out`) AS n").Where("datetime(`created_at`) >= datetime(?) AND server_id = ?", u.GetTransferDurationStart().UTC(), server.ID).Scan(&res)
src += float64(res.N)
}
case "transfer_all_cycle":
src = float64(server.State.NetOutTransfer - uint64(server.PrevHourlyTransferOut) + server.State.NetInTransfer - uint64(server.PrevHourlyTransferIn))
if u.CycleInterval != 0 {
var res NResult
db.Model(&Transfer{}).Select("SUM(`in`+`out`) AS n").Where("created_at > ? AND server_id = ?", u.GetTransferDurationStart(), server.ID).Scan(&res)
db.Model(&Transfer{}).Select("SUM(`in`+`out`) AS n").Where("datetime(`created_at`) >= datetime(?) AND server_id = ?", u.GetTransferDurationStart().UTC(), server.ID).Scan(&res)
src += float64(res.N)
}
case "load1":
@@ -120,6 +135,16 @@ func (u *Rule) Snapshot(cycleTransferStats *CycleTransferStats, server *Server,
src = float64(server.State.UdpConnCount)
case "process_count":
src = float64(server.State.ProcessCount)
case "temperature_max":
var temp []float64
if server.State.Temperatures != nil {
for _, tempStat := range server.State.Temperatures {
if tempStat.Temperature != 0 {
temp = append(temp, tempStat.Temperature)
}
}
src = maxSliceValue(temp)
}
}
// 循环区间流量检测 · 更新下次需要检测时间
+3 -1
View File
@@ -21,6 +21,7 @@ type Server struct {
EnableIPv4 bool // 是否启用DDNS IPv4
EnableIpv6 bool // 是否启用DDNS IPv6
DDNSDomain string // DDNS中的前缀 如基础域名为abc.oracle DDNSName为mjj 就会把mjj.abc.oracle解析服务器IP 为空则停用
DDNSProfile string // DDNS配置
Host *Host `gorm:"-"`
State *HostState `gorm:"-"`
@@ -56,5 +57,6 @@ func (s Server) Marshal() template.JS {
note, _ := utils.Json.Marshal(s.Note)
secret, _ := utils.Json.Marshal(s.Secret)
ddnsDomain, _ := utils.Json.Marshal(s.DDNSDomain)
return template.JS(fmt.Sprintf(`{"ID":%d,"Name":%s,"Secret":%s,"DisplayIndex":%d,"Tag":%s,"Note":%s,"HideForGuest": %s,"EnableDDNS": %s,"EnableIPv4": %s,"EnableIpv6": %s,"DDNSDomain": %s}`, s.ID, name, secret, s.DisplayIndex, tag, note, boolToString(s.HideForGuest), boolToString(s.EnableDDNS), boolToString(s.EnableIPv4), boolToString(s.EnableIpv6), ddnsDomain)) // #nosec
ddnsProfile, _ := utils.Json.Marshal(s.DDNSProfile)
return template.JS(fmt.Sprintf(`{"ID":%d,"Name":%s,"Secret":%s,"DisplayIndex":%d,"Tag":%s,"Note":%s,"HideForGuest": %s,"EnableDDNS": %s,"EnableIPv4": %s,"EnableIpv6": %s,"DDNSDomain": %s,"DDNSProfile": %s}`, s.ID, name, secret, s.DisplayIndex, tag, note, boolToString(s.HideForGuest), boolToString(s.EnableDDNS), boolToString(s.EnableIPv4), boolToString(s.EnableIpv6), ddnsDomain, ddnsProfile)) // #nosec
}
+7 -7
View File
@@ -13,7 +13,7 @@ type ProviderCloudflare struct {
Secret string
}
func (provider ProviderCloudflare) UpdateDomain(domainConfig *DomainConfig) bool {
func (provider *ProviderCloudflare) UpdateDomain(domainConfig *DomainConfig) bool {
if domainConfig == nil {
return false
}
@@ -42,7 +42,7 @@ func (provider ProviderCloudflare) UpdateDomain(domainConfig *DomainConfig) bool
return resultV4 && resultV6
}
func (provider ProviderCloudflare) addDomainRecord(zoneID string, domainConfig *DomainConfig, isIpv4 bool) bool {
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)
@@ -58,7 +58,7 @@ func (provider ProviderCloudflare) addDomainRecord(zoneID string, domainConfig *
}
}
func (provider ProviderCloudflare) getZoneID(domain string) (string, error) {
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)
@@ -81,7 +81,7 @@ func (provider ProviderCloudflare) getZoneID(domain string) (string, error) {
return "", fmt.Errorf("找不到 Zone ID")
}
func (provider ProviderCloudflare) findDNSRecord(zoneID string, domain string, isIPv4 bool) (map[string]interface{}, error) {
func (provider *ProviderCloudflare) findDNSRecord(zoneID string, domain string, isIPv4 bool) (map[string]interface{}, error) {
var ipType = "A"
if !isIPv4 {
ipType = "AAAA"
@@ -106,7 +106,7 @@ func (provider ProviderCloudflare) findDNSRecord(zoneID string, domain string, i
return nil, nil // 没有找到 DNS 记录
}
func (provider ProviderCloudflare) createDNSRecord(zoneID string, domainConfig *DomainConfig, isIPv4 bool) bool {
func (provider *ProviderCloudflare) createDNSRecord(zoneID string, domainConfig *DomainConfig, isIPv4 bool) bool {
var ipType = "A"
var ipAddr = domainConfig.Ipv4Addr
if !isIPv4 {
@@ -126,7 +126,7 @@ func (provider ProviderCloudflare) createDNSRecord(zoneID string, domainConfig *
return err == nil
}
func (provider ProviderCloudflare) updateDNSRecord(zoneID string, recordID string, domainConfig *DomainConfig, isIPv4 bool) bool {
func (provider *ProviderCloudflare) updateDNSRecord(zoneID string, recordID string, domainConfig *DomainConfig, isIPv4 bool) bool {
var ipType = "A"
var ipAddr = domainConfig.Ipv4Addr
if !isIPv4 {
@@ -147,7 +147,7 @@ func (provider ProviderCloudflare) updateDNSRecord(zoneID string, recordID strin
}
// 以下为辅助方法,如发送 HTTP 请求等
func (provider ProviderCloudflare) sendRequest(method string, url string, data []byte) ([]byte, error) {
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 {
+1 -1
View File
@@ -2,6 +2,6 @@ package ddns
type ProviderDummy struct{}
func (provider ProviderDummy) UpdateDomain(domainConfig *DomainConfig) bool {
func (provider *ProviderDummy) UpdateDomain(domainConfig *DomainConfig) bool {
return false
}
+5 -26
View File
@@ -1,7 +1,7 @@
package ddns
import (
"fmt"
"golang.org/x/net/publicsuffix"
"net/http"
"strings"
)
@@ -33,29 +33,8 @@ func SetStringHeadersToRequest(req *http.Request, headers []string) {
}
// 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 // 当域名不包含子域名时,无前缀
func SplitDomain(domain string) (prefix string, realDomain string) {
realDomain, _ = publicsuffix.EffectiveTLDPlusOne(domain)
prefix = domain[:len(domain)-len(realDomain)-1]
return prefix, realDomain
}
+228
View File
@@ -0,0 +1,228 @@
package ddns
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"log"
"net/http"
"strconv"
"strings"
"time"
)
const (
url = "https://dnspod.tencentcloudapi.com"
)
type ProviderTencentCloud struct {
SecretID string
SecretKey string
}
func (provider *ProviderTencentCloud) UpdateDomain(domainConfig *DomainConfig) bool {
if domainConfig == nil {
return false
}
// 当IPv4和IPv6同时成功才算作成功
var resultV4 = true
var resultV6 = true
if domainConfig.EnableIPv4 {
if !provider.addDomainRecord(domainConfig, true) {
resultV4 = false
}
}
if domainConfig.EnableIpv6 {
if !provider.addDomainRecord(domainConfig, false) {
resultV6 = false
}
}
return resultV4 && resultV6
}
func (provider *ProviderTencentCloud) addDomainRecord(domainConfig *DomainConfig, isIpv4 bool) bool {
record, err := provider.findDNSRecord(domainConfig.FullDomain, isIpv4)
if err != nil {
log.Printf("查找 DNS 记录时出错: %s\n", err)
return false
}
if errResponse, ok := record["Error"].(map[string]interface{}); ok {
if errCode, ok := errResponse["Code"].(string); ok && errCode == "ResourceNotFound.NoDataOfRecord" { // 没有找到 DNS 记录
// 添加 DNS 记录
return provider.createDNSRecord(domainConfig.FullDomain, domainConfig, isIpv4)
} else {
log.Printf("查询 DNS 记录时出错,错误代码为: %s\n", errCode)
}
}
// 默认情况下更新 DNS 记录
return provider.updateDNSRecord(domainConfig.FullDomain, record["RecordList"].([]interface{})[0].(map[string]interface{})["RecordId"].(float64), domainConfig, isIpv4)
}
func (provider *ProviderTencentCloud) findDNSRecord(domain string, isIPv4 bool) (map[string]interface{}, error) {
var ipType = "A"
if !isIPv4 {
ipType = "AAAA"
}
_, realDomain := SplitDomain(domain)
prefix, _ := SplitDomain(domain)
data := map[string]interface{}{
"RecordType": ipType,
"Domain": realDomain,
"RecordLine": "默认",
"Subdomain": prefix,
}
jsonData, _ := json.Marshal(data)
body, err := provider.sendRequest("DescribeRecordList", jsonData)
if err != nil {
return nil, err
}
var res map[string]interface{}
err = json.Unmarshal(body, &res)
if err != nil {
return nil, err
}
result := res["Response"].(map[string]interface{})
return result, nil
}
func (provider *ProviderTencentCloud) createDNSRecord(domain string, domainConfig *DomainConfig, isIPv4 bool) bool {
var ipType = "A"
var ipAddr = domainConfig.Ipv4Addr
if !isIPv4 {
ipType = "AAAA"
ipAddr = domainConfig.Ipv6Addr
}
_, realDomain := SplitDomain(domain)
prefix, _ := SplitDomain(domain)
data := map[string]interface{}{
"RecordType": ipType,
"RecordLine": "默认",
"Domain": realDomain,
"SubDomain": prefix,
"Value": ipAddr,
"TTL": 600,
}
jsonData, _ := json.Marshal(data)
_, err := provider.sendRequest("CreateRecord", jsonData)
return err == nil
}
func (provider *ProviderTencentCloud) updateDNSRecord(domain string, recordID float64, domainConfig *DomainConfig, isIPv4 bool) bool {
var ipType = "A"
var ipAddr = domainConfig.Ipv4Addr
if !isIPv4 {
ipType = "AAAA"
ipAddr = domainConfig.Ipv6Addr
}
_, realDomain := SplitDomain(domain)
prefix, _ := SplitDomain(domain)
data := map[string]interface{}{
"RecordType": ipType,
"RecordLine": "默认",
"Domain": realDomain,
"SubDomain": prefix,
"Value": ipAddr,
"TTL": 600,
"RecordId": recordID,
}
jsonData, _ := json.Marshal(data)
_, err := provider.sendRequest("ModifyRecord", jsonData)
return err == nil
}
// 以下为辅助方法,如发送 HTTP 请求等
func (provider *ProviderTencentCloud) sendRequest(action string, data []byte) ([]byte, error) {
client := &http.Client{}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-TC-Version", "2021-03-23")
provider.signRequest(provider.SecretID, provider.SecretKey, req, action, string(data))
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
}
// https://github.com/jeessy2/ddns-go/blob/master/util/tencent_cloud_signer.go
func (provider *ProviderTencentCloud) sha256hex(s string) string {
b := sha256.Sum256([]byte(s))
return hex.EncodeToString(b[:])
}
func (provider *ProviderTencentCloud) hmacsha256(s, key string) string {
hashed := hmac.New(sha256.New, []byte(key))
hashed.Write([]byte(s))
return string(hashed.Sum(nil))
}
func (provider *ProviderTencentCloud) WriteString(strs ...string) string {
var b strings.Builder
for _, str := range strs {
b.WriteString(str)
}
return b.String()
}
func (provider *ProviderTencentCloud) signRequest(secretId string, secretKey string, r *http.Request, action string, payload string) {
algorithm := "TC3-HMAC-SHA256"
service := "dnspod"
host := provider.WriteString(service, ".tencentcloudapi.com")
timestamp := time.Now().Unix()
timestampStr := strconv.FormatInt(timestamp, 10)
// 步骤 1:拼接规范请求串
canonicalHeaders := provider.WriteString("content-type:application/json\nhost:", host, "\nx-tc-action:", strings.ToLower(action), "\n")
signedHeaders := "content-type;host;x-tc-action"
hashedRequestPayload := provider.sha256hex(payload)
canonicalRequest := provider.WriteString("POST\n/\n\n", canonicalHeaders, "\n", signedHeaders, "\n", hashedRequestPayload)
// 步骤 2:拼接待签名字符串
date := time.Unix(timestamp, 0).UTC().Format("2006-01-02")
credentialScope := provider.WriteString(date, "/", service, "/tc3_request")
hashedCanonicalRequest := provider.sha256hex(canonicalRequest)
string2sign := provider.WriteString(algorithm, "\n", timestampStr, "\n", credentialScope, "\n", hashedCanonicalRequest)
// 步骤 3:计算签名
secretDate := provider.hmacsha256(date, provider.WriteString("TC3", secretKey))
secretService := provider.hmacsha256(service, secretDate)
secretSigning := provider.hmacsha256("tc3_request", secretService)
signature := hex.EncodeToString([]byte(provider.hmacsha256(string2sign, secretSigning)))
// 步骤 4:拼接 Authorization
authorization := provider.WriteString(algorithm, " Credential=", secretId, "/", credentialScope, ", SignedHeaders=", signedHeaders, ", Signature=", signature)
r.Header.Add("Authorization", authorization)
r.Header.Set("Host", host)
r.Header.Set("X-TC-Action", action)
r.Header.Add("X-TC-Timestamp", timestampStr)
}
+1 -1
View File
@@ -14,7 +14,7 @@ type ProviderWebHook struct {
RequestHeader string
}
func (provider ProviderWebHook) UpdateDomain(domainConfig *DomainConfig) bool {
func (provider *ProviderWebHook) UpdateDomain(domainConfig *DomainConfig) bool {
if domainConfig == nil {
return false
}
+1
View File
@@ -0,0 +1 @@
stub
+54
View File
@@ -0,0 +1,54 @@
package geoip
import (
"embed"
"fmt"
"log"
"net"
"strings"
maxminddb "github.com/oschwald/maxminddb-golang"
)
//go:embed geoip.db
var geoDBFS embed.FS
var (
dbData []byte
err error
)
type IPInfo struct {
Country string `maxminddb:"country"`
CountryName string `maxminddb:"country_name"`
Continent string `maxminddb:"continent"`
ContinentName string `maxminddb:"continent_name"`
}
func init() {
dbData, err = geoDBFS.ReadFile("geoip.db")
if err != nil {
log.Printf("NEZHA>> Failed to open geoip database: %v", err)
}
}
func Lookup(ip net.IP, record *IPInfo) (string, error) {
db, err := maxminddb.FromBytes(dbData)
if err != nil {
return "", err
}
defer db.Close()
err = db.Lookup(ip, record)
if err != nil {
return "", err
}
if record.Country != "" {
return strings.ToLower(record.Country), nil
} else if record.Continent != "" {
return strings.ToLower(record.Continent), nil
}
return "", fmt.Errorf("IP not found")
}
+65
View File
@@ -0,0 +1,65 @@
package grpcx
import (
"context"
"io"
"sync/atomic"
"github.com/naiba/nezha/proto"
)
var _ io.ReadWriteCloser = &IOStreamWrapper{}
type IOStream interface {
Recv() (*proto.IOStreamData, error)
Send(*proto.IOStreamData) error
Context() context.Context
}
type IOStreamWrapper struct {
IOStream
dataBuf []byte
closed *atomic.Bool
closeCh chan struct{}
}
func NewIOStreamWrapper(stream IOStream) *IOStreamWrapper {
return &IOStreamWrapper{
IOStream: stream,
closeCh: make(chan struct{}),
closed: new(atomic.Bool),
}
}
func (iw *IOStreamWrapper) Read(p []byte) (n int, err error) {
if len(iw.dataBuf) > 0 {
n := copy(p, iw.dataBuf)
iw.dataBuf = iw.dataBuf[n:]
return n, nil
}
var data *proto.IOStreamData
if data, err = iw.Recv(); err != nil {
return 0, err
}
n = copy(p, data.Data)
if n < len(data.Data) {
iw.dataBuf = data.Data[n:]
}
return n, nil
}
func (iw *IOStreamWrapper) Write(p []byte) (n int, err error) {
err = iw.Send(&proto.IOStreamData{Data: p})
return len(p), err
}
func (iw *IOStreamWrapper) Close() error {
if iw.closed.CompareAndSwap(false, true) {
close(iw.closeCh)
}
return nil
}
func (iw *IOStreamWrapper) Wait() {
<-iw.closeCh
}
+1
View File
@@ -16,6 +16,7 @@ var adminPage = map[string]bool{
"/monitor": true,
"/setting": true,
"/notification": true,
"/nat": true,
"/cron": true,
"/api": true,
}
+22
View File
@@ -0,0 +1,22 @@
package cloudflare
import (
"github.com/naiba/nezha/model"
"github.com/naiba/nezha/service/singleton"
)
type UserInfo struct {
Sub string `json:"sub"`
Email string `json:"email"`
Name string `json:"name"`
Groups []string `json:"groups"`
}
func (u UserInfo) MapToNezhaUser() model.User {
var user model.User
singleton.DB.Where("login = ?", u.Sub).First(&user)
user.Login = u.Sub
user.Email = u.Email
user.Name = u.Name
return user
}
+56
View File
@@ -0,0 +1,56 @@
package general
import (
"github.com/naiba/nezha/model"
"github.com/naiba/nezha/service/singleton"
)
type UserInfo struct {
Sub string `json:"sub"`
Username string `json:"preferred_username"`
Email string `json:"email"`
Name string `json:"name"`
Groups []string `json:"groups,omitempty"`
Roles []string `json:"roles,omitempty"`
}
func (u UserInfo) MapToNezhaUser(loginClaim string, groupClaim string, adminGroups []string, autoCreate bool) model.User {
var user model.User
var login string
var groups []string
var isAdmin bool
if loginClaim == "email" {
login = u.Email
} else if loginClaim == "preferred_username" {
login = u.Username
} else {
login = u.Sub
}
if groupClaim == "roles" {
groups = u.Roles
} else {
groups = u.Groups
}
// Check if user is admin
adminGroupSet := make(map[string]struct{}, len(adminGroups))
for _, adminGroup := range adminGroups {
adminGroupSet[adminGroup] = struct{}{}
}
for _, group := range groups {
if _, found := adminGroupSet[group]; found {
isAdmin = true
break
}
}
result := singleton.DB.Where("login = ?", login).First(&user)
user.Login = login
user.Email = u.Email
user.Name = u.Name
user.SuperAdmin = isAdmin
if result.Error != nil && autoCreate {
singleton.DB.Create(&user)
} else if result.Error != nil {
return model.User{}
}
return user
}
+56
View File
@@ -0,0 +1,56 @@
package utils
import (
"bytes"
"io"
"net"
"net/http"
"github.com/gin-gonic/gin"
)
var _ io.ReadWriteCloser = &RequestWrapper{}
type RequestWrapper struct {
req *http.Request
reader *bytes.Buffer
writer net.Conn
}
func NewRequestWrapper(req *http.Request, writer gin.ResponseWriter) (*RequestWrapper, error) {
conn, _, err := writer.Hijack()
if err != nil {
return nil, err
}
buf := bytes.NewBuffer(nil)
if err = req.Write(buf); err != nil {
return nil, err
}
return &RequestWrapper{
req: req,
reader: buf,
writer: conn,
}, nil
}
func (rw *RequestWrapper) Read(p []byte) (int, error) {
count, err := rw.reader.Read(p)
if err == nil {
return count, nil
}
if err != io.EOF {
return count, err
}
// request 数据读完之后等待客户端断开连接或 grpc 超时
return rw.writer.Read(p)
}
func (rw *RequestWrapper) Write(p []byte) (int, error) {
return rw.writer.Write(p)
}
func (rw *RequestWrapper) Close() error {
rw.req.Body.Close()
rw.writer.Close()
return nil
}
+41 -10
View File
@@ -1,25 +1,56 @@
package websocketx
import (
"io"
"sync"
"github.com/gorilla/websocket"
"github.com/samber/lo"
)
var _ io.ReadWriteCloser = &Conn{}
type Conn struct {
*websocket.Conn
writeLock sync.Mutex
writeLock *sync.Mutex
dataBuf []byte
}
func (conn *Conn) WriteMessage(msgType int, data []byte) error {
func NewConn(conn *websocket.Conn) *Conn {
return &Conn{Conn: conn, writeLock: new(sync.Mutex)}
}
func (conn *Conn) Write(data []byte) (int, error) {
conn.writeLock.Lock()
defer conn.writeLock.Unlock()
var err error
lo.TryCatchWithErrorValue(func() error {
return conn.Conn.WriteMessage(msgType, data)
}, func(res any) {
err = res.(error)
})
return err
if err := conn.Conn.WriteMessage(websocket.BinaryMessage, data); err != nil {
return 0, err
}
return len(data), nil
}
func (conn *Conn) WriteMessage(messageType int, data []byte) error {
conn.writeLock.Lock()
defer conn.writeLock.Unlock()
return conn.Conn.WriteMessage(messageType, data)
}
func (conn *Conn) Read(data []byte) (int, error) {
if len(conn.dataBuf) > 0 {
n := copy(data, conn.dataBuf)
conn.dataBuf = conn.dataBuf[n:]
return n, nil
}
mType, innerData, err := conn.Conn.ReadMessage()
if err != nil {
return 0, err
}
// 将文本消息转换为命令输入
if mType == websocket.TextMessage {
innerData = append([]byte{0}, innerData...)
}
n := copy(data, innerData)
if n < len(innerData) {
conn.dataBuf = innerData[n:]
}
return n, nil
}
+358 -109
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.30.0
// protoc v3.21.12
// protoc-gen-go v1.34.1
// protoc v5.26.1
// source: proto/nezha.proto
package proto
@@ -35,8 +35,9 @@ type Host struct {
Virtualization string `protobuf:"bytes,8,opt,name=virtualization,proto3" json:"virtualization,omitempty"`
BootTime uint64 `protobuf:"varint,9,opt,name=boot_time,json=bootTime,proto3" json:"boot_time,omitempty"`
Ip string `protobuf:"bytes,10,opt,name=ip,proto3" json:"ip,omitempty"`
CountryCode string `protobuf:"bytes,11,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"`
CountryCode string `protobuf:"bytes,11,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"` // deprecated
Version string `protobuf:"bytes,12,opt,name=version,proto3" json:"version,omitempty"`
Gpu []string `protobuf:"bytes,13,rep,name=gpu,proto3" json:"gpu,omitempty"`
}
func (x *Host) Reset() {
@@ -155,26 +156,35 @@ func (x *Host) GetVersion() string {
return ""
}
func (x *Host) GetGpu() []string {
if x != nil {
return x.Gpu
}
return nil
}
type State struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Cpu float64 `protobuf:"fixed64,1,opt,name=cpu,proto3" json:"cpu,omitempty"`
MemUsed uint64 `protobuf:"varint,3,opt,name=mem_used,json=memUsed,proto3" json:"mem_used,omitempty"`
SwapUsed uint64 `protobuf:"varint,4,opt,name=swap_used,json=swapUsed,proto3" json:"swap_used,omitempty"`
DiskUsed uint64 `protobuf:"varint,5,opt,name=disk_used,json=diskUsed,proto3" json:"disk_used,omitempty"`
NetInTransfer uint64 `protobuf:"varint,6,opt,name=net_in_transfer,json=netInTransfer,proto3" json:"net_in_transfer,omitempty"`
NetOutTransfer uint64 `protobuf:"varint,7,opt,name=net_out_transfer,json=netOutTransfer,proto3" json:"net_out_transfer,omitempty"`
NetInSpeed uint64 `protobuf:"varint,8,opt,name=net_in_speed,json=netInSpeed,proto3" json:"net_in_speed,omitempty"`
NetOutSpeed uint64 `protobuf:"varint,9,opt,name=net_out_speed,json=netOutSpeed,proto3" json:"net_out_speed,omitempty"`
Uptime uint64 `protobuf:"varint,10,opt,name=uptime,proto3" json:"uptime,omitempty"`
Load1 float64 `protobuf:"fixed64,11,opt,name=load1,proto3" json:"load1,omitempty"`
Load5 float64 `protobuf:"fixed64,12,opt,name=load5,proto3" json:"load5,omitempty"`
Load15 float64 `protobuf:"fixed64,13,opt,name=load15,proto3" json:"load15,omitempty"`
TcpConnCount uint64 `protobuf:"varint,14,opt,name=tcp_conn_count,json=tcpConnCount,proto3" json:"tcp_conn_count,omitempty"`
UdpConnCount uint64 `protobuf:"varint,15,opt,name=udp_conn_count,json=udpConnCount,proto3" json:"udp_conn_count,omitempty"`
ProcessCount uint64 `protobuf:"varint,16,opt,name=process_count,json=processCount,proto3" json:"process_count,omitempty"`
Cpu float64 `protobuf:"fixed64,1,opt,name=cpu,proto3" json:"cpu,omitempty"`
MemUsed uint64 `protobuf:"varint,3,opt,name=mem_used,json=memUsed,proto3" json:"mem_used,omitempty"`
SwapUsed uint64 `protobuf:"varint,4,opt,name=swap_used,json=swapUsed,proto3" json:"swap_used,omitempty"`
DiskUsed uint64 `protobuf:"varint,5,opt,name=disk_used,json=diskUsed,proto3" json:"disk_used,omitempty"`
NetInTransfer uint64 `protobuf:"varint,6,opt,name=net_in_transfer,json=netInTransfer,proto3" json:"net_in_transfer,omitempty"`
NetOutTransfer uint64 `protobuf:"varint,7,opt,name=net_out_transfer,json=netOutTransfer,proto3" json:"net_out_transfer,omitempty"`
NetInSpeed uint64 `protobuf:"varint,8,opt,name=net_in_speed,json=netInSpeed,proto3" json:"net_in_speed,omitempty"`
NetOutSpeed uint64 `protobuf:"varint,9,opt,name=net_out_speed,json=netOutSpeed,proto3" json:"net_out_speed,omitempty"`
Uptime uint64 `protobuf:"varint,10,opt,name=uptime,proto3" json:"uptime,omitempty"`
Load1 float64 `protobuf:"fixed64,11,opt,name=load1,proto3" json:"load1,omitempty"`
Load5 float64 `protobuf:"fixed64,12,opt,name=load5,proto3" json:"load5,omitempty"`
Load15 float64 `protobuf:"fixed64,13,opt,name=load15,proto3" json:"load15,omitempty"`
TcpConnCount uint64 `protobuf:"varint,14,opt,name=tcp_conn_count,json=tcpConnCount,proto3" json:"tcp_conn_count,omitempty"`
UdpConnCount uint64 `protobuf:"varint,15,opt,name=udp_conn_count,json=udpConnCount,proto3" json:"udp_conn_count,omitempty"`
ProcessCount uint64 `protobuf:"varint,16,opt,name=process_count,json=processCount,proto3" json:"process_count,omitempty"`
Temperatures []*State_SensorTemperature `protobuf:"bytes,17,rep,name=temperatures,proto3" json:"temperatures,omitempty"`
Gpu float64 `protobuf:"fixed64,18,opt,name=gpu,proto3" json:"gpu,omitempty"`
}
func (x *State) Reset() {
@@ -314,6 +324,75 @@ func (x *State) GetProcessCount() uint64 {
return 0
}
func (x *State) GetTemperatures() []*State_SensorTemperature {
if x != nil {
return x.Temperatures
}
return nil
}
func (x *State) GetGpu() float64 {
if x != nil {
return x.Gpu
}
return 0
}
type State_SensorTemperature struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Temperature float64 `protobuf:"fixed64,2,opt,name=temperature,proto3" json:"temperature,omitempty"`
}
func (x *State_SensorTemperature) Reset() {
*x = State_SensorTemperature{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_nezha_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *State_SensorTemperature) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*State_SensorTemperature) ProtoMessage() {}
func (x *State_SensorTemperature) ProtoReflect() protoreflect.Message {
mi := &file_proto_nezha_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use State_SensorTemperature.ProtoReflect.Descriptor instead.
func (*State_SensorTemperature) Descriptor() ([]byte, []int) {
return file_proto_nezha_proto_rawDescGZIP(), []int{2}
}
func (x *State_SensorTemperature) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *State_SensorTemperature) GetTemperature() float64 {
if x != nil {
return x.Temperature
}
return 0
}
type Task struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -327,7 +406,7 @@ type Task struct {
func (x *Task) Reset() {
*x = Task{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_nezha_proto_msgTypes[2]
mi := &file_proto_nezha_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -340,7 +419,7 @@ func (x *Task) String() string {
func (*Task) ProtoMessage() {}
func (x *Task) ProtoReflect() protoreflect.Message {
mi := &file_proto_nezha_proto_msgTypes[2]
mi := &file_proto_nezha_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -353,7 +432,7 @@ func (x *Task) ProtoReflect() protoreflect.Message {
// Deprecated: Use Task.ProtoReflect.Descriptor instead.
func (*Task) Descriptor() ([]byte, []int) {
return file_proto_nezha_proto_rawDescGZIP(), []int{2}
return file_proto_nezha_proto_rawDescGZIP(), []int{3}
}
func (x *Task) GetId() uint64 {
@@ -392,7 +471,7 @@ type TaskResult struct {
func (x *TaskResult) Reset() {
*x = TaskResult{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_nezha_proto_msgTypes[3]
mi := &file_proto_nezha_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -405,7 +484,7 @@ func (x *TaskResult) String() string {
func (*TaskResult) ProtoMessage() {}
func (x *TaskResult) ProtoReflect() protoreflect.Message {
mi := &file_proto_nezha_proto_msgTypes[3]
mi := &file_proto_nezha_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -418,7 +497,7 @@ func (x *TaskResult) ProtoReflect() protoreflect.Message {
// Deprecated: Use TaskResult.ProtoReflect.Descriptor instead.
func (*TaskResult) Descriptor() ([]byte, []int) {
return file_proto_nezha_proto_rawDescGZIP(), []int{3}
return file_proto_nezha_proto_rawDescGZIP(), []int{4}
}
func (x *TaskResult) GetId() uint64 {
@@ -467,7 +546,7 @@ type Receipt struct {
func (x *Receipt) Reset() {
*x = Receipt{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_nezha_proto_msgTypes[4]
mi := &file_proto_nezha_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -480,7 +559,7 @@ func (x *Receipt) String() string {
func (*Receipt) ProtoMessage() {}
func (x *Receipt) ProtoReflect() protoreflect.Message {
mi := &file_proto_nezha_proto_msgTypes[4]
mi := &file_proto_nezha_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -493,7 +572,7 @@ func (x *Receipt) ProtoReflect() protoreflect.Message {
// Deprecated: Use Receipt.ProtoReflect.Descriptor instead.
func (*Receipt) Descriptor() ([]byte, []int) {
return file_proto_nezha_proto_rawDescGZIP(), []int{4}
return file_proto_nezha_proto_rawDescGZIP(), []int{5}
}
func (x *Receipt) GetProced() bool {
@@ -503,11 +582,113 @@ func (x *Receipt) GetProced() bool {
return false
}
type IOStreamData struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
}
func (x *IOStreamData) Reset() {
*x = IOStreamData{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_nezha_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *IOStreamData) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*IOStreamData) ProtoMessage() {}
func (x *IOStreamData) ProtoReflect() protoreflect.Message {
mi := &file_proto_nezha_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use IOStreamData.ProtoReflect.Descriptor instead.
func (*IOStreamData) Descriptor() ([]byte, []int) {
return file_proto_nezha_proto_rawDescGZIP(), []int{6}
}
func (x *IOStreamData) GetData() []byte {
if x != nil {
return x.Data
}
return nil
}
type GeoIP struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"`
CountryCode string `protobuf:"bytes,2,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"`
}
func (x *GeoIP) Reset() {
*x = GeoIP{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_nezha_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GeoIP) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GeoIP) ProtoMessage() {}
func (x *GeoIP) ProtoReflect() protoreflect.Message {
mi := &file_proto_nezha_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GeoIP.ProtoReflect.Descriptor instead.
func (*GeoIP) Descriptor() ([]byte, []int) {
return file_proto_nezha_proto_rawDescGZIP(), []int{7}
}
func (x *GeoIP) GetIp() string {
if x != nil {
return x.Ip
}
return ""
}
func (x *GeoIP) GetCountryCode() string {
if x != nil {
return x.CountryCode
}
return ""
}
var File_proto_nezha_proto protoreflect.FileDescriptor
var file_proto_nezha_proto_rawDesc = []byte{
0x0a, 0x11, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6e, 0x65, 0x7a, 0x68, 0x61, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x12, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe0, 0x02, 0x0a, 0x04, 0x48,
0x6f, 0x74, 0x6f, 0x12, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf2, 0x02, 0x0a, 0x04, 0x48,
0x6f, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12,
0x29, 0x0a, 0x10, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x76, 0x65, 0x72, 0x73,
@@ -529,65 +710,89 @@ var file_proto_nezha_proto_rawDesc = []byte{
0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65,
0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43,
0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0c,
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xd3, 0x03,
0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x70, 0x75, 0x18, 0x01,
0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x63, 0x70, 0x75, 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x65, 0x6d,
0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x6d, 0x65, 0x6d,
0x55, 0x73, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x75, 0x73, 0x65,
0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x73, 0x77, 0x61, 0x70, 0x55, 0x73, 0x65,
0x64, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x69, 0x73, 0x6b, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x05,
0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x69, 0x73, 0x6b, 0x55, 0x73, 0x65, 0x64, 0x12, 0x26,
0x0a, 0x0f, 0x6e, 0x65, 0x74, 0x5f, 0x69, 0x6e, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65,
0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x6e, 0x65, 0x74, 0x49, 0x6e, 0x54, 0x72,
0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x6e, 0x65, 0x74, 0x5f, 0x6f, 0x75,
0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04,
0x52, 0x0e, 0x6e, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72,
0x12, 0x20, 0x0a, 0x0c, 0x6e, 0x65, 0x74, 0x5f, 0x69, 0x6e, 0x5f, 0x73, 0x70, 0x65, 0x65, 0x64,
0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x6e, 0x65, 0x74, 0x49, 0x6e, 0x53, 0x70, 0x65,
0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x6e, 0x65, 0x74, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x73, 0x70,
0x65, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6e, 0x65, 0x74, 0x4f, 0x75,
0x74, 0x53, 0x70, 0x65, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65,
0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x14,
0x0a, 0x05, 0x6c, 0x6f, 0x61, 0x64, 0x31, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x6c,
0x6f, 0x61, 0x64, 0x31, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x6f, 0x61, 0x64, 0x35, 0x18, 0x0c, 0x20,
0x01, 0x28, 0x01, 0x52, 0x05, 0x6c, 0x6f, 0x61, 0x64, 0x35, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f,
0x61, 0x64, 0x31, 0x35, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x6c, 0x6f, 0x61, 0x64,
0x31, 0x35, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x63, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x5f, 0x63,
0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x74, 0x63, 0x70, 0x43,
0x6f, 0x6e, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x75, 0x64, 0x70, 0x5f,
0x63, 0x6f, 0x6e, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x04,
0x52, 0x0c, 0x75, 0x64, 0x70, 0x43, 0x6f, 0x6e, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23,
0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18,
0x10, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6f,
0x75, 0x6e, 0x74, 0x22, 0x3e, 0x0a, 0x04, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x0e, 0x0a, 0x02, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74,
0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12,
0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64,
0x61, 0x74, 0x61, 0x22, 0x7a, 0x0a, 0x0a, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c,
0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69,
0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52,
0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x03,
0x20, 0x01, 0x28, 0x02, 0x52, 0x05, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x64,
0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12,
0x1e, 0x0a, 0x0a, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x18, 0x05, 0x20,
0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x22,
0x21, 0x0a, 0x07, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72,
0x6f, 0x63, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x70, 0x72, 0x6f, 0x63,
0x65, 0x64, 0x32, 0xd6, 0x01, 0x0a, 0x0c, 0x4e, 0x65, 0x7a, 0x68, 0x61, 0x53, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x11, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x79, 0x73,
0x74, 0x65, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x1a, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52,
0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x10, 0x52, 0x65, 0x70, 0x6f,
0x72, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x0b, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x2e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x0a, 0x52,
0x65, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x1a, 0x0e, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x22, 0x00, 0x12, 0x2b,
0x0a, 0x0b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x0b, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x22, 0x00, 0x30, 0x01, 0x42, 0x09, 0x5a, 0x07, 0x2e,
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a,
0x03, 0x67, 0x70, 0x75, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x67, 0x70, 0x75, 0x22,
0xa9, 0x04, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x70, 0x75,
0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x63, 0x70, 0x75, 0x12, 0x19, 0x0a, 0x08, 0x6d,
0x65, 0x6d, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x6d,
0x65, 0x6d, 0x55, 0x73, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x75,
0x73, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x73, 0x77, 0x61, 0x70, 0x55,
0x73, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x69, 0x73, 0x6b, 0x5f, 0x75, 0x73, 0x65, 0x64,
0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x69, 0x73, 0x6b, 0x55, 0x73, 0x65, 0x64,
0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x74, 0x5f, 0x69, 0x6e, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73,
0x66, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x6e, 0x65, 0x74, 0x49, 0x6e,
0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x6e, 0x65, 0x74, 0x5f,
0x6f, 0x75, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01,
0x28, 0x04, 0x52, 0x0e, 0x6e, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66,
0x65, 0x72, 0x12, 0x20, 0x0a, 0x0c, 0x6e, 0x65, 0x74, 0x5f, 0x69, 0x6e, 0x5f, 0x73, 0x70, 0x65,
0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x6e, 0x65, 0x74, 0x49, 0x6e, 0x53,
0x70, 0x65, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x6e, 0x65, 0x74, 0x5f, 0x6f, 0x75, 0x74, 0x5f,
0x73, 0x70, 0x65, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6e, 0x65, 0x74,
0x4f, 0x75, 0x74, 0x53, 0x70, 0x65, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x70, 0x74, 0x69,
0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65,
0x12, 0x14, 0x0a, 0x05, 0x6c, 0x6f, 0x61, 0x64, 0x31, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x01, 0x52,
0x05, 0x6c, 0x6f, 0x61, 0x64, 0x31, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x6f, 0x61, 0x64, 0x35, 0x18,
0x0c, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x6c, 0x6f, 0x61, 0x64, 0x35, 0x12, 0x16, 0x0a, 0x06,
0x6c, 0x6f, 0x61, 0x64, 0x31, 0x35, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x6c, 0x6f,
0x61, 0x64, 0x31, 0x35, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x63, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x6e,
0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x74, 0x63,
0x70, 0x43, 0x6f, 0x6e, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x75, 0x64,
0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0f, 0x20, 0x01,
0x28, 0x04, 0x52, 0x0c, 0x75, 0x64, 0x70, 0x43, 0x6f, 0x6e, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74,
0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e,
0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73,
0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x42, 0x0a, 0x0c, 0x74, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61,
0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72,
0x54, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x0c, 0x74, 0x65, 0x6d,
0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x67, 0x70, 0x75,
0x18, 0x12, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x67, 0x70, 0x75, 0x22, 0x4f, 0x0a, 0x17, 0x53,
0x74, 0x61, 0x74, 0x65, 0x5f, 0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x54, 0x65, 0x6d, 0x70, 0x65,
0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x74, 0x65,
0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52,
0x0b, 0x74, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x3e, 0x0a, 0x04,
0x54, 0x61, 0x73, 0x6b, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x04, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x7a, 0x0a, 0x0a,
0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79,
0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14,
0x0a, 0x05, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x05, 0x64,
0x65, 0x6c, 0x61, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01,
0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x75, 0x63, 0x63,
0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x75,
0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x22, 0x21, 0x0a, 0x07, 0x52, 0x65, 0x63, 0x65,
0x69, 0x70, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x08, 0x52, 0x06, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x64, 0x22, 0x22, 0x0a, 0x0c, 0x49,
0x4f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x64,
0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22,
0x3a, 0x0a, 0x05, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e,
0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x32, 0xbf, 0x02, 0x0a, 0x0c,
0x4e, 0x65, 0x7a, 0x68, 0x61, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x11,
0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x53, 0x74, 0x61, 0x74,
0x65, 0x12, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x1a,
0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x22,
0x00, 0x12, 0x31, 0x0a, 0x10, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65,
0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x6f,
0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x65, 0x69,
0x70, 0x74, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x0a, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61,
0x73, 0x6b, 0x12, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x52,
0x65, 0x73, 0x75, 0x6c, 0x74, 0x1a, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65,
0x63, 0x65, 0x69, 0x70, 0x74, 0x22, 0x00, 0x12, 0x2b, 0x0a, 0x0b, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48,
0x6f, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x61, 0x73, 0x6b,
0x22, 0x00, 0x30, 0x01, 0x12, 0x3a, 0x0a, 0x08, 0x49, 0x4f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d,
0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x4f, 0x53, 0x74, 0x72, 0x65, 0x61,
0x6d, 0x44, 0x61, 0x74, 0x61, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x4f,
0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01,
0x12, 0x2b, 0x0a, 0x0b, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x12,
0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x1a, 0x0c, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x22, 0x00, 0x42, 0x09, 0x5a,
0x07, 0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -602,28 +807,36 @@ func file_proto_nezha_proto_rawDescGZIP() []byte {
return file_proto_nezha_proto_rawDescData
}
var file_proto_nezha_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_proto_nezha_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
var file_proto_nezha_proto_goTypes = []interface{}{
(*Host)(nil), // 0: proto.Host
(*State)(nil), // 1: proto.State
(*Task)(nil), // 2: proto.Task
(*TaskResult)(nil), // 3: proto.TaskResult
(*Receipt)(nil), // 4: proto.Receipt
(*Host)(nil), // 0: proto.Host
(*State)(nil), // 1: proto.State
(*State_SensorTemperature)(nil), // 2: proto.State_SensorTemperature
(*Task)(nil), // 3: proto.Task
(*TaskResult)(nil), // 4: proto.TaskResult
(*Receipt)(nil), // 5: proto.Receipt
(*IOStreamData)(nil), // 6: proto.IOStreamData
(*GeoIP)(nil), // 7: proto.GeoIP
}
var file_proto_nezha_proto_depIdxs = []int32{
1, // 0: proto.NezhaService.ReportSystemState:input_type -> proto.State
0, // 1: proto.NezhaService.ReportSystemInfo:input_type -> proto.Host
3, // 2: proto.NezhaService.ReportTask:input_type -> proto.TaskResult
0, // 3: proto.NezhaService.RequestTask:input_type -> proto.Host
4, // 4: proto.NezhaService.ReportSystemState:output_type -> proto.Receipt
4, // 5: proto.NezhaService.ReportSystemInfo:output_type -> proto.Receipt
4, // 6: proto.NezhaService.ReportTask:output_type -> proto.Receipt
2, // 7: proto.NezhaService.RequestTask:output_type -> proto.Task
4, // [4:8] is the sub-list for method output_type
0, // [0:4] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
2, // 0: proto.State.temperatures:type_name -> proto.State_SensorTemperature
1, // 1: proto.NezhaService.ReportSystemState:input_type -> proto.State
0, // 2: proto.NezhaService.ReportSystemInfo:input_type -> proto.Host
4, // 3: proto.NezhaService.ReportTask:input_type -> proto.TaskResult
0, // 4: proto.NezhaService.RequestTask:input_type -> proto.Host
6, // 5: proto.NezhaService.IOStream:input_type -> proto.IOStreamData
7, // 6: proto.NezhaService.LookupGeoIP:input_type -> proto.GeoIP
5, // 7: proto.NezhaService.ReportSystemState:output_type -> proto.Receipt
5, // 8: proto.NezhaService.ReportSystemInfo:output_type -> proto.Receipt
5, // 9: proto.NezhaService.ReportTask:output_type -> proto.Receipt
3, // 10: proto.NezhaService.RequestTask:output_type -> proto.Task
6, // 11: proto.NezhaService.IOStream:output_type -> proto.IOStreamData
7, // 12: proto.NezhaService.LookupGeoIP:output_type -> proto.GeoIP
7, // [7:13] is the sub-list for method output_type
1, // [1:7] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_proto_nezha_proto_init() }
@@ -657,7 +870,7 @@ func file_proto_nezha_proto_init() {
}
}
file_proto_nezha_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Task); i {
switch v := v.(*State_SensorTemperature); i {
case 0:
return &v.state
case 1:
@@ -669,7 +882,7 @@ func file_proto_nezha_proto_init() {
}
}
file_proto_nezha_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TaskResult); i {
switch v := v.(*Task); i {
case 0:
return &v.state
case 1:
@@ -681,6 +894,18 @@ func file_proto_nezha_proto_init() {
}
}
file_proto_nezha_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TaskResult); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_nezha_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Receipt); i {
case 0:
return &v.state
@@ -692,6 +917,30 @@ func file_proto_nezha_proto_init() {
return nil
}
}
file_proto_nezha_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*IOStreamData); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_nezha_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GeoIP); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
@@ -699,7 +948,7 @@ func file_proto_nezha_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_proto_nezha_proto_rawDesc,
NumEnums: 0,
NumMessages: 5,
NumMessages: 8,
NumExtensions: 0,
NumServices: 1,
},
+20 -1
View File
@@ -8,6 +8,8 @@ service NezhaService {
rpc ReportSystemInfo(Host)returns(Receipt){}
rpc ReportTask(TaskResult)returns(Receipt){}
rpc RequestTask(Host)returns(stream Task){}
rpc IOStream(stream IOStreamData)returns(stream IOStreamData){}
rpc LookupGeoIP(GeoIP)returns(GeoIP){}
}
message Host {
@@ -21,8 +23,9 @@ message Host {
string virtualization = 8;
uint64 boot_time = 9;
string ip = 10;
string country_code = 11;
string country_code = 11; // deprecated
string version = 12;
repeated string gpu = 13;
}
message State {
@@ -41,6 +44,13 @@ message State {
uint64 tcp_conn_count = 14;
uint64 udp_conn_count = 15;
uint64 process_count = 16;
repeated State_SensorTemperature temperatures = 17;
double gpu = 18;
}
message State_SensorTemperature {
string name = 1;
double temperature = 2;
}
message Task {
@@ -60,3 +70,12 @@ message TaskResult {
message Receipt{
bool proced = 1;
}
message IOStreamData {
bytes data = 1;
}
message GeoIP {
string ip = 1;
string country_code = 2;
}
+107 -1
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.3.0
// - protoc v3.21.12
// - protoc v5.26.1
// source: proto/nezha.proto
package proto
@@ -23,6 +23,8 @@ const (
NezhaService_ReportSystemInfo_FullMethodName = "/proto.NezhaService/ReportSystemInfo"
NezhaService_ReportTask_FullMethodName = "/proto.NezhaService/ReportTask"
NezhaService_RequestTask_FullMethodName = "/proto.NezhaService/RequestTask"
NezhaService_IOStream_FullMethodName = "/proto.NezhaService/IOStream"
NezhaService_LookupGeoIP_FullMethodName = "/proto.NezhaService/LookupGeoIP"
)
// NezhaServiceClient is the client API for NezhaService service.
@@ -33,6 +35,8 @@ type NezhaServiceClient interface {
ReportSystemInfo(ctx context.Context, in *Host, opts ...grpc.CallOption) (*Receipt, error)
ReportTask(ctx context.Context, in *TaskResult, opts ...grpc.CallOption) (*Receipt, error)
RequestTask(ctx context.Context, in *Host, opts ...grpc.CallOption) (NezhaService_RequestTaskClient, error)
IOStream(ctx context.Context, opts ...grpc.CallOption) (NezhaService_IOStreamClient, error)
LookupGeoIP(ctx context.Context, in *GeoIP, opts ...grpc.CallOption) (*GeoIP, error)
}
type nezhaServiceClient struct {
@@ -102,6 +106,46 @@ func (x *nezhaServiceRequestTaskClient) Recv() (*Task, error) {
return m, nil
}
func (c *nezhaServiceClient) IOStream(ctx context.Context, opts ...grpc.CallOption) (NezhaService_IOStreamClient, error) {
stream, err := c.cc.NewStream(ctx, &NezhaService_ServiceDesc.Streams[1], NezhaService_IOStream_FullMethodName, opts...)
if err != nil {
return nil, err
}
x := &nezhaServiceIOStreamClient{stream}
return x, nil
}
type NezhaService_IOStreamClient interface {
Send(*IOStreamData) error
Recv() (*IOStreamData, error)
grpc.ClientStream
}
type nezhaServiceIOStreamClient struct {
grpc.ClientStream
}
func (x *nezhaServiceIOStreamClient) Send(m *IOStreamData) error {
return x.ClientStream.SendMsg(m)
}
func (x *nezhaServiceIOStreamClient) Recv() (*IOStreamData, error) {
m := new(IOStreamData)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *nezhaServiceClient) LookupGeoIP(ctx context.Context, in *GeoIP, opts ...grpc.CallOption) (*GeoIP, error) {
out := new(GeoIP)
err := c.cc.Invoke(ctx, NezhaService_LookupGeoIP_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// NezhaServiceServer is the server API for NezhaService service.
// All implementations should embed UnimplementedNezhaServiceServer
// for forward compatibility
@@ -110,6 +154,8 @@ type NezhaServiceServer interface {
ReportSystemInfo(context.Context, *Host) (*Receipt, error)
ReportTask(context.Context, *TaskResult) (*Receipt, error)
RequestTask(*Host, NezhaService_RequestTaskServer) error
IOStream(NezhaService_IOStreamServer) error
LookupGeoIP(context.Context, *GeoIP) (*GeoIP, error)
}
// UnimplementedNezhaServiceServer should be embedded to have forward compatible implementations.
@@ -128,6 +174,12 @@ func (UnimplementedNezhaServiceServer) ReportTask(context.Context, *TaskResult)
func (UnimplementedNezhaServiceServer) RequestTask(*Host, NezhaService_RequestTaskServer) error {
return status.Errorf(codes.Unimplemented, "method RequestTask not implemented")
}
func (UnimplementedNezhaServiceServer) IOStream(NezhaService_IOStreamServer) error {
return status.Errorf(codes.Unimplemented, "method IOStream not implemented")
}
func (UnimplementedNezhaServiceServer) LookupGeoIP(context.Context, *GeoIP) (*GeoIP, error) {
return nil, status.Errorf(codes.Unimplemented, "method LookupGeoIP not implemented")
}
// UnsafeNezhaServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to NezhaServiceServer will
@@ -215,6 +267,50 @@ func (x *nezhaServiceRequestTaskServer) Send(m *Task) error {
return x.ServerStream.SendMsg(m)
}
func _NezhaService_IOStream_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(NezhaServiceServer).IOStream(&nezhaServiceIOStreamServer{stream})
}
type NezhaService_IOStreamServer interface {
Send(*IOStreamData) error
Recv() (*IOStreamData, error)
grpc.ServerStream
}
type nezhaServiceIOStreamServer struct {
grpc.ServerStream
}
func (x *nezhaServiceIOStreamServer) Send(m *IOStreamData) error {
return x.ServerStream.SendMsg(m)
}
func (x *nezhaServiceIOStreamServer) Recv() (*IOStreamData, error) {
m := new(IOStreamData)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func _NezhaService_LookupGeoIP_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GeoIP)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(NezhaServiceServer).LookupGeoIP(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: NezhaService_LookupGeoIP_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(NezhaServiceServer).LookupGeoIP(ctx, req.(*GeoIP))
}
return interceptor(ctx, in, info, handler)
}
// NezhaService_ServiceDesc is the grpc.ServiceDesc for NezhaService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@@ -234,6 +330,10 @@ var NezhaService_ServiceDesc = grpc.ServiceDesc{
MethodName: "ReportTask",
Handler: _NezhaService_ReportTask_Handler,
},
{
MethodName: "LookupGeoIP",
Handler: _NezhaService_LookupGeoIP_Handler,
},
},
Streams: []grpc.StreamDesc{
{
@@ -241,6 +341,12 @@ var NezhaService_ServiceDesc = grpc.ServiceDesc{
Handler: _NezhaService_RequestTask_Handler,
ServerStreams: true,
},
{
StreamName: "IOStream",
Handler: _NezhaService_IOStream_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "proto/nezha.proto",
}
+9
View File
@@ -628,6 +628,9 @@ other = "Enable DDNS IPv6"
[DDNSDomain]
other = "DDNS Domain"
[DDNSProfile]
other = "DDNS Profile Name"
[Feature]
other = "Feature"
@@ -637,8 +640,14 @@ other = "Template"
[Stat]
other = "Asset"
[Temperature]
other = "Temperature"
[DisableSwitchTemplateInFrontend]
other = "Disable Switch Template in Frontend"
[ServersOnWorldMap]
other = "Servers On World Map"
[NAT]
other = "NAT"
+10 -1
View File
@@ -628,6 +628,9 @@ other = "Habilitar DDNS IPv6"
[DDNSDomain]
other = "Dominio DDNS"
[DDNSProfile]
other = "Nombre del perfil de DDNS"
[Feature]
other = "Característica"
@@ -637,8 +640,14 @@ other = "Plantilla"
[Stat]
other = "Stat"
[Temperature]
other = "Temperatura"
[DisableSwitchTemplateInFrontend]
other = "Deshabilitar Cambio de Plantilla en Frontend"
[ServersOnWorldMap]
other = "Servidores en el mapa mundial"
other = "Servidores en el mapa mundial"
[NAT]
other = "NAT"
+9
View File
@@ -628,6 +628,9 @@ other = "启用DDNS IPv6"
[DDNSDomain]
other = "DDNS域名"
[DDNSProfile]
other = "DDNS配置名"
[Feature]
other = "功能"
@@ -637,8 +640,14 @@ other = "主题"
[Stat]
other = "信息"
[Temperature]
other = "温度"
[DisableSwitchTemplateInFrontend]
other = "禁止前台切换模板"
[ServersOnWorldMap]
other = "服务器世界分布图"
[NAT]
other = "内网穿透"
+9
View File
@@ -628,6 +628,9 @@ other = "啟用DDNS IPv6"
[DDNSDomain]
other = "DDNS網域"
[DDNSProfile]
other = "DDNS設定名"
[Feature]
other = "功能"
@@ -637,8 +640,14 @@ other = "主題"
[Stat]
other = "信息"
[Temperature]
other = "溫度"
[DisableSwitchTemplateInFrontend]
other = "禁止前台切換主題"
[ServersOnWorldMap]
other = "伺服器世界分布圖"
[NAT]
other = "NAT"
+24
View File
@@ -91,6 +91,7 @@ function showFormModal(modelSelector, formID, URL, getData) {
item.name.endsWith("_id") ||
item.name === "id" ||
item.name === "ID" ||
item.name === "ServerID" ||
item.name === "RequestType" ||
item.name === "RequestMethod" ||
item.name === "TriggerMode" ||
@@ -255,6 +256,28 @@ function addOrEditNotification(notification) {
);
}
function addOrEditNAT(nat) {
const modal = $(".nat.modal");
modal.children(".header").text((nat ? LANG.Edit : LANG.Add));
modal
.find(".nezha-primary-btn.button")
.html(
nat
? LANG.Edit + '<i class="edit icon"></i>'
: LANG.Add + '<i class="add icon"></i>'
);
modal.find("input[name=ID]").val(nat ? nat.ID : null);
modal.find("input[name=ServerID]").val(nat ? nat.ServerID : null);
modal.find("input[name=Name]").val(nat ? nat.Name : null);
modal.find("input[name=Host]").val(nat ? nat.Host : null);
modal.find("input[name=Domain]").val(nat ? nat.Domain : null);
showFormModal(
".nat.modal",
"#natForm",
"/api/nat"
);
}
function connectToServer(id) {
post('/terminal', { Host: window.location.host, Protocol: window.location.protocol, ID: id })
}
@@ -303,6 +326,7 @@ function addOrEditServer(server, conf) {
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=DDNSProfile]").val(server ? server.DDNSProfile : null);
modal
.find("input[name=DisplayIndex]")
.val(server ? server.DisplayIndex : null);
+4
View File
@@ -145,6 +145,10 @@ td {
z-index: 9999;
}
.temp-detail {
cursor: pointer;
}
.ui.content {
margin:0 !important;
padding:1em !important;
+1 -1
View File
@@ -6,7 +6,7 @@ const mixinsVue = {
adaptedTemplates: [
{ key: 'default', name: 'Default', icon: 'th large' },
{ key: 'angel-kanade', name: 'AngelKanade', icon: 'square' },
{ key: 'server-status', name: 'SeverStatus', icon: 'list' }
{ key: 'server-status', name: 'ServerStatus', icon: 'list' }
]
},
created() {
+74 -72
View File
@@ -1,5 +1,6 @@
body[theme="dark"] {
background: #31363b;
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
background: rgba(49, 54, 59, 1);
color: #f1f1f1;
}
@@ -7,14 +8,10 @@ body[theme="dark"] .navbar .navbar-brand {
color: #ffffff;
}
body[theme="dark"] .navbar .navbar-brand {
color: #ffffff;
}
body[theme="dark"] .navbar .dropdown-menu {
list-style-image: initial;
background-color: #171a1e;
border-color: #31363b;
background-color: rgba(23, 26, 30, 1);
border-color: rgba(49, 54, 59, 1);
box-shadow: rgba(0, 0, 0, 0.18) 0px 6px 12px;
}
@@ -24,8 +21,8 @@ body[theme="dark"] .navbar .dropdown-menu > li > a {
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%);
background-color: rgba(37, 40, 42, 1);
background-image: linear-gradient(rgba(28, 29, 38, 1) 0, rgba(28, 29, 38, 1) 100%);
}
body[theme="dark"] .navbar .navbar-nav > .open > a:focus,
@@ -37,18 +34,13 @@ body[theme="dark"] .navbar .navbar-nav > .open > a{
background-color: transparent !important;
}
body[theme="dark"] .navbar .navbar-nav .open .dropdown-menu>li>a {
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: #1c1d26;
background-color: rgba(28, 29, 38, 1);
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;
@@ -57,57 +49,10 @@ body[theme="dark"] .content {
body[theme="dark"] .table {
background-image: initial;
background-color: #1c1d26;
background-color: rgba(28, 29, 38, 1);
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: #1c1d26;
}
body[theme="dark"] .table-striped tbody > tr.odd > td,
body[theme="dark"] .table-striped tbody > tr.odd > th {
background-color: #1c1d26;
}
body[theme="dark"] .progress {
background-image: linear-gradient(#2c2c2c 0,#1c1d26 100%);
background-color: #1c1d26;
}
body[theme="dark"] .progress-bar {
color: white;
}
body[theme="dark"] .progress-bar-success {
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(#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(#942320 0, #a12623 100%);
}
body[theme="dark"] .progress-offline .progress-bar-danger {
background-image: linear-gradient(grey 0, grey 100%);
}
body[theme="dark"] .table > tbody > tr > td:before,
body[theme="dark"] .table > tfoot > tr > td:before,
body[theme="dark"] .table > thead > tr > td:before,
@@ -115,17 +60,69 @@ 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-striped tbody > tr,
body[theme="dark"] .table-striped tbody > tr.even > td,
body[theme="dark"] .table-striped tbody > tr.even > th {
background-color: rgba(28, 29, 38, 1);
}
body[theme="dark"] .table-striped tbody > tr,
body[theme="dark"] .table-striped tbody > tr.odd > td,
body[theme="dark"] .table-striped tbody > tr.odd > th {
/* background-color: rgba(28, 29, 38, 1); 282B2E*/
background-color: rgba(34, 35, 46, 1);
}
body[theme="dark"] .table-hover > tbody > tr:hover{
background-color: unset;
}
body[theme="dark"] .table-hover > tbody > tr:not(.expandRow):hover > td {
background-color: #171a1e;
background-color: rgba(48, 50, 65, 1);
}
body[theme="dark"] .table-striped > tbody > tr:nth-of-type(2n+1) {
background-color: #1c1d26;
/* expandRow展开部分样式 */
body[theme="dark"] .table > tbody > tr.expandRow.odd > td:before{
/* background-color: rgba(28, 29, 38, 1); 282B2E*/
background-color: rgba(34, 35, 46, 1);
}
body[theme="dark"] .table > tbody > tr.expandRow.even > td:before{
background-color: rgba(28, 29, 38, 1);
}
/* expandRow展开部分样式结束 */
body[theme="dark"] .progress {
background-image: linear-gradient(#2c2c2c 0,rgba(28, 29, 38, 1) 100%);
background-color: rgba(28, 29, 38, 1);
}
body[theme="dark"] .progress-bar {
color: #ffffff;
}
body[theme="dark"] .progress-bar-success {
background-image: linear-gradient(#4d853a 0, #367e36 100%);
}
body[theme="dark"] .progress-offline .progress-bar-success {
background-image: linear-gradient(#808080 0, #808080 100%);
}
body[theme="dark"] .progress-bar-warning {
background-image: linear-gradient(#995f0d 0, #b5710f 100%);
}
body[theme="dark"] .progress-offline .progress-bar-warning {
background-image: linear-gradient(#808080 0, #808080 100%);
}
body[theme="dark"] .progress-bar-danger {
background-image: linear-gradient(#942320 0, #a12623 100%);
}
body[theme="dark"] .progress-offline .progress-bar-danger {
background-image: linear-gradient(#808080 0, #808080 100%);
}
body[theme="dark"] .node-cell.status .status-icon.online {
@@ -149,7 +146,7 @@ body[theme="dark"] .service-status .warning {
}
body[theme="dark"] .service-day-status-icon {
background-color: grey;
background-color: rgba(128, 128, 128, 1);
}
body[theme="dark"] footer p a,
@@ -159,7 +156,7 @@ body[theme="dark"] footer p a:hover {
}
body[theme="dark"] .modal-content{
background-color: #1c1d26;
background-color: rgba(28, 29, 38, 1);
color: #f1f1f1;
}
@@ -170,3 +167,8 @@ body[theme="dark"] .modal-header{
body[theme="dark"] .modal-header i{
color: #f1f1f1;
}
body[theme="dark"] .toolbox i{
color: rgba(241, 241, 241, 1);
background-color: rgba(0, 0, 0, 0.5);
}
+36 -1
View File
@@ -50,6 +50,11 @@ body {
/*margin-right: 5px;*/
}
.navbar .dropdown:hover,
.navbar .dropdown a{
cursor: pointer;
}
.navbar .dropdown .dropdown-toggle {
padding-bottom: 10px;
padding-top: 10px;
@@ -59,7 +64,7 @@ body {
margin:0px -15px;
}
.navbar .navbar-nav>li>a {
.navbar .navbar-nav > li > a {
color:#f1f1f1;
}
@@ -137,6 +142,10 @@ body {
text-align: left;
}
tr.accordion-toggle{
cursor:pointer;
}
.table > tbody > tr > td,
.table > tbody > tr > th,
.table > tfoot > tr > td,
@@ -230,6 +239,23 @@ body {
max-width: 50px;
}
.temp-detail {
cursor: pointer;
}
/*加快Agent详情下拉展示速度*/
.collapsing{
-webkit-transition-property:height,visibility;
-o-transition-property:height,visibility;
transition-property:height,visibility;
-webkit-transition-duration:.15s;
-o-transition-duration:.15s;
transition-duration:.15s;
-webkit-transition-timing-function: linear;
-o-transition-timing-function: linear;
transition-timing-function: linear
}
/*正文结束*/
/* 服务页 正文*/
@@ -317,6 +343,15 @@ body {
right: 13px;
}
footer{
padding-bottom: 2rem;
}
footer p{
text-align: center;
font-size: 10px;
}
@media only screen and (max-width: 1200px) {
.accordian-body{
margin: 5px 0px 5px 10px;
+36 -51
View File
@@ -7,16 +7,17 @@ const mixinsVue = {
showGoTop: false,
preferredTemplate: null,
isMobile: false,
staticUrl: '/static/theme-server-status',
adaptedTemplates: [
{ key: 'default', name: 'Default', icon: 'th large' },
{ key: 'angel-kanade', name: 'AngelKanade', icon: 'square' },
{ key: 'server-status', name: 'SeverStatus', icon: 'list' }
{ key: 'server-status', name: 'ServerStatus', icon: 'list' }
]
},
created() {
this.isMobile = this.checkIsMobile();
this.initTheme();
this.storedShowGroup();
this.theme = this.initTheme();
this.showGroup = this.initShowGroup();
this.preferredTemplate = this.getCookie('preferred_theme') ? this.getCookie('preferred_theme') : this.$root.defaultTemplate;
window.addEventListener('scroll', this.handleScroll);
},
@@ -24,17 +25,6 @@ const mixinsVue = {
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;
@@ -42,6 +32,38 @@ const mixinsVue = {
window.location.reload();
}
},
initTheme() {
const storedTheme = localStorage.getItem("theme");
const theme = (storedTheme === 'dark' || storedTheme === 'light') ? storedTheme : (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
this.setTheme(theme);
return theme;
},
setTheme(theme) {
document.body.setAttribute("theme", theme);
this.theme = theme;
localStorage.setItem("theme", theme);
// 重新赋值全局调色
this.colors = this.theme == "dark" ? this.colorsDark : this.colorsLight;
if(this.$root.page == 'index') {
this.reloadCharts(); // 重新载入echarts图表
}
},
initShowGroup() {
const storedShowGroup = localStorage.getItem("showGroup");
const showGroup = storedShowGroup !== null ? JSON.parse(storedShowGroup) : false;
if (storedShowGroup === null) {
localStorage.setItem("showGroup", showGroup);
}
return showGroup;
},
toggleShowGroup() {
this.showGroup = !this.showGroup;
localStorage.setItem("showGroup", this.showGroup);
if (this.$root.page == 'service') {
this.$root.initTooltip();
}
},
updateCookie(name, value) {
document.cookie = name + "=" + value +"; path=/";
},
@@ -57,43 +79,6 @@ const mixinsVue = {
}
return cookieValue;
},
setTheme(title, store = false) {
this.theme = title;
document.body.setAttribute("theme", title);
if (store) {
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;
},
initTheme() {
const storeTheme = localStorage.getItem("theme");
if (storeTheme === 'dark' || storeTheme === 'light') {
this.setTheme(storeTheme, true);
} else {
this.isSystemTheme = true
const handleChange = (mediaQueryListEvent) => {
if (localStorage.getItem("theme")) {
return
}
if (mediaQueryListEvent.matches) {
this.setTheme('dark');
} else {
this.setTheme('light');
}
}
const mediaQueryListDark = window.matchMedia('(prefers-color-scheme: dark)');
this.setTheme(mediaQueryListDark.matches ? 'dark' : 'light');
mediaQueryListDark.addEventListener("change", handleChange);
}
},
toFixed2(f) {
return f.toFixed(2)
},
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4 -5
View File
@@ -6,17 +6,16 @@
{{.Version}}</small>
</div>
</div>
<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="https://unpkg.com/jquery@3.7.1/dist/jquery.min.js"></script>
<script src="https://unpkg.com/semantic-ui@2.4.0/dist/semantic.min.js"></script>
<script src="/static/semantic-ui-alerts.min.js"></script>
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/vue/2.6.14/vue.min.js"></script>
<script src="/static/main.js?v20240330"></script>
<script src="https://unpkg.com/vue@2.6.14/dist/vue.min.js"></script>
<script src="/static/main.js?v20240714"></script>
<script>
(function () {
updateLang({{.LANG }});
})();
</script>
</body>
</html>
{{end}}
+4 -8
View File
@@ -1,21 +1,17 @@
{{define "common/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" 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="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" />
<link rel="stylesheet" type="text/css" href="https://unpkg.com/semantic-ui@2.4.0/dist/semantic.min.css">
<link rel="stylesheet" type="text/css" href="https://unpkg.com/font-logos@0.17.0/assets/font-logos.css">
<link rel="stylesheet" type="text/css" href="/static/semantic-ui-alerts.min.css">
<link rel="stylesheet" type="text/css" href="/static/main.css?v2022042314">
<link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" />
</head>
<body>
{{end}}
{{end}}
+1
View File
@@ -9,6 +9,7 @@
<a class='item{{if eq .MatchedPath "/monitor"}} active{{end}}' href="/monitor"><i class="rss icon"></i>{{tr "Services"}}</a>
<a class='item{{if eq .MatchedPath "/cron"}} active{{end}}' href="/cron"><i class="clock icon"></i>{{tr "Task"}}</a>
<a class='item{{if eq .MatchedPath "/notification"}} active{{end}}' href="/notification"><i class="bell icon"></i>{{tr "Notification"}}</a>
<a class='item{{if eq .MatchedPath "/nat"}} active{{end}}' href="/nat"><i class="exchange icon"></i>{{tr "NAT"}}</a>
<a class='item{{if eq .MatchedPath "/setting"}} active{{end}}' href="/setting">
<i class="settings icon"></i>{{tr "Settings"}}
</a>
+31
View File
@@ -0,0 +1,31 @@
{{define "component/nat"}}
<div class="ui tiny nat modal transition hidden">
<div class="header">Add</div>
<div class="content">
<form id="natForm" class="ui form">
<input type="hidden" name="ID">
<div class="field">
<label>{{tr "Name"}}</label>
<input type="text" name="Name">
</div>
<div class="field">
<label>Agent ID</label>
<input type="number" name="ServerID" placeholder="1">
</div>
<div class="field">
<label>内网服务</label>
<input type="text" name="Host" placeholder="192.168.1.1:80(带端口)">
</div>
<div class="field">
<label>绑定域名</label>
<input type="text" name="Domain" placeholder="router.app.yourdomain.com">
</div>
</form>
</div>
<div class="actions">
<div class="ui negative button">{{tr "Cancel"}}</div>
<button class="ui positive nezha-primary-btn right labeled icon button">{{tr "Confirm"}}<i class="checkmark icon"></i>
</button>
</div>
</div>
{{end}}
+5 -1
View File
@@ -48,6 +48,10 @@
<label>{{tr "DDNSDomain"}}</label>
<input type="text" name="DDNSDomain" placeholder="{{tr "DDNSDomain"}}">
</div>
<div class="field">
<label>{{tr "DDNSProfile"}}</label>
<input type="text" name="DDNSProfile" placeholder="{{tr "DDNSProfile"}}">
</div>
<div class="field">
<label>{{tr "Note"}}</label>
<textarea name="Note"></textarea>
@@ -57,7 +61,7 @@
<div class="ui message">
{{if .Conf.GRPCHost}}
curl -L https://raw.githubusercontent.com/naiba/nezha/master/script/install.sh -o nezha.sh && chmod
+x nezha.sh && sudo ./nezha.sh install_agent <code class="command">{{.Conf.GRPCHost}}</code> <code
+x nezha.sh && ./nezha.sh install_agent <code class="command">{{.Conf.GRPCHost}}</code> <code
class="command">{{if .Conf.ProxyGRPCPort}}{{.Conf.ProxyGRPCPort}}{{else}}{{.Conf.GRPCPort}}{{end}}</code> <code
class="command hostSecret"></code> <code class="command">{{if .Conf.TLS}}--tls{{end}}</code>
{{else}}
+54
View File
@@ -0,0 +1,54 @@
{{define "dashboard-default/nat"}}
{{template "common/header" .}}
{{template "common/menu" .}}
<div class="nb-container">
<div class="ui container">
<div class="ui grid">
<div class="right floated right aligned twelve wide column">
<button class="ui right labeled nezha-primary-btn icon button" onclick="addOrEditNAT()"><i
class="add icon"></i> Add
</button>
</div>
</div>
<table class="ui very basic table">
<thead>
<tr>
<th>ID</th>
<th>{{tr "Name"}}</th>
<th>Agent ID</th>
<th>内网服务</th>
<th>绑定域名</th>
<th>{{tr "Administration"}}</th>
</tr>
</thead>
<tbody>
{{range $item := .NAT}}
<tr>
<td>{{$item.ID}}</td>
<td>{{$item.Name}}</td>
<td>{{$item.ServerID}}</td>
<td>{{$item.Host}}</td>
<td>{{$item.Domain}}</td>
<td>
<div class="ui mini icon buttons">
<button class="ui button" onclick="addOrEditNAT({{$item}})">
<i class="edit icon"></i>
</button>
<button class="ui button"
onclick="showConfirm('确定删除NAT隧道?','确认删除',deleteRequest,'/api/nat/'+{{$item.ID}})">
<i class="trash alternate outline icon"></i>
</button>
</div>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
{{template "component/nat"}}
{{template "common/footer" .}}
<script>
$('.checkbox').checkbox()
</script>
{{end}}
-3
View File
@@ -1,18 +1,15 @@
{{define "dashboard-default/redirect"}}
<!DOCTYPE html>
<html lang="{{.Conf.Language}}">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Redirecting..</title>
</head>
<body>
<p>If you are not redirected, please click <a href="{{.URL}}">here</a>.</p>
<script>window.location.href = "{{.URL}}"</script>
</body>
</html>
{{end}}
+7 -2
View File
@@ -60,7 +60,7 @@
</td>
<td>
<button class="ui icon green mini button"
data-clipboard-text="{{if $.Conf.GRPCHost}}{{if eq $.Conf.Language "zh-CN"}}curl -L https://raw.githubusercontent.com/naiba/nezha/master/script/install.sh{{else}}curl -L https://raw.githubusercontent.com/naiba/nezha/master/script/install_en.sh{{end}} -o nezha.sh && chmod +x nezha.sh && sudo ./nezha.sh install_agent {{$.Conf.GRPCHost}} {{if $.Conf.ProxyGRPCPort}}{{$.Conf.ProxyGRPCPort}}{{else}}{{$.Conf.GRPCPort}}{{end}} {{$server.Secret}}{{if $.Conf.TLS}} --tls{{end}}{{else}}{{tr "NoDomainAlert"}}{{end}}"
data-clipboard-text="{{if $.Conf.GRPCHost}}{{if eq $.Conf.Language "zh-CN"}}curl -L https://raw.githubusercontent.com/naiba/nezha/master/script/install.sh{{else}}curl -L https://raw.githubusercontent.com/naiba/nezha/master/script/install_en.sh{{end}} -o nezha.sh && chmod +x nezha.sh && ./nezha.sh install_agent {{$.Conf.GRPCHost}} {{if $.Conf.ProxyGRPCPort}}{{$.Conf.ProxyGRPCPort}}{{else}}{{$.Conf.GRPCPort}}{{end}} {{$server.Secret}}{{if $.Conf.TLS}} --tls{{end}}{{else}}{{tr "NoDomainAlert"}}{{end}}"
data-tooltip="{{tr "ClickToCopy"}}">
<i class="linux icon"></i>
</button>
@@ -69,6 +69,11 @@
data-tooltip="{{tr "ClickToCopy"}}">
<i class="windows icon"></i>
</button>
<button class="ui icon green mini button"
data-clipboard-text="{{if $.Conf.GRPCHost}}curl -L https://raw.githubusercontent.com/naiba/nezha/master/script/install.command -o nezha.command && chmod +x nezha.command && sudo ./nezha.command install_agent {{$.Conf.GRPCHost}} {{if $.Conf.ProxyGRPCPort}}{{$.Conf.ProxyGRPCPort}}{{else}}{{$.Conf.GRPCPort}}{{end}} {{$server.Secret}}{{if $.Conf.TLS}} --tls{{end}}{{else}}{{tr "NoDomainAlert"}}{{end}}"
data-tooltip="{{tr "ClickToCopy"}}">
<i class="apple icon"></i>
</button>
</td>
<td style="word-break: break-word;white-space: pre-wrap;">{{$server.Note}}</td>
<td>
@@ -93,7 +98,7 @@
</div>
{{template "component/server" .}}
{{template "common/footer" .}}
<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 src="https://unpkg.com/clipboard@2.0.11/dist/clipboard.min.js"></script>
<script>
var clipboard = new ClipboardJS('.ui.icon.green.mini.button');
const checkBoxList = document.querySelectorAll('tbody > tr > td > input.nezha-servers[type=checkbox]')
+4 -8
View File
@@ -1,16 +1,14 @@
{{define "dashboard-default/terminal"}}
<!DOCTYPE html>
<html lang="{{.Conf.Language}}">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>tty@{{.ServerName}} - {{.Title}}</title>
<link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" />
<link href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/xterm/4.11.0/xterm.css" type="text/css" rel="stylesheet"/>
<link type="text/css" rel="stylesheet" href="https://unpkg.com/[email protected]/css/xterm.css" />
</head>
<style>
html,
body,
@@ -25,12 +23,11 @@
background-color: black;
}
</style>
<body onresize="onResize()">
<div id="terminal-container"></div>
<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 src="https://unpkg.com/[email protected]/lib/xterm.js"></script>
<script src="https://unpkg.com/@xterm/addon-attach@0.11.0/lib/addon-attach.js"></script>
<script src="https://unpkg.com/@xterm/addon-fit@0.10.0/lib/addon-fit.js"></script>
<script>
let sendResizing = false;
@@ -93,6 +90,5 @@
}
</script>
</body>
</html>
{{end}}
+3
View File
@@ -24,6 +24,9 @@
[<span
v-if="server.Host.Virtualization">@#server.Host.Virtualization#@:</span>@#server.Host.Arch#@]<br />
CPU: @#server.Host.CPU#@<br />
<template v-if="server.Host.GPU">
GPU: @#server.Host.GPU#@
</template>
{{tr "DiskUsed"}}:
@#formatByteSize(server.State.DiskUsed)#@/@#formatByteSize(server.Host.DiskTotal)#@<br />
{{tr "MemUsed"}}:
+3
View File
@@ -24,6 +24,9 @@
<i :class="item.icon + ' icon'"></i>@#item.name#@
<i class="check icon" v-if="preferredTemplate === item.key"></i>
</a>
<a v-if="defaultTemplate === 'custom'" @click="toggleTemplate('custom')" class="item">
<i class="expand icon"></i>Custom
</a>
</div>
</div>
{{ end }}
+12 -11
View File
@@ -7,19 +7,20 @@
<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>
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/semantic.min.css">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/assets/font-logos.css">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/css/flag-icons.min.css">
<link rel="stylesheet" type="text/css" href="/static/semantic-ui-alerts.min.css">
<link rel="stylesheet" type="text/css" href="/static/theme-default/css/main.css?v20240616">
<script src="https://unpkg.com/[email protected]/dist/jquery.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/semantic.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/echarts.min.js"></script>
<script src="/static/semantic-ui-alerts.min.js"></script>
<script src="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}}
{{end}}
File diff suppressed because it is too large Load Diff
+3
View File
@@ -27,6 +27,9 @@
<i :class="item.icon + ' icon'"></i>@#item.name#@
<i class="check icon" v-if="preferredTemplate === item.key"></i>
</a>
<a v-if="defaultTemplate === 'custom'" @click="toggleTemplate('custom')" class="item">
<i class="expand icon"></i>Custom
</a>
</div>
</div>
{{ end }}
+1 -1
View File
@@ -221,7 +221,7 @@
var disk = this.formatByteSize(server.State.DiskUsed) + '/' + this.formatByteSize(server.Host.DiskTotal);
var upTime = this.secondToDate(server.State.Uptime);
var tooltip =
`{content: 'System: ${server.Host.Platform}-${server.Host.PlatformVersion}[${server.Host.Arch}]<br>CPU: ${server.Host.CPU}<br>Disk: ${disk}<br>Online: ${upTime}<br>Version: ${server.Host.Version}'}`;
`{content: 'System: ${server.Host.Platform}-${server.Host.PlatformVersion}[${server.Host.Arch}]<br>CPU: ${server.Host.CPU}<br>GPU: ${server.Host.GPU}<br>Disk: ${disk}<br>Online: ${upTime}<br>Version: ${server.Host.Version}'}`;
return tooltip
}
}
+6 -8
View File
@@ -1,20 +1,18 @@
{{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 ServerStatus | Powered by <a target="_blank" href="https://github.com/naiba/nezha">{{tr "NezhaMonitoring"}}</a> {{.Version}}
</p>
<footer>
<p>{{ .Conf.Site.Brand }} | Theme ServerStatus | Powered by <a target="_blank" href="https://github.com/naiba/nezha">{{tr "NezhaMonitoring"}}</a> {{.Version}}</p>
</footer>
<aside class="toolbox">
<span v-if="page=='index' && countryMapChartData.length!=0" class="showMapChart">
<i @click="showMapChart" data-toggle="modal" data-target="#mapChartBox" class="bi bi-geo-alt"></i>
</span>
<span class="toggleView">
<i v-if="showGroup" @click="toggleView" class="show-nogroup bi bi-justify"></i>
<i v-else @click="toggleView" class="show-group bi bi-view-stacked"></i>
<i v-if="showGroup" @click="toggleShowGroup" class="show-nogroup bi bi-justify"></i>
<i v-else @click="toggleShowGroup" class="show-group bi bi-view-stacked"></i>
</span>
<span class="setTheme">
<i v-if="theme === 'light'" @click="setTheme('dark', 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>
<i v-if="theme === 'light'" @click="setTheme('dark')" class="setTheme-dark bi bi-moon-fill"></i>
<i v-else @click="setTheme('light')" class="setTheme-light bi bi-brightness-high-fill"></i>
</span>
<span v-if="showGoTop" class="showGoTop">
<i @click="goTop" class="goTop bi bi-arrow-up"></i>
@@ -39,6 +39,11 @@
<i class="check icon" v-if="preferredTemplate === item.key"></i>
</a>
</li>
<li v-if="defaultTemplate === 'custom'">
<a @click="toggleTemplate('custom')" class="item">
<i class="expand icon" style="font-size:1em"></i>Custom
</a>
</li>
</ul>
</li>
{{ end }}
+21 -15
View File
@@ -6,16 +6,21 @@
<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="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?v20240407">
<link rel="stylesheet" href="/static/theme-server-status/css/dark.css?v20240407">
<link rel="stylesheet" href="/static/theme-server-status/css/light.css?v20240407">
<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" />
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="/static/theme-server-status/css/main.css?v20240724">
<link rel="stylesheet" href="/static/theme-server-status/css/dark.css?v20240724">
<link rel="stylesheet" href="/static/theme-server-status/css/light.css?v20240407">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/assets/font-logos.css">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/css/flag-icons.min.css">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/semantic.min.css">
<script src="https://unpkg.com/[email protected]/dist/jquery.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/js/bootstrap.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/echarts.min.js"></script>
<script src="/static/theme-server-status/js/mixin.js?v20240711"></script>
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="/static/theme-server-status/js/html5shiv.js"></script>
@@ -24,12 +29,13 @@
{{if ts .CustomCode}}
{{.CustomCode|safe}}
{{end}}
<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="https://cdn.jsdelivr.net/npm/echarts/map/js/world.js"></script>
<script src="/static/theme-server-status/js/mixin.js?v20240302"></script>
</head>
<body>
{{end}}
+29 -8
View File
@@ -27,10 +27,10 @@
</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-if='isWindowsPlatform(node.host.Platform)' class="windows icon"></i>
<i v-else-if='getFontLogoClass(node.host.Platform) == "" && node.stateuptime > 0' class="fl-tux"></i>
<i v-else :class="'fl-' + getFontLogoClass(node.host.Platform)"></i>
<span class="node-cell-os-text">@#node.os#@</span>
<span class="node-cell-os-text">@#getPlatformName(node.os)#@</span>
</td>
<td style="text-align: center;" class="node-cell location">
<i :class="'fi fi-' + node.location"></i>
@@ -71,20 +71,28 @@
</span>
<span class="node-cell-expand" v-if="node.host.CPU">
<span class="node-cell-expand-label">CPU:</span>
@#node.host.CPU.join(",")#@
@#node.host.CPU.join(",")#@ (@#node.cpu.percent#@%)
</span>
<span class="node-cell-expand" v-if="node.host.GPU">
<span class="node-cell-expand-label">GPU:</span>
@#node.host.GPU.join(",")#@
(@#parseInt(node.state.GPU >=0 ? node.state.GPU : 0)#@%)
</span>
<span class="node-cell-expand">
<span class="node-cell-expand-label">{{tr "DiskUsed"}}:</span>
@#formatByteSize(node.state.DiskUsed)#@ / @#formatByteSize(node.host.DiskTotal)#@
@#formatByteSize(node.state.DiskUsed)#@ / @#formatByteSize(node.host.DiskTotal)#@ (@#node.hdd.percent#@%)
</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)#@%)
@#formatByteSize(node.state.MemUsed)#@ / @#formatByteSize(node.host.MemTotal)#@ (@#node.memory.percent#@%)
</span>
<span class="node-cell-expand">
<span v-if="node.host.SwapTotal > 0" 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>(@#toFixed2(node.state.SwapUsed / node.host.SwapTotal * 100)#@%)</span>
</span>
<span v-else class="node-cell-expand">
<span class="node-cell-expand-label">{{tr "SwapUsed"}}: OFF</span>
</span>
<span class="node-cell-expand">
<span class="node-cell-expand-label">{{tr "NetTransfer"}}:</span>
@@ -117,6 +125,19 @@
<span class="node-cell-expand-label">{{tr "Uptime"}}:</span>
@#node.uptime#@
</span>
<span v-if="node.state.Temperatures && getTemperature(node.state.Temperatures, sensorList) > 0" @click="toggleDetailedTemp(node.ID)" class="node-cell-expand temp-detail" :id="`temp-${node.ID}`" temp-detail-show="0">
<span class="node-cell-expand-label">{{tr "Temperature"}}:</span>
<span :id="`temp-main-${node.ID}`" class="node-cell-expand-label">
@#getTemperature(node.state.Temperatures, sensorList)#@°C
<i class="bi bi-chevron-double-down"></i>
</span>
<span :id="`temp-detail-${node.ID}`" class="node-cell-expand-label" style="display:none">
<span v-for="temp in node.state.Temperatures" :key="temp.Name" v-if="temp.Temperature !== 0">
[ @#temp.Name#@ : @#temp.Temperature#@°C ]
</span>
<i class="bi bi-chevron-double-up"></i>
</span>
</span>
<span class="node-cell-expand">
<span class="node-cell-expand-label">{{tr "Version"}}:</span>
@#node.host.Version#@
+29 -8
View File
@@ -30,10 +30,10 @@
</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-if='isWindowsPlatform(node.host.Platform)' class="windows icon"></i>
<i v-else-if='getFontLogoClass(node.host.Platform) == "" && node.stateuptime > 0' class="fl-tux"></i>
<i v-else :class="'fl-' + getFontLogoClass(node.host.Platform)"></i>
<span class="node-cell-os-text">@#node.os#@</span>
<span class="node-cell-os-text">@#getPlatformName(node.os)#@</span>
</td>
<td style="text-align: center;" class="node-cell location">
<i :class="'fi fi-' + node.location"></i>
@@ -74,20 +74,28 @@
</span>
<span class="node-cell-expand" v-if="node.host.CPU">
<span class="node-cell-expand-label">CPU:</span>
@#node.host.CPU.join(",")#@
@#node.host.CPU.join(",")#@ (@#node.cpu.percent#@%)
</span>
<span class="node-cell-expand" v-if="node.host.GPU">
<span class="node-cell-expand-label">GPU:</span>
@#node.host.GPU.join(",")#@
(@#parseInt(node.state.GPU >=0 ? node.state.GPU : 0)#@%)
</span>
<span class="node-cell-expand">
<span class="node-cell-expand-label">{{tr "DiskUsed"}}:</span>
@#formatByteSize(node.state.DiskUsed)#@ / @#formatByteSize(node.host.DiskTotal)#@
@#formatByteSize(node.state.DiskUsed)#@ / @#formatByteSize(node.host.DiskTotal)#@ (@#node.hdd.percent#@%)
</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)#@%)
@#formatByteSize(node.state.MemUsed)#@ / @#formatByteSize(node.host.MemTotal)#@ (@#node.memory.percent#@%)
</span>
<span class="node-cell-expand">
<span v-if="node.host.SwapTotal > 0" 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>(@#toFixed2(node.state.SwapUsed / node.host.SwapTotal * 100)#@%)</span>
</span>
<span v-else class="node-cell-expand">
<span class="node-cell-expand-label">{{tr "SwapUsed"}}: OFF</span>
</span>
<span class="node-cell-expand">
<span class="node-cell-expand-label">{{tr "NetTransfer"}}:</span>
@@ -120,6 +128,19 @@
<span class="node-cell-expand-label">{{tr "Uptime"}}:</span>
@#node.uptime#@
</span>
<span v-if="node.state.Temperatures && getTemperature(node.state.Temperatures, sensorList) > 0" @click="toggleDetailedTemp(node.ID)" class="node-cell-expand temp-detail" :id="`temp-${node.ID}`" temp-detail-show="0">
<span class="node-cell-expand-label">{{tr "Temperature"}}:</span>
<span :id="`temp-main-${node.ID}`" class="node-cell-expand-label">
@#getTemperature(node.state.Temperatures, sensorList)#@°C
<i class="bi bi-chevron-double-down"></i>
</span>
<span :id="`temp-detail-${node.ID}`" class="node-cell-expand-label" style="display:none">
<span v-for="temp in node.state.Temperatures" :key="temp.Name" v-if="temp.Temperature !== 0">
[ @#temp.Name#@ : @#temp.Temperature#@°C ]
</span>
<i class="bi bi-chevron-double-up"></i>
</span>
</span>
<span class="node-cell-expand">
<span class="node-cell-expand-label">{{tr "Version"}}:</span>
@#node.host.Version#@
File diff suppressed because one or more lines are too long
+118 -99
View File
@@ -4,74 +4,74 @@
{{template "theme-server-status/content-nav" .}}
<!-- 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">
<section v-if="servicesTag.length === 0" class="container content" style="max-width: 95vw; min-height: .01%;overflow-x: auto;">
<p>No Valid Service Monitor Configuration Entries Found. Please Verify in the <a href="/monitor">Admin Panel</a>.</p>
</section>
<section v-else class="container content" style="max-width: 95vw; min-height: .01%;overflow-x: auto;" v-for="group in servicesTag">
{{template "theme-server-status/service-group-true" .}}
</section>
</template>
<!-- showGroup false -->
<template v-else>
<section class="container content" style="max-width: 95vw; min-height: .01%;overflow-x: auto;">
<section v-if="servicesNoTag.length === 0" class="container content" style="max-width: 95vw; min-height: .01%;overflow-x: auto;">
<p>No Valid Service Monitor Configuration Entries Found. Please Verify in the <a href="/monitor">Admin Panel</a>.</p>
</section>
<section v-else class="container content" style="max-width: 95vw; min-height: .01%;overflow-x: auto;">
{{template "theme-server-status/service-group-false" .}}
</section>
</template>
<section class="container content table-responsive" style="max-width: 95vw">
{{if .CycleTransferStats}}
<table class="table table-striped table-condensed table-hover">
<thead>
<tr class="node-group-tag">
<th colspan="16" style="border:none;">
{{tr "CycleTransferStats"}}
</th>
</tr>
<tr class="node-group-cell">
<th class="node-cell center">ID</th>
<th class="node-cell center">{{tr "Rules"}}</th>
<th class="node-cell center">{{tr "Server"}}</th>
<th class="node-cell center">{{tr "From"}}</th>
<th class="node-cell center">{{tr "To"}}</th>
<th class="node-cell center">MAX</th>
<th class="node-cell center">MIN</th>
<th class="node-cell center">{{tr "NextCheck"}}</th>
<th class="node-cell center">{{tr "CurrentUsage"}}</th>
<th class="node-cell center">{{tr "Transleft"}}</th>
</tr>
</thead>
<tbody>
{{range $id, $stats := .CycleTransferStats}}
{{range $innerId, $transfer := $stats.Transfer}}
{{$TransLeftPercent := TransLeftPercent (UintToFloat $transfer) (UintToFloat $stats.Max)}}
<tr>
<td class="node-cell center">{{$id}}</td>
<td class="node-cell center">{{$stats.Name}}</td>
<td class="node-cell center">{{index $stats.ServerName $innerId}}</td>
<td class="node-cell center">{{$stats.From|tf}}</td>
<td class="node-cell center">{{$stats.To|tf}}</td>
<td class="node-cell center">{{$stats.Max|bf}}</td>
<td class="node-cell center">{{$stats.Min|bf}}</td>
<td class="node-cell center">{{(index $stats.NextUpdate $innerId)|sft}}</td>
<td class="node-cell center">{{$transfer|bf}}</td>
<td class="node-cell center">
<div class="progress">
<div style="width: {{$TransLeftPercent}}%" :class="'progress-bar progress-bar-' + toSSBar('{{TransClassName $TransLeftPercent}}')">
<small style="display: inline-block;width: max-content;">{{TransLeft $stats.Max $transfer}} / {{$TransLeftPercent}} %</small>
</div>
</div>
</td>
</tr>
{{end}}
{{end}}
</tbody>
</table>
{{end}}
</section>
{{if .CycleTransferStats}}
<section class="container content table-responsive" style="max-width: 95vw">
<table class="table table-striped table-condensed table-hover">
<thead>
<tr class="node-group-tag">
<th colspan="16" style="border:none;">
{{tr "CycleTransferStats"}}
</th>
</tr>
<tr class="node-group-cell">
<th class="node-cell center">ID</th>
<th class="node-cell center">{{tr "Rules"}}</th>
<th class="node-cell center">{{tr "Server"}}</th>
<th class="node-cell center">{{tr "From"}}</th>
<th class="node-cell center">{{tr "To"}}</th>
<th class="node-cell center">MAX</th>
<th class="node-cell center">MIN</th>
<th class="node-cell center">{{tr "NextCheck"}}</th>
<th class="node-cell center">{{tr "CurrentUsage"}}</th>
<th class="node-cell center">{{tr "Transleft"}}</th>
</tr>
</thead>
<tbody>
{{range $id, $stats := .CycleTransferStats}}
{{range $innerId, $transfer := $stats.Transfer}}
{{$TransLeftPercent := TransLeftPercent (UintToFloat $transfer) (UintToFloat $stats.Max)}}
<tr>
<td class="node-cell center">{{$id}}</td>
<td class="node-cell center">{{$stats.Name}}</td>
<td class="node-cell center">{{index $stats.ServerName $innerId}}</td>
<td class="node-cell center">{{$stats.From|tf}}</td>
<td class="node-cell center">{{$stats.To|tf}}</td>
<td class="node-cell center">{{$stats.Max|bf}}</td>
<td class="node-cell center">{{$stats.Min|bf}}</td>
<td class="node-cell center">{{(index $stats.NextUpdate $innerId)|sft}}</td>
<td class="node-cell center">{{$transfer|bf}}</td>
<td class="node-cell center">
<div class="progress">
<div style="width: {{$TransLeftPercent}}%" :class="'progress-bar progress-bar-' + toSSBar('{{TransClassName $TransLeftPercent}}')">
<small style="display: inline-block;width: max-content;">{{TransLeft $stats.Max $transfer}} / {{$TransLeftPercent}} %</small>
</div>
</div>
</td>
</tr>
{{end}}
{{end}}
</tbody>
</table>
</section>
{{end}}
{{template "theme-server-status/content-footer" .}}
</div>
<script>
// 初始化 Tooltip
$(document).ready(function(){
$('[data-toggle="tooltip"]').tooltip();
});
</script>
<script>
new Vue({
el: '#app',
@@ -80,13 +80,16 @@
page: 'service',
defaultTemplate: {{.Conf.Site.Theme}},
templates: {{.Themes}},
services: {{.Services}},
servicesTag: [],
servicesNoTag: [],
servicesNoTag: []
},
created() {
this.initData();
this.servicesTag = this.groupingData(this.initData(this.services),"type");
this.servicesNoTag = this.initData(this.services);
},
mounted() {
this.initTooltip();
},
mixins: [mixinsVue],
methods: {
@@ -103,32 +106,36 @@
}
return n
},
initData() {
// @formatter:off
const services = []
{{range $service := .Services}}
services.push({
type: '{{$service.Monitor.Type}}',
name: '{{$service.Monitor.Name}}',
currentUp: parseInt('{{$service.CurrentUp}}'),
currentDown: parseInt('{{$service.CurrentDown}}'),
totalUp: parseInt('{{$service.TotalUp}}'),
totalDown: parseInt('{{$service.TotalDown}}'),
delay: '{{$service.Delay}}'.replaceAll("[","").replaceAll("]","").split(" "),
up: '{{$service.Up}}'.replaceAll("[","").replaceAll("]","").split(" "),
down: '{{$service.Down}}'.replaceAll("[","").replaceAll("]","").split(" "),
})
{{end}}
// @formatter:on
for (let i = 0; i < services.length; i++) {
const service = services[i];
service.avgDelay = parseInt(service.delay[service.delay.length - 1]) + "ms"
service.health = this.getStateInfo(this.getPercent(service.currentUp, service.currentDown))
service.dayDetail = this.getDayTails(service)
service.totalUpTime = this.getProgressInfo(this.getPercent(service.totalUp, service.totalDown))
initData(services) {
let nodes = [];
for (let key in services) {
const service = services[key];
let node = {
type: service.Monitor.Type,
name: service.Monitor.Name,
currentUp: parseInt(service.CurrentUp),
currentDown: parseInt(service.CurrentDown),
totalUp: parseInt(service.TotalUp),
totalDown: parseInt(service.TotalDown),
up: service.Up,
down: service.Down,
delay: service.Delay,
avgDelay: parseInt(this.getAvgDelay(service.Delay)) + "ms",
health: this.getStateInfo(this.getPercent(service.CurrentUp, service.CurrentDown)),
totalUpTime: this.getProgressInfo(this.getPercent(service.TotalUp, service.TotalDown))
};
nodes.push(node);
};
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
node.dayDetail = this.getDayTails(node)
}
this.servicesTag = this.groupingData(services,"type");
this.servicesNoTag = services;
return nodes
},
initTooltip() {
$(document).ready(function(){
$('[data-toggle="tooltip"]').tooltip();
});
},
getPercent(up, down) {
if (!up) {
@@ -151,24 +158,24 @@
return this.toFixed2(currentUp / total * 100)
},
getDayTails(service) {
const result = []
const result = [];
for (let i = 0; i < service.up.length; i++) {
const up = service.up[i]
const down = service.down[i]
const delay = service.delay[i]
let percent = this.getPercent(up, down)
const up = service.up[i];
const down = service.down[i];
let delay = Number(service.delay[i]);
let percent = this.getPercent(up, down);
if (percent <= 0) {
percent = 0;
}
let className = this.getStateInfo(percent).className
let available = '{{tr "Availability"}}'
let averageLatency = '{{tr "AverageLatency"}}'
const text = `${this.beforeDay(service.up.length - i - 1)}${available}${percent}%${averageLatency}${delay}ms`
let className = this.getStateInfo(percent).className;
let available = '{{tr "Availability"}}';
let averageLatency = '{{tr "AverageLatency"}}';
const text = `${this.beforeDay(service.up.length - i - 1)}${available}${Number(percent).toFixed(3)}%${averageLatency}${Number(delay).toFixed(3)}ms`;
result.push({
text, className
})
});
}
return result
return result;
},
beforeDay(days) {
const today = new Date();
@@ -203,12 +210,13 @@
return result;
},
getProgressInfo(percent) {
const result = this.getStateInfo(percent)
const result = this.getStateInfo(percent);
result.style = `width: ${parseInt(percent)}%`;
result.percent = Number(percent).toFixed(2);
const className = result.className;
if (className === "good") {
result.className = 'progress-bar progress-bar-success'
} else if (className === "waining") {
} else if (className === "warning") {
result.className = 'progress-bar progress-bar-warning'
} else if (className === "danger") {
result.className = 'progress-bar progress-bar-danger'
@@ -218,8 +226,19 @@
}
return result
},
getAvgDelay(array) {
const { sum, count } = array.reduce((acc, value) => {
if (value !== 0) {
acc.sum += value;
acc.count += 1;
}
return acc;
}, { sum: 0, count: 0 });
return count > 0 ? sum / count : 0;
}
}
})
</script>
{{template "theme-server-status/footer" .}}
{{end}}
{{end}}
+10 -1
View File
@@ -14,7 +14,7 @@ site:
theme: "default"
ddns:
enable: false
provider: "webhook"
provider: "webhook" # 如需使用多配置功能,请把此项留空
accessid: ""
accesssecret: ""
webhookmethod: ""
@@ -22,3 +22,12 @@ ddns:
webhookrequestbody: ""
webhookheaders: ""
maxretries: 3
profiles:
example:
provider: ""
accessid: ""
accesssecret: ""
webhookmethod: ""
webhookurl: ""
webhookrequestbody: ""
webhookheaders: ""
+293
View File
@@ -0,0 +1,293 @@
#!/bin/bash
#========================================================
# System Required: macOS 10.13+
# Description: Nezha Agent Install Script (macOS)
# Github: https://github.com/naiba/nezha
#========================================================
NZ_BASE_PATH="/opt/nezha"
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
red='\033[0;31m'
green='\033[0;32m'
yellow='\033[0;33m'
plain='\033[0m'
export PATH=$PATH:/usr/local/bin
pre_check() {
# check root
[[ $EUID -ne 0 ]] && echo -e "${red}ERROR: ${plain} This script must be run with the root user!\n" && exit 1
## os_arch
if [[ $(uname -m | grep 'x86_64') != "" ]]; then
os_arch="amd64"
elif [[ $(uname -m | grep 'arm64\|arm64e') != "" ]]; then
os_arch="arm64"
fi
## China_IP
if [[ -z "${CN}" ]]; then
if [[ $(curl -m 10 -s http://ip-api.com/json |grep 'country' |grep -q 'China') != "" ]]; then
echo "According to the information provided by ip-api.com, the current IP may be in China"
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"
CN=true
;;
[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"
;;
esac
fi
fi
if [[ -n "${CUSTOM_MIRROR}" ]]; then
GITHUB_RAW_URL="gitee.com/naibahq/nezha/raw/master"
GITHUB_URL=$CUSTOM_MIRROR
else
if [[ -z "${CN}" ]]; then
GITHUB_RAW_URL="raw.githubusercontent.com/naiba/nezha/master"
GITHUB_URL="github.com"
else
GITHUB_RAW_URL="gitee.com/naibahq/nezha/raw/master"
GITHUB_URL="gitee.com"
fi
fi
}
before_show_menu() {
echo && echo -n -e "${yellow}* Press Enter to return to the main menu *${plain}" && read temp
show_menu
}
install_agent() {
echo -e "> Install Nezha Agent"
echo -e "Obtaining Agent version"
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://gitee.com/api/v5/repos/naibahq/agent/releases/latest" | awk -F '"' '{for(i=1;i<=NF;i++){if($i=="tag_name"){print $(i+2)}}}')
fi
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 "Fail to obtaine agent version, please check if the network can link https://api.github.com/repos/nezhahq/agent/releases/latest"
return 0
else
echo -e "The current latest version is: ${version}"
fi
# Nezha Agent Folder
mkdir -p $NZ_AGENT_PATH
chmod -R 777 $NZ_AGENT_PATH
echo -e "Downloading Agent"
if [[ -z $CN ]]; then
NZ_AGENT_URL="https://${GITHUB_URL}/nezhahq/agent/releases/download/${version}/nezha-agent_darwin_${os_arch}.zip"
else
NZ_AGENT_URL="https://${GITHUB_URL}/naibahq/agent/releases/download/${version}/nezha-agent_darwin_${os_arch}.zip"
fi
curl -o nezha-agent_darwin_${os_arch}.zip -L -f --retry 2 --retry-max-time 60 $NZ_AGENT_URL >/dev/null 2>&1
if [[ $? != 0 ]]; then
echo -e "${red}Fail to download agent, please check if the network can link ${GITHUB_URL}${plain}"
return 0
fi
unzip -qo nezha-agent_darwin_${os_arch}.zip &&
mv nezha-agent $NZ_AGENT_PATH &&
rm -rf nezha-agent_darwin_${os_arch}.zip README.md
if [ $# -ge 3 ]; then
modify_agent_config "$@"
else
modify_agent_config 0
fi
if [[ $# == 0 ]]; then
before_show_menu
fi
}
modify_agent_config() {
echo -e "> Modify Agent Configuration"
if [ $# -lt 3 ]; then
echo "Please add Agent in the admin panel first, record the secret" &&
read -ep "Please enter a domain that resolves to the IP where the panel is located (no CDN sets): " nz_grpc_host &&
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'
if [[ -z "${nz_grpc_host}" || -z "${nz_client_secret}" ]]; then
echo -e "${red}All options cannot be empty${plain}"
before_show_menu
return 1
fi
if [[ -z "${nz_grpc_port}" ]]; then
nz_grpc_port=5555
fi
else
nz_grpc_host=$1
nz_grpc_port=$2
nz_client_secret=$3
shift 3
if [ $# -gt 0 ]; then
args=" $*"
fi
fi
${NZ_AGENT_PATH}/nezha-agent service install -s "$nz_grpc_host:$nz_grpc_port" -p $nz_client_secret $args >/dev/null 2>&1
if [ $? -ne 0 ]; then
${NZ_AGENT_PATH}/nezha-agent service uninstall >/dev/null 2>&1
${NZ_AGENT_PATH}/nezha-agent service install -s "$nz_grpc_host:$nz_grpc_port" -p $nz_client_secret $args >/dev/null 2>&1
fi
echo -e "Agent configuration ${green} modified successfully, please wait for agent self-restart to take effect${plain}"
#if [[ $# == 0 ]]; then
# before_show_menu
#fi
}
show_agent_log() {
echo -e "> > View Agent Log"
tail -n 10 /var/log/nezha-agent.err.log
if [[ $# == 0 ]]; then
before_show_menu
fi
}
uninstall_agent() {
echo -e "> Uninstall Agent"
${NZ_AGENT_PATH}/nezha-agent service uninstall
rm -rf $NZ_AGENT_PATH
clean_all
if [[ $# == 0 ]]; then
before_show_menu
fi
}
restart_agent() {
echo -e "> Restart Agent"
${NZ_AGENT_PATH}/nezha-agent service restart
if [[ $# == 0 ]]; then
before_show_menu
fi
}
clean_all() {
if [ -z "$(ls -A ${NZ_BASE_PATH})" ]; then
rm -rf ${NZ_BASE_PATH}
fi
}
show_usage() {
echo "Nezha Agent Management Script Usage: "
echo "--------------------------------------------------------"
echo "./nezha.sh install_agent - Install Agent"
echo "./nezha.sh modify_agent_config - Modify Agent Configuration"
echo "./nezha.sh show_agent_log - View Agent Log"
echo "./nezha.sh uninstall_agent - Uninstall Agent"
echo "./nezha.sh restart_agent - Restart Agent"
echo "./nezha.sh update_script - Update Script"
echo "--------------------------------------------------------"
}
show_menu() {
echo -e "
${green}Nezha Agent Management Script${plain} ${red}macOS${plain}
--- https://github.com/naiba/nezha ---
${green}1.${plain} Install Agent
${green}2.${plain} Modify Agent Configuration
${green}3.${plain} View Agent Log
${green}4.${plain} Uninstall Agent
${green}5.${plain} Restart Agent
————————————————-
${green}0.${plain} Exit Script
"
echo && read -ep "Please enter [0-5]: " num
case "${num}" in
0)
exit 0
;;
1)
install_agent
;;
2)
modify_agent_config
;;
3)
show_agent_log
;;
4)
uninstall_agent
;;
5)
restart_agent
;;
*)
echo -e "${red}Please enter the correct number [0-5]${plain}"
;;
esac
}
pre_check
if [[ $# > 0 ]]; then
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
;;
*) show_usage ;;
esac
else
show_menu
fi
+44 -27
View File
@@ -7,64 +7,81 @@ if($PSVersionTable.PSVersion.Major -lt 5){
exit
}
$agentrepo = "nezhahq/agent"
$nssmrepo = "nezhahq/nssm-backup"
# x86 or x64
# x86 or x64 or arm64
if ([System.Environment]::Is64BitOperatingSystem) {
$file = "nezha-agent_windows_amd64.zip"
if ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") {
$file = "nezha-agent_windows_arm64.zip"
} else {
$file = "nezha-agent_windows_amd64.zip"
}
}
else {
$file = "nezha-agent_windows_386.zip"
}
$agentreleases = "https://api.github.com/repos/$agentrepo/releases"
$nssmreleases = "https://api.github.com/repos/$nssmrepo/releases"
#重复运行自动更新
if (Test-Path "C:\nezha") {
if (Test-Path "C:\nezha\nezha-agent.exe") {
Write-Host "Nezha monitoring already exists, delete and reinstall" -BackgroundColor DarkGreen -ForegroundColor White
C:/nezha/nssm.exe stop nezha
C:/nezha/nssm.exe remove nezha
C:\nezha\nezha-agent.exe service uninstall
Remove-Item "C:\nezha" -Recurse
}
#TLS/SSL
Write-Host "Determining latest nezha release" -BackgroundColor DarkGreen -ForegroundColor White
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$agenttag = (Invoke-WebRequest -Uri $agentreleases -UseBasicParsing | ConvertFrom-Json)[0].tag_name
$nssmtag = (Invoke-WebRequest -Uri $nssmreleases -UseBasicParsing | ConvertFrom-Json)[0].tag_name
if ([string]::IsNullOrWhiteSpace($agenttag)) {
$optionUrl = "https://fastly.jsdelivr.net/gh/nezhahq/agent/"
Try {
$response = Invoke-WebRequest -Uri $optionUrl -UseBasicParsing -TimeoutSec 10
if ($response.StatusCode -eq 200) {
$versiontext = $response.Content | findstr /c:"option.value"
$version = [regex]::Match($versiontext, "@(\d+\.\d+\.\d+)").Groups[1].Value
$agenttag = "v" + $version
}
} Catch {
$optionUrl = "https://gcore.jsdelivr.net/gh/nezhahq/agent/"
$response = Invoke-WebRequest -Uri $optionUrl -UseBasicParsing -TimeoutSec 10
if ($response.StatusCode -eq 200) {
$versiontext = $response.Content | findstr /c:"option.value"
$version = [regex]::Match($versiontext, "@(\d+\.\d+\.\d+)").Groups[1].Value
$agenttag = "v" + $version
}
}
}
#Region判断
$ipapi= Invoke-RestMethod -Uri "https://api.myip.com/" -UserAgent "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1"
$region=$ipapi.cc
$ipapi = ""
$region = "Unknown"
foreach ($url in ("https://dash.cloudflare.com/cdn-cgi/trace","https://cf-ns.com/cdn-cgi/trace","https://1.0.0.1/cdn-cgi/trace")) {
try {
$ipapi = Invoke-RestMethod -Uri $url -TimeoutSec 5 -UseBasicParsing
if ($ipapi -match "loc=(\w+)" ) {
$region = $Matches[1]
break
}
}
catch {
Write-Host "Error occurred while querying $url : $_"
}
}
echo $ipapi
if($region -ne "CN"){
$download = "https://github.com/$agentrepo/releases/download/$agenttag/$file"
$nssmdownload="https://github.com/$nssmrepo/releases/download/$nssmtag/nssm.zip"
Write-Host "Location:$region,connect directly!" -BackgroundColor DarkRed -ForegroundColor Green
}else{
$download = "https://dn-dao-github-mirror.daocloud.io/$agentrepo/releases/download/$agenttag/$file"
$nssmdownload="https://dn-dao-github-mirror.daocloud.io/$nssmrepo/releases/download/$nssmtag/nssm.zip"
$download = "https://gitee.com/naibahq/agent/releases/download/$agenttag/$file"
Write-Host "Location:CN,use mirror address" -BackgroundColor DarkRed -ForegroundColor Green
}
echo $download
echo $nssmdownload
Invoke-WebRequest $download -OutFile "C:\nezha.zip"
#使用nssm安装服务
Invoke-WebRequest $nssmdownload -OutFile "C:\nssm.zip"
#解压
Expand-Archive "C:\nezha.zip" -DestinationPath "C:\temp" -Force
Expand-Archive "C:\nssm.zip" -DestinationPath "C:\temp" -Force
if (!(Test-Path "C:\nezha")) { New-Item -Path "C:\nezha" -type directory }
#整理文件
Move-Item -Path "C:\temp\nezha-agent.exe" -Destination "C:\nezha\nezha-agent.exe"
if ($file = "nezha-agent_windows_amd64.zip") {
Move-Item -Path "C:\temp\nssm-2.24\win64\nssm.exe" -Destination "C:\nezha\nssm.exe"
}
else {
Move-Item -Path "C:\temp\nssm-2.24\win32\nssm.exe" -Destination "C:\nezha\nssm.exe"
}
#清理垃圾
Remove-Item "C:\nezha.zip"
Remove-Item "C:\nssm.zip"
Remove-Item "C:\temp" -Recurse
#安装部分
C:\nezha\nssm.exe install nezha C:\nezha\nezha-agent.exe -s $server -p $key $tls -d
C:\nezha\nssm.exe start nezha
C:\nezha\nezha-agent.exe service install -s $server -p $key $tls
#enjoy
Write-Host "Enjoy It!" -BackgroundColor DarkGreen -ForegroundColor Red
Write-Host "Enjoy It!" -BackgroundColor DarkGreen -ForegroundColor Red
+303 -265
View File
File diff suppressed because it is too large Load Diff
+304 -268
View File
File diff suppressed because it is too large Load Diff
-19
View File
@@ -1,19 +0,0 @@
#!/sbin/openrc-run
supervisor=supervise-daemon
name=nezha-agent
output_log=/var/log/${name}.log
error_log=/var/log/${name}.err
SERVER="nz_grpc_host:nz_grpc_port" #Dashboard 地址 ip:port
SECRET="nz_client_secret" #SECRET
pidfile="/run/${RC_SVCNAME}.pid"
command="/opt/nezha/agent/nezha-agent"
command_args="-s ${SERVER} -p ${SECRET}"
command_background=true
depend() {
need net
}
start_pre() {
checkpath -f -m 0644 -o root:root "/var/log/${name}.log"
}
-30
View File
@@ -1,30 +0,0 @@
[Unit]
Description=Nezha Agent
After=syslog.target
#After=network.target
#After=nezha-dashboard.service
[Service]
# Modify these two values and uncomment them if you have
# repos with lots of files and get an HTTP error 500 because
# of that
###
#LimitMEMLOCK=infinity
#LimitNOFILE=65535
Type=simple
User=root
Group=root
WorkingDirectory=/opt/nezha/agent/
ExecStart=/opt/nezha/agent/nezha-agent -s nz_grpc_host:nz_grpc_port -p nz_client_secret
Restart=always
#Environment=DEBUG=true
# Some distributions may not support these hardening directives. If you cannot start the service due
# to an unknown option, comment out the ones not supported by your version of systemd.
#ProtectSystem=full
#PrivateDevices=yes
#PrivateTmp=yes
#NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
+3 -1
View File
@@ -1 +1,3 @@
protoc --go-grpc_out="require_unimplemented_servers=false:." --go_out="." proto/*.proto
protoc --go-grpc_out="require_unimplemented_servers=false:." --go_out="." proto/*.proto
rm -rf ../agent/proto
cp -r proto ../agent
+4 -4
View File
@@ -10,19 +10,19 @@ import (
"github.com/naiba/nezha/service/singleton"
)
type AuthHandler struct {
type authHandler struct {
ClientSecret string
}
func (a *AuthHandler) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
func (a *authHandler) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{"client_secret": a.ClientSecret}, nil
}
func (a *AuthHandler) RequireTransportSecurity() bool {
func (a *authHandler) RequireTransportSecurity() bool {
return false
}
func (a *AuthHandler) Check(ctx context.Context) (uint64, error) {
func (a *authHandler) Check(ctx context.Context) (uint64, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return 0, status.Errorf(codes.Unauthenticated, "获取 metaData 失败")
+140
View File
@@ -0,0 +1,140 @@
package rpc
import (
"errors"
"io"
"sync/atomic"
"time"
)
type ioStreamContext struct {
userIo io.ReadWriteCloser
agentIo io.ReadWriteCloser
userIoConnectCh chan struct{}
agentIoConnectCh chan struct{}
}
func (s *NezhaHandler) CreateStream(streamId string) {
s.ioStreamMutex.Lock()
defer s.ioStreamMutex.Unlock()
s.ioStreams[streamId] = &ioStreamContext{
userIoConnectCh: make(chan struct{}),
agentIoConnectCh: make(chan struct{}),
}
}
func (s *NezhaHandler) GetStream(streamId string) (*ioStreamContext, error) {
s.ioStreamMutex.RLock()
defer s.ioStreamMutex.RUnlock()
if ctx, ok := s.ioStreams[streamId]; ok {
return ctx, nil
}
return nil, errors.New("stream not found")
}
func (s *NezhaHandler) CloseStream(streamId string) error {
s.ioStreamMutex.Lock()
defer s.ioStreamMutex.Unlock()
if ctx, ok := s.ioStreams[streamId]; ok {
if ctx.userIo != nil {
ctx.userIo.Close()
}
if ctx.agentIo != nil {
ctx.agentIo.Close()
}
delete(s.ioStreams, streamId)
}
return nil
}
func (s *NezhaHandler) UserConnected(streamId string, userIo io.ReadWriteCloser) error {
stream, err := s.GetStream(streamId)
if err != nil {
return err
}
stream.userIo = userIo
close(stream.userIoConnectCh)
return nil
}
func (s *NezhaHandler) AgentConnected(streamId string, agentIo io.ReadWriteCloser) error {
stream, err := s.GetStream(streamId)
if err != nil {
return err
}
stream.agentIo = agentIo
close(stream.agentIoConnectCh)
return nil
}
func (s *NezhaHandler) StartStream(streamId string, timeout time.Duration) error {
stream, err := s.GetStream(streamId)
if err != nil {
return err
}
timeoutTimer := time.NewTimer(timeout)
LOOP:
for {
select {
case <-stream.userIoConnectCh:
if stream.agentIo != nil {
timeoutTimer.Stop()
break LOOP
}
case <-stream.agentIoConnectCh:
if stream.userIo != nil {
timeoutTimer.Stop()
break LOOP
}
case <-time.After(timeout):
break LOOP
}
time.Sleep(time.Millisecond * 500)
}
if stream.userIo == nil && stream.agentIo == nil {
return errors.New("timeout: no connection established")
}
if stream.userIo == nil {
return errors.New("timeout: user connection not established")
}
if stream.agentIo == nil {
return errors.New("timeout: agent connection not established")
}
isDone := new(atomic.Bool)
endCh := make(chan struct{})
go func() {
_, innerErr := io.Copy(stream.userIo, stream.agentIo)
if innerErr != nil {
err = innerErr
}
if isDone.CompareAndSwap(false, true) {
close(endCh)
}
}()
go func() {
_, innerErr := io.Copy(stream.agentIo, stream.userIo)
if innerErr != nil {
err = innerErr
}
if isDone.CompareAndSwap(false, true) {
close(endCh)
}
}()
<-endCh
return err
}
+84 -6
View File
@@ -3,11 +3,16 @@ package rpc
import (
"context"
"fmt"
"github.com/naiba/nezha/pkg/ddns"
"github.com/naiba/nezha/pkg/utils"
"log"
"net"
"sync"
"time"
"github.com/naiba/nezha/pkg/ddns"
"github.com/naiba/nezha/pkg/geoip"
"github.com/naiba/nezha/pkg/grpcx"
"github.com/naiba/nezha/pkg/utils"
"github.com/jinzhu/copier"
"github.com/nicksnyder/go-i18n/v2/i18n"
@@ -16,8 +21,20 @@ import (
"github.com/naiba/nezha/service/singleton"
)
var NezhaHandlerSingleton *NezhaHandler
type NezhaHandler struct {
Auth *AuthHandler
Auth *authHandler
ioStreams map[string]*ioStreamContext
ioStreamMutex *sync.RWMutex
}
func NewNezhaHandler() *NezhaHandler {
return &NezhaHandler{
Auth: &authHandler{},
ioStreamMutex: new(sync.RWMutex),
ioStreams: make(map[string]*ioStreamContext),
}
}
func (s *NezhaHandler) ReportTask(c context.Context, r *pb.TaskResult) (*pb.Receipt, error) {
@@ -106,6 +123,7 @@ func (s *NezhaHandler) ReportSystemState(c context.Context, r *pb.State) (*pb.Re
func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Receipt, error) {
var clientID uint64
var provider ddns.Provider
var err error
if clientID, err = s.Auth.Check(c); err != nil {
return nil, err
@@ -120,9 +138,12 @@ func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Rece
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 singleton.Conf.DDNS.Provider == "" {
provider, err = singleton.GetDDNSProviderFromProfile(singleton.ServerList[clientID].DDNSProfile)
} else {
provider, err = singleton.GetDDNSProviderFromString(singleton.Conf.DDNS.Provider)
}
if err == nil && serverDomain != "" {
ipv4, ipv6, _ := utils.SplitIPAddr(host.IP)
maxRetries := int(singleton.Conf.DDNS.MaxRetries)
@@ -137,7 +158,7 @@ func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Rece
} else {
// 虽然会在启动时panic, 可以断言不会走这个分支, 但是考虑到动态加载配置或者其它情况, 这里输出一下方便检查奇奇怪怪的BUG
log.Printf("NEZHA>> 未找到对应的DDNS提供者(%s), 请前往config.yml检查你的设置\n", singleton.Conf.DDNS.Provider)
log.Printf("NEZHA>> 未找到对应的DDNS配置(%s), 或者是provider填写不正确, 请前往config.yml检查你的设置\n", singleton.ServerList[clientID].DDNSProfile)
}
}
@@ -169,6 +190,63 @@ func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Rece
singleton.ServerList[clientID].PrevHourlyTransferOut = singleton.ServerList[clientID].PrevHourlyTransferOut - int64(singleton.ServerList[clientID].State.NetOutTransfer)
}
// 不要冲掉国家码
if singleton.ServerList[clientID].Host != nil {
host.CountryCode = singleton.ServerList[clientID].Host.CountryCode
}
singleton.ServerList[clientID].Host = &host
return &pb.Receipt{Proced: true}, nil
}
func (s *NezhaHandler) IOStream(stream pb.NezhaService_IOStreamServer) error {
if _, err := s.Auth.Check(stream.Context()); err != nil {
return err
}
id, err := stream.Recv()
if err != nil {
return err
}
if id == nil || len(id.Data) < 4 || (id.Data[0] != 0xff && id.Data[1] != 0x05 && id.Data[2] != 0xff && id.Data[3] == 0x05) {
return fmt.Errorf("invalid stream id")
}
streamId := string(id.Data[4:])
if _, err := s.GetStream(streamId); err != nil {
return err
}
iw := grpcx.NewIOStreamWrapper(stream)
if err := s.AgentConnected(streamId, iw); err != nil {
return err
}
iw.Wait()
return nil
}
func (s *NezhaHandler) LookupGeoIP(c context.Context, r *pb.GeoIP) (*pb.GeoIP, error) {
var clientID uint64
var err error
if clientID, err = s.Auth.Check(c); err != nil {
return nil, err
}
// 根据内置数据库查询 IP 地理位置
record := &geoip.IPInfo{}
ip := r.GetIp()
netIP := net.ParseIP(ip)
location, err := geoip.Lookup(netIP, record)
if err != nil {
return nil, err
}
// 将地区码写入到 Host
singleton.ServerLock.RLock()
defer singleton.ServerLock.RUnlock()
if singleton.ServerList[clientID].Host == nil {
return nil, fmt.Errorf("host not found")
}
singleton.ServerList[clientID].Host.CountryCode = location
return &pb.GeoIP{Ip: ip, CountryCode: location}, nil
}
+17 -17
View File
@@ -15,8 +15,6 @@ var (
ServerAPI = &ServerAPIService{}
MonitorAPI = &MonitorAPIService{}
once = &sync.Once{}
)
type ServerAPIService struct{}
@@ -28,13 +26,14 @@ type CommonResponse struct {
}
type CommonServerInfo struct {
ID uint64 `json:"id"`
Name string `json:"name"`
Tag string `json:"tag"`
LastActive int64 `json:"last_active"`
IPV4 string `json:"ipv4"`
IPV6 string `json:"ipv6"`
ValidIP string `json:"valid_ip"`
ID uint64 `json:"id"`
Name string `json:"name"`
Tag string `json:"tag"`
LastActive int64 `json:"last_active"`
IPV4 string `json:"ipv4"`
IPV6 string `json:"ipv6"`
ValidIP string `json:"valid_ip"`
DisplayIndex int `json:"display_index"`
}
// StatusResponse 服务器状态子结构 包含服务器信息与状态信息
@@ -78,7 +77,7 @@ func InitAPI() {
UserIDToApiTokenList = make(map[uint64][]string)
}
func LoadAPI() {
func loadAPI() {
InitAPI()
var tokenList []*model.ApiToken
DB.Find(&tokenList)
@@ -143,13 +142,14 @@ func (s *ServerAPIService) GetAllStatus() *ServerStatusResponse {
}
ipv4, ipv6, validIP := utils.SplitIPAddr(host.IP)
info := CommonServerInfo{
ID: v.ID,
Name: v.Name,
Tag: v.Tag,
LastActive: v.LastActive.Unix(),
IPV4: ipv4,
IPV6: ipv6,
ValidIP: validIP,
ID: v.ID,
Name: v.Name,
Tag: v.Tag,
LastActive: v.LastActive.Unix(),
IPV4: ipv4,
IPV6: ipv6,
ValidIP: validIP,
DisplayIndex: v.DisplayIndex,
}
res.Result = append(res.Result, &StatusResponse{
CommonServerInfo: info,
+2 -2
View File
@@ -24,8 +24,8 @@ func InitCronTask() {
Crons = make(map[uint64]*model.Cron)
}
// LoadCronTasks 加载计划任务
func LoadCronTasks() {
// loadCronTasks 加载计划任务
func loadCronTasks() {
InitCronTask()
var crons []model.Cron
DB.Find(&crons)
+52 -6
View File
@@ -1,10 +1,10 @@
package singleton
import (
"errors"
"fmt"
ddns2 "github.com/naiba/nezha/pkg/ddns"
"log"
ddns2 "github.com/naiba/nezha/pkg/ddns"
)
func RetryableUpdateDomain(provider ddns2.Provider, config *ddns2.DomainConfig, maxRetries int) bool {
@@ -25,18 +25,64 @@ func RetryableUpdateDomain(provider ddns2.Provider, config *ddns2.DomainConfig,
func GetDDNSProviderFromString(provider string) (ddns2.Provider, error) {
switch provider {
case "webhook":
return ddns2.ProviderWebHook{
return &ddns2.ProviderWebHook{
URL: Conf.DDNS.WebhookURL,
RequestMethod: Conf.DDNS.WebhookMethod,
RequestBody: Conf.DDNS.WebhookRequestBody,
RequestHeader: Conf.DDNS.WebhookHeaders,
}, nil
case "dummy":
return ddns2.ProviderDummy{}, nil
return &ddns2.ProviderDummy{}, nil
case "cloudflare":
return ddns2.ProviderCloudflare{
return &ddns2.ProviderCloudflare{
Secret: Conf.DDNS.AccessSecret,
}, nil
case "tencentcloud":
return &ddns2.ProviderTencentCloud{
SecretID: Conf.DDNS.AccessID,
SecretKey: Conf.DDNS.AccessSecret,
}, nil
}
return ddns2.ProviderDummy{}, errors.New(fmt.Sprintf("无法找到配置的DDNS提供者%s", Conf.DDNS.Provider))
return &ddns2.ProviderDummy{}, fmt.Errorf("无法找到配置的DDNS提供者%s", Conf.DDNS.Provider)
}
func GetDDNSProviderFromProfile(profileName string) (ddns2.Provider, error) {
profile, ok := Conf.DDNS.Profiles[profileName]
if !ok {
return &ddns2.ProviderDummy{}, fmt.Errorf("未找到配置项 %s", profileName)
}
switch profile.Provider {
case "webhook":
return &ddns2.ProviderWebHook{
URL: profile.WebhookURL,
RequestMethod: profile.WebhookMethod,
RequestBody: profile.WebhookRequestBody,
RequestHeader: profile.WebhookHeaders,
}, nil
case "dummy":
return &ddns2.ProviderDummy{}, nil
case "cloudflare":
return &ddns2.ProviderCloudflare{
Secret: profile.AccessSecret,
}, nil
case "tencentcloud":
return &ddns2.ProviderTencentCloud{
SecretID: profile.AccessID,
SecretKey: profile.AccessSecret,
}, nil
}
return &ddns2.ProviderDummy{}, fmt.Errorf("无法找到配置的DDNS提供者%s", profile.Provider)
}
func ValidateDDNSProvidersFromProfiles() error {
validProviders := map[string]bool{"webhook": true, "dummy": true, "cloudflare": true, "tencentcloud": true}
providers := make(map[string]string)
for profileName, profile := range Conf.DDNS.Profiles {
if _, ok := validProviders[profile.Provider]; !ok {
return fmt.Errorf("无法找到配置的DDNS提供者%s", profile.Provider)
}
providers[profileName] = profile.Provider
}
return nil
}
+31
View File
@@ -0,0 +1,31 @@
package singleton
import (
"sync"
"github.com/naiba/nezha/model"
)
var natCache = make(map[string]*model.NAT)
var natCacheRwLock = new(sync.RWMutex)
func initNAT() {
OnNATUpdate()
}
func OnNATUpdate() {
natCacheRwLock.Lock()
defer natCacheRwLock.Unlock()
var nats []*model.NAT
DB.Find(&nats)
natCache = make(map[string]*model.NAT)
for i := 0; i < len(nats); i++ {
natCache[nats[i].Domain] = nats[i]
}
}
func GetNATConfigByDomain(domain string) *model.NAT {
natCacheRwLock.RLock()
defer natCacheRwLock.RUnlock()
return natCache[domain]
}
+2 -2
View File
@@ -24,8 +24,8 @@ func InitNotification() {
NotificationIDToTag = make(map[uint64]string)
}
// LoadNotifications 从 DB 初始化通知方式相关参数
func LoadNotifications() {
// loadNotifications 从 DB 初始化通知方式相关参数
func loadNotifications() {
InitNotification()
notificationsLock.Lock()
defer notificationsLock.Unlock()
+2 -2
View File
@@ -25,8 +25,8 @@ func InitServer() {
ServerTagToIDList = make(map[string][]uint64)
}
// LoadServers 加载服务器列表并根据ID排序
func LoadServers() {
// loadServers 加载服务器列表并根据ID排序
func loadServers() {
InitServer()
var servers []model.Server
DB.Find(&servers)
+1 -1
View File
@@ -335,7 +335,7 @@ func (ss *ServiceSentinel) worker() {
// 从服务状态汇报管道获取汇报的服务数据
for r := range ss.serviceReportChannel {
if ss.monitors[r.Data.GetId()] == nil || ss.monitors[r.Data.GetId()].ID == 0 {
log.Printf("NEZAH>> 错误的服务监控上报 %+v", r)
log.Printf("NEZHA>> 错误的服务监控上报 %+v", r)
continue
}
mh := r.Data
+23 -17
View File
@@ -34,10 +34,11 @@ func InitTimezoneAndCache() {
// LoadSingleton 加载子服务并执行
func LoadSingleton() {
LoadNotifications() // 加载通知服务
LoadServers() // 加载服务器列表
LoadCronTasks() // 加载定时任务
LoadAPI()
loadNotifications() // 加载通知服务
loadServers() // 加载服务器列表
loadCronTasks() // 加载定时任务
loadAPI()
initNAT()
}
// InitConfigFromPath 从给出的文件路径中加载配置
@@ -47,17 +48,21 @@ func InitConfigFromPath(path string) {
if err != nil {
panic(err)
}
ValidateConfig()
validateConfig()
}
// ValidateConfig 验证配置文件有效性
func ValidateConfig() {
// 如果DDNS启用则检查Provider是否存在, 不存在直接退出
// validateConfig 验证配置文件有效性
func validateConfig() {
var err error
if Conf.DDNS.Provider == "" {
err = ValidateDDNSProvidersFromProfiles()
} else {
_, err = GetDDNSProviderFromString(Conf.DDNS.Provider)
}
if err != nil {
panic(err)
}
if Conf.DDNS.Enable {
_, err := GetDDNSProviderFromString(Conf.DDNS.Provider)
if err != nil {
panic(err)
}
if Conf.DDNS.MaxRetries < 1 || Conf.DDNS.MaxRetries > 10 {
panic(fmt.Errorf("DDNS.MaxRetries值域为[1, 10]的整数, 当前为 %d", Conf.DDNS.MaxRetries))
}
@@ -78,7 +83,8 @@ func InitDBFromPath(path string) {
}
err = DB.AutoMigrate(model.Server{}, model.User{},
model.Notification{}, model.AlertRule{}, model.Monitor{},
model.MonitorHistory{}, model.Cron{}, model.Transfer{}, model.ApiToken{})
model.MonitorHistory{}, model.Cron{}, model.Transfer{},
model.ApiToken{}, model.NAT{})
if err != nil {
panic(err)
}
@@ -89,7 +95,7 @@ func RecordTransferHourlyUsage() {
ServerLock.Lock()
defer ServerLock.Unlock()
now := time.Now()
nowTrimSeconds := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, Loc)
nowTrimSeconds := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, now.Location())
var txs []model.Transfer
for id, server := range ServerList {
tx := model.Transfer{
@@ -132,7 +138,7 @@ func CleanMonitorHistory() {
if !rule.IsTransferDurationRule() {
continue
}
dataCouldRemoveBefore := rule.GetTransferDurationStart()
dataCouldRemoveBefore := rule.GetTransferDurationStart().UTC()
// 判断规则影响的机器范围
if rule.Cover == model.RuleCoverAll {
// 更新全局可以清理的数据点
@@ -151,12 +157,12 @@ func CleanMonitorHistory() {
}
}
for id, couldRemove := range specialServerKeep {
DB.Unscoped().Delete(&model.Transfer{}, "server_id = ? AND created_at < ?", id, couldRemove)
DB.Unscoped().Delete(&model.Transfer{}, "server_id = ? AND datetime(`created_at`) < datetime(?)", id, couldRemove)
}
if allServerKeep.IsZero() {
DB.Unscoped().Delete(&model.Transfer{}, "server_id NOT IN (?)", specialServerIDs)
} else {
DB.Unscoped().Delete(&model.Transfer{}, "server_id NOT IN (?) AND created_at < ?", specialServerIDs, allServerKeep)
DB.Unscoped().Delete(&model.Transfer{}, "server_id NOT IN (?) AND datetime(`created_at`) < datetime(?)", specialServerIDs, allServerKeep)
}
}