Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c18e0e420e | ||
|
|
d9097540c3 | ||
|
|
6b05a21641 | ||
|
|
f289b76f47 | ||
|
|
89fe4ec621 | ||
|
|
fca2f60380 | ||
|
|
6a70e67ef9 | ||
|
|
22842ebc75 | ||
|
|
aef968250f | ||
|
|
100600d594 | ||
|
|
fe234f4f38 | ||
|
|
df9487b5fc | ||
|
|
26d81f6d7a | ||
|
|
9d96c58ff9 | ||
|
|
5bb7efdeb9 | ||
|
|
519882c4a9 | ||
|
|
ed96432b79 | ||
|
|
1c057d93bd | ||
|
|
6005004681 | ||
|
|
d8f4236404 | ||
|
|
f95191c8af | ||
|
|
34fedd91d2 | ||
|
|
f675b884f5 | ||
|
|
0b13fec029 | ||
|
|
67b788a969 | ||
|
|
b63f693661 | ||
|
|
ac64aa7355 | ||
|
|
05d69d5c07 |
+131
@@ -0,0 +1,131 @@
|
|||||||
|
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 delete_gitee_releases(latest_id, client, uri, token):
|
||||||
|
get_data = {
|
||||||
|
'access_token': token
|
||||||
|
}
|
||||||
|
|
||||||
|
release_info = []
|
||||||
|
release_response = client.get(uri, json=get_data)
|
||||||
|
if release_response.status_code == 200:
|
||||||
|
release_info = release_response.json()
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
f"Request failed with status code {release_response.status_code}")
|
||||||
|
|
||||||
|
release_ids = []
|
||||||
|
for block in release_info:
|
||||||
|
if 'id' in block:
|
||||||
|
release_ids.append(block['id'])
|
||||||
|
|
||||||
|
print(f'Current release ids: {release_ids}')
|
||||||
|
release_ids.remove(latest_id)
|
||||||
|
|
||||||
|
for id in release_ids:
|
||||||
|
release_uri = f"{uri}/{id}"
|
||||||
|
delete_data = {
|
||||||
|
'access_token': token
|
||||||
|
}
|
||||||
|
delete_response = client.delete(release_uri, json=delete_data)
|
||||||
|
if delete_response.status_code == 204:
|
||||||
|
print(f'Successfully deleted release #{id}.')
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
f"Request failed with status code {delete_response.status_code}")
|
||||||
|
|
||||||
|
|
||||||
|
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}")
|
||||||
|
|
||||||
|
# 仅保留最新 Release 以防超出 Gitee 仓库配额
|
||||||
|
try:
|
||||||
|
delete_gitee_releases(release_id, api_client,
|
||||||
|
release_api_uri, access_token)
|
||||||
|
except ValueError as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
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()
|
||||||
@@ -12,6 +12,13 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- 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
|
- name: Extract branch name
|
||||||
run: |
|
run: |
|
||||||
export TAG_NAME=$(echo ${GITHUB_REF#refs/tags/})
|
export TAG_NAME=$(echo ${GITHUB_REF#refs/tags/})
|
||||||
@@ -108,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/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/docker-compose.yaml
|
||||||
curl -s https://purge.jsdelivr.net/gh/$LOWER_USERNAME/nezha@master/script/config.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'
|
||||||
|
})
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<br>
|
<br>
|
||||||
<small><i>LOGO designed by <a href="https://xio.ng" target="_blank">熊大</a> .</i></small>
|
<small><i>LOGO designed by <a href="https://xio.ng" target="_blank">熊大</a> .</i></small>
|
||||||
<br><br>
|
<br><br>
|
||||||
<img alt="GitHub release (with filter)" src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&style=for-the-badge&logo=github&label=Dashboard"> <img src="https://img.shields.io/github/v/release/nezhahq/agent?color=brightgreen&label=Agent&style=for-the-badge&logo=github"> <img src="https://img.shields.io/github/actions/workflow/status/nezhahq/agent/agent.yml?label=Agent%20CI&logo=github&style=for-the-badge"> <img src="https://img.shields.io/badge/Installer-v0.17.0-brightgreen?style=for-the-badge&logo=linux">
|
<img alt="GitHub release (with filter)" src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&style=for-the-badge&logo=github&label=Dashboard"> <img src="https://img.shields.io/github/v/release/nezhahq/agent?color=brightgreen&label=Agent&style=for-the-badge&logo=github"> <img src="https://img.shields.io/github/actions/workflow/status/nezhahq/agent/agent.yml?label=Agent%20CI&logo=github&style=for-the-badge"> <img src="https://img.shields.io/badge/Installer-v0.18.3-brightgreen?style=for-the-badge&logo=linux">
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
<p>:trollface: <b>Nezha Monitoring: Self-hostable, lightweight, servers and websites monitoring and O&M tool.</b></p>
|
<p>:trollface: <b>Nezha Monitoring: Self-hostable, lightweight, servers and websites monitoring and O&M tool.</b></p>
|
||||||
@@ -43,8 +43,8 @@ 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>
|
<!--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/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/Erope" title="卖女孩的小火柴"><img src="https://avatars.githubusercontent.com/u/44471469?v=4" width="50;" alt="卖女孩的小火柴"/></a>
|
||||||
<a href="https://github.com/dysf888" title="黑歌"><img src="https://avatars.githubusercontent.com/u/47450409?v=4" width="50;" alt="黑歌"/></a>
|
|
||||||
<a href="https://github.com/uubulb" title="UUBulb"><img src="https://avatars.githubusercontent.com/u/35923940?v=4" width="50;" alt="UUBulb"/></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/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/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/MikoyChinese" title="MikoyChinese"><img src="https://avatars.githubusercontent.com/u/22676744?v=4" width="50;" alt="MikoyChinese"/></a>
|
||||||
@@ -66,22 +66,25 @@ You can change the dashboard language in the settings page (`/setting`) after th
|
|||||||
<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/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/Creling" title="Creling"><img src="https://avatars.githubusercontent.com/u/43109504?v=4" width="50;" alt="Creling"/></a>
|
||||||
<a href="https://github.com/coreff" title="Core F"><img src="https://avatars.githubusercontent.com/u/38347122?v=4" width="50;" alt="Core F"/></a>
|
<a href="https://github.com/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/eya46" title="eya46"><img src="https://avatars.githubusercontent.com/u/61458340?v=4" width="50;" alt="eya46"/></a>
|
|
||||||
<a href="https://github.com/acgpiano" title="Acgpiano"><img src="https://avatars.githubusercontent.com/u/15900800?v=4" width="50;" alt="Acgpiano"/></a>
|
|
||||||
<a href="https://github.com/adminsama" title="adminsama"><img src="https://avatars.githubusercontent.com/u/60880076?v=4" width="50;" alt="adminsama"/></a>
|
|
||||||
<a href="https://github.com/hmsjy2017" title="Tony"><img src="https://avatars.githubusercontent.com/u/42692274?v=4" width="50;" alt="Tony"/></a>
|
|
||||||
<a href="https://github.com/nickfox-taterli" title="Tater Li"><img src="https://avatars.githubusercontent.com/u/19658596?v=4" width="50;" alt="Tater Li"/></a>
|
<a href="https://github.com/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/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/dreamingsleeping" title="Nanjing Hopefun Network Technology Co. Ltd."><img src="https://avatars.githubusercontent.com/u/13828658?v=4" width="50;" alt="Nanjing Hopefun Network Technology Co. Ltd."/></a>
|
||||||
|
<a href="https://github.com/silver-ymz" title="Mingzhuo Yin"><img src="https://avatars.githubusercontent.com/u/78400701?v=4" width="50;" alt="Mingzhuo Yin"/></a>
|
||||||
<a href="https://github.com/MartijnLindeman" title="Martijn Lindeman"><img src="https://avatars.githubusercontent.com/u/78365708?v=4" width="50;" alt="Martijn Lindeman"/></a>
|
<a href="https://github.com/MartijnLindeman" title="Martijn Lindeman"><img src="https://avatars.githubusercontent.com/u/78365708?v=4" width="50;" alt="Martijn Lindeman"/></a>
|
||||||
|
<a href="https://github.com/xrgzs" title="MadDogOwner"><img src="https://avatars.githubusercontent.com/u/26499123?v=4" width="50;" alt="MadDogOwner"/></a>
|
||||||
<a href="https://github.com/funnyzak" title="Leon"><img src="https://avatars.githubusercontent.com/u/2562087?v=4" width="50;" alt="Leon"/></a>
|
<a href="https://github.com/funnyzak" title="Leon"><img src="https://avatars.githubusercontent.com/u/2562087?v=4" width="50;" alt="Leon"/></a>
|
||||||
<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/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/techotaku" title="Ian Li"><img src="https://avatars.githubusercontent.com/u/1948179?v=4" width="50;" alt="Ian Li"/></a>
|
||||||
@@ -90,6 +93,9 @@ You can change the dashboard language in the settings page (`/setting`) after th
|
|||||||
<a href="https://github.com/wwng2333" title="Crazy"><img src="https://avatars.githubusercontent.com/u/17147265?v=4" width="50;" alt="Crazy"/></a>
|
<a href="https://github.com/wwng2333" title="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-->
|
<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
|
## Star History
|
||||||
|
|
||||||
[](https://star-history.com/#naiba/nezha&Timeline)
|
[](https://star-history.com/#naiba/nezha&Timeline)
|
||||||
|
|||||||
@@ -260,8 +260,8 @@ func (cp *commonPage) home(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var upgrader = websocket.Upgrader{
|
var upgrader = websocket.Upgrader{
|
||||||
ReadBufferSize: 1024,
|
ReadBufferSize: 10240,
|
||||||
WriteBufferSize: 1024,
|
WriteBufferSize: 10240,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Data struct {
|
type Data struct {
|
||||||
@@ -305,8 +305,8 @@ func (cp *commonPage) ws(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cp *commonPage) terminal(c *gin.Context) {
|
func (cp *commonPage) terminal(c *gin.Context) {
|
||||||
terminalID := c.Param("id")
|
streamId := c.Param("id")
|
||||||
if _, err := rpc.NezhaHandlerSingleton.GetStream(terminalID); err != nil {
|
if _, err := rpc.NezhaHandlerSingleton.GetStream(streamId); err != nil {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
Title: "无权访问",
|
Title: "无权访问",
|
||||||
@@ -316,7 +316,7 @@ func (cp *commonPage) terminal(c *gin.Context) {
|
|||||||
}, true)
|
}, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer rpc.NezhaHandlerSingleton.CloseStream(terminalID)
|
defer rpc.NezhaHandlerSingleton.CloseStream(streamId)
|
||||||
|
|
||||||
wsConn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
wsConn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -344,11 +344,11 @@ func (cp *commonPage) terminal(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err = rpc.NezhaHandlerSingleton.UserConnected(terminalID, conn); err != nil {
|
if err = rpc.NezhaHandlerSingleton.UserConnected(streamId, conn); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rpc.NezhaHandlerSingleton.StartStream(terminalID, time.Second*10)
|
rpc.NezhaHandlerSingleton.StartStream(streamId, time.Second*10)
|
||||||
}
|
}
|
||||||
|
|
||||||
type createTerminalRequest struct {
|
type createTerminalRequest struct {
|
||||||
@@ -380,7 +380,7 @@ func (cp *commonPage) createTerminal(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := uuid.GenerateUUID()
|
streamId, err := uuid.GenerateUUID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
@@ -394,7 +394,7 @@ func (cp *commonPage) createTerminal(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rpc.NezhaHandlerSingleton.CreateStream(id)
|
rpc.NezhaHandlerSingleton.CreateStream(streamId)
|
||||||
|
|
||||||
singleton.ServerLock.RLock()
|
singleton.ServerLock.RLock()
|
||||||
server := singleton.ServerList[createTerminalReq.ID]
|
server := singleton.ServerList[createTerminalReq.ID]
|
||||||
@@ -411,7 +411,7 @@ func (cp *commonPage) createTerminal(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
terminalData, _ := utils.Json.Marshal(&model.TerminalTask{
|
terminalData, _ := utils.Json.Marshal(&model.TerminalTask{
|
||||||
StreamID: id,
|
StreamID: streamId,
|
||||||
})
|
})
|
||||||
if err := server.TaskStream.Send(&proto.Task{
|
if err := server.TaskStream.Send(&proto.Task{
|
||||||
Type: model.TaskTypeTerminalGRPC,
|
Type: model.TaskTypeTerminalGRPC,
|
||||||
@@ -428,7 +428,7 @@ func (cp *commonPage) createTerminal(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/terminal", mygin.CommonEnvironment(c, gin.H{
|
c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/terminal", mygin.CommonEnvironment(c, gin.H{
|
||||||
"SessionID": id,
|
"SessionID": streamId,
|
||||||
"ServerName": server.Name,
|
"ServerName": server.Name,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
@@ -14,16 +15,26 @@ import (
|
|||||||
"code.cloudfoundry.org/bytefmt"
|
"code.cloudfoundry.org/bytefmt"
|
||||||
"github.com/gin-contrib/pprof"
|
"github.com/gin-contrib/pprof"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/hashicorp/go-uuid"
|
||||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||||
|
|
||||||
|
"github.com/naiba/nezha/model"
|
||||||
"github.com/naiba/nezha/pkg/mygin"
|
"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/resource"
|
||||||
|
"github.com/naiba/nezha/service/rpc"
|
||||||
"github.com/naiba/nezha/service/singleton"
|
"github.com/naiba/nezha/service/singleton"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ServeWeb(port uint) *http.Server {
|
func ServeWeb(port uint) *http.Server {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
|
if singleton.Conf.Debug {
|
||||||
|
gin.SetMode(gin.DebugMode)
|
||||||
|
pprof.Register(r)
|
||||||
|
}
|
||||||
|
r.Use(natGateway)
|
||||||
tmpl := template.New("").Funcs(funcMap)
|
tmpl := template.New("").Funcs(funcMap)
|
||||||
var err error
|
var err error
|
||||||
tmpl, err = tmpl.ParseFS(resource.TemplateFS, "template/**/*.html")
|
tmpl, err = tmpl.ParseFS(resource.TemplateFS, "template/**/*.html")
|
||||||
@@ -32,10 +43,6 @@ func ServeWeb(port uint) *http.Server {
|
|||||||
}
|
}
|
||||||
tmpl = loadThirdPartyTemplates(tmpl)
|
tmpl = loadThirdPartyTemplates(tmpl)
|
||||||
r.SetHTMLTemplate(tmpl)
|
r.SetHTMLTemplate(tmpl)
|
||||||
if singleton.Conf.Debug {
|
|
||||||
gin.SetMode(gin.DebugMode)
|
|
||||||
pprof.Register(r)
|
|
||||||
}
|
|
||||||
r.Use(mygin.RecordPath)
|
r.Use(mygin.RecordPath)
|
||||||
staticFs, err := fs.Sub(resource.StaticFS, "static")
|
staticFs, err := fs.Sub(resource.StaticFS, "static")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -44,7 +51,6 @@ func ServeWeb(port uint) *http.Server {
|
|||||||
r.StaticFS("/static", http.FS(staticFs))
|
r.StaticFS("/static", http.FS(staticFs))
|
||||||
r.Static("/static-custom", "resource/static/custom")
|
r.Static("/static-custom", "resource/static/custom")
|
||||||
routers(r)
|
routers(r)
|
||||||
|
|
||||||
page404 := func(c *gin.Context) {
|
page404 := func(c *gin.Context) {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
@@ -238,3 +244,64 @@ var funcMap = template.FuncMap{
|
|||||||
return singleton.StatusCodeToString(singleton.GetStatusCode(val))
|
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()
|
||||||
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ func (ma *memberAPI) serve() {
|
|||||||
mr.POST("/batch-update-server-group", ma.batchUpdateServerGroup)
|
mr.POST("/batch-update-server-group", ma.batchUpdateServerGroup)
|
||||||
mr.POST("/batch-delete-server", ma.batchDeleteServer)
|
mr.POST("/batch-delete-server", ma.batchDeleteServer)
|
||||||
mr.POST("/notification", ma.addOrEditNotification)
|
mr.POST("/notification", ma.addOrEditNotification)
|
||||||
|
mr.POST("/nat", ma.addOrEditNAT)
|
||||||
mr.POST("/alert-rule", ma.addOrEditAlertRule)
|
mr.POST("/alert-rule", ma.addOrEditAlertRule)
|
||||||
mr.POST("/setting", ma.updateSetting)
|
mr.POST("/setting", ma.updateSetting)
|
||||||
mr.DELETE("/:model/:id", ma.delete)
|
mr.DELETE("/:model/:id", ma.delete)
|
||||||
@@ -209,6 +210,11 @@ func (ma *memberAPI) delete(c *gin.Context) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
singleton.OnDeleteNotification(id)
|
singleton.OnDeleteNotification(id)
|
||||||
}
|
}
|
||||||
|
case "nat":
|
||||||
|
err = singleton.DB.Unscoped().Delete(&model.NAT{}, "id = ?", id).Error
|
||||||
|
if err == nil {
|
||||||
|
singleton.OnNATUpdate()
|
||||||
|
}
|
||||||
case "monitor":
|
case "monitor":
|
||||||
err = singleton.DB.Unscoped().Delete(&model.Monitor{}, "id = ?", id).Error
|
err = singleton.DB.Unscoped().Delete(&model.Monitor{}, "id = ?", id).Error
|
||||||
if err == nil {
|
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 {
|
type alertRuleForm struct {
|
||||||
ID uint64
|
ID uint64
|
||||||
Name string
|
Name string
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ func (mp *memberPage) serve() {
|
|||||||
mr.GET("/monitor", mp.monitor)
|
mr.GET("/monitor", mp.monitor)
|
||||||
mr.GET("/cron", mp.cron)
|
mr.GET("/cron", mp.cron)
|
||||||
mr.GET("/notification", mp.notification)
|
mr.GET("/notification", mp.notification)
|
||||||
|
mr.GET("/nat", mp.nat)
|
||||||
mr.GET("/setting", mp.setting)
|
mr.GET("/setting", mp.setting)
|
||||||
mr.GET("/api", mp.api)
|
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) {
|
func (mp *memberPage) setting(c *gin.Context) {
|
||||||
c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/setting", mygin.CommonEnvironment(c, gin.H{
|
c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/setting", mygin.CommonEnvironment(c, gin.H{
|
||||||
"Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "Settings"}),
|
"Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "Settings"}),
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/naiba/nezha/cmd/dashboard/controller"
|
"github.com/naiba/nezha/cmd/dashboard/controller"
|
||||||
"github.com/naiba/nezha/cmd/dashboard/rpc"
|
"github.com/naiba/nezha/cmd/dashboard/rpc"
|
||||||
"github.com/naiba/nezha/model"
|
"github.com/naiba/nezha/model"
|
||||||
|
"github.com/naiba/nezha/proto"
|
||||||
"github.com/naiba/nezha/service/singleton"
|
"github.com/naiba/nezha/service/singleton"
|
||||||
"github.com/ory/graceful"
|
"github.com/ory/graceful"
|
||||||
flag "github.com/spf13/pflag"
|
flag "github.com/spf13/pflag"
|
||||||
@@ -68,6 +70,7 @@ func main() {
|
|||||||
go singleton.AlertSentinelStart()
|
go singleton.AlertSentinelStart()
|
||||||
singleton.NewServiceSentinel(serviceSentinelDispatchBus)
|
singleton.NewServiceSentinel(serviceSentinelDispatchBus)
|
||||||
srv := controller.ServeWeb(singleton.Conf.HTTPPort)
|
srv := controller.ServeWeb(singleton.Conf.HTTPPort)
|
||||||
|
go dispatchReportInfoTask()
|
||||||
if err := graceful.Graceful(func() error {
|
if err := graceful.Graceful(func() error {
|
||||||
return srv.ListenAndServe()
|
return srv.ListenAndServe()
|
||||||
}, func(c context.Context) error {
|
}, func(c context.Context) error {
|
||||||
@@ -80,3 +83,18 @@ func main() {
|
|||||||
log.Printf("NEZHA>> ERROR: %v", err)
|
log.Printf("NEZHA>> ERROR: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dispatchReportInfoTask() {
|
||||||
|
time.Sleep(time.Second * 15)
|
||||||
|
singleton.ServerLock.RLock()
|
||||||
|
defer singleton.ServerLock.RUnlock()
|
||||||
|
for _, server := range singleton.ServerList {
|
||||||
|
if server == nil || server.TaskStream == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
server.TaskStream.Send(&proto.Task{
|
||||||
|
Type: model.TaskTypeReportHostInfo,
|
||||||
|
Data: "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ require (
|
|||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.4.0
|
github.com/nicksnyder/go-i18n/v2 v2.4.0
|
||||||
github.com/ory/graceful v0.1.3
|
github.com/ory/graceful v0.1.3
|
||||||
|
github.com/oschwald/maxminddb-golang v1.13.1
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
|
|||||||
@@ -131,6 +131,8 @@ github.com/onsi/gomega v1.33.0 h1:snPCflnZrpMsy94p4lXVEkHo12lmPnc3vY5XBbreexE=
|
|||||||
github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY=
|
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 h1:FaeXcHZh168WzS+bqruqWEw/HgXWLdNv2nJ+fbhxbhc=
|
||||||
github.com/ory/graceful v0.1.3/go.mod h1:4zFz687IAF7oNHHiB586U4iL+/4aV09o/PYLE34t2bA=
|
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 h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
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=
|
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||||
@@ -173,8 +175,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
|||||||
+9
-2
@@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
_ = iota
|
_ = iota
|
||||||
TaskTypeHTTPGET
|
TaskTypeHTTPGet
|
||||||
TaskTypeICMPPing
|
TaskTypeICMPPing
|
||||||
TaskTypeTCPPing
|
TaskTypeTCPPing
|
||||||
TaskTypeCommand
|
TaskTypeCommand
|
||||||
@@ -21,12 +21,19 @@ const (
|
|||||||
TaskTypeUpgrade
|
TaskTypeUpgrade
|
||||||
TaskTypeKeepalive
|
TaskTypeKeepalive
|
||||||
TaskTypeTerminalGRPC
|
TaskTypeTerminalGRPC
|
||||||
|
TaskTypeNAT
|
||||||
|
TaskTypeReportHostInfo
|
||||||
)
|
)
|
||||||
|
|
||||||
type TerminalTask struct {
|
type TerminalTask struct {
|
||||||
StreamID string
|
StreamID string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TaskNAT struct {
|
||||||
|
StreamID string
|
||||||
|
Host string
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MonitorCoverAll = iota
|
MonitorCoverAll = iota
|
||||||
MonitorCoverIgnoreAll
|
MonitorCoverIgnoreAll
|
||||||
@@ -114,7 +121,7 @@ func (m *Monitor) AfterFind(tx *gorm.DB) error {
|
|||||||
|
|
||||||
// IsServiceSentinelNeeded 判断该任务类型是否需要进行服务监控 需要则返回true
|
// IsServiceSentinelNeeded 判断该任务类型是否需要进行服务监控 需要则返回true
|
||||||
func IsServiceSentinelNeeded(t uint64) bool {
|
func IsServiceSentinelNeeded(t uint64) bool {
|
||||||
return t != TaskTypeCommand && t != TaskTypeTerminal && t != TaskTypeUpgrade
|
return t != TaskTypeCommand && t != TaskTypeTerminalGRPC && t != TaskTypeUpgrade
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Monitor) InitSkipServers() error {
|
func (m *Monitor) InitSkipServers() error {
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type NAT struct {
|
||||||
|
Common
|
||||||
|
Name string
|
||||||
|
ServerID uint64
|
||||||
|
Host string
|
||||||
|
Domain string `gorm:"unique"`
|
||||||
|
}
|
||||||
+5
-5
@@ -17,11 +17,11 @@ type Server struct {
|
|||||||
Note string `json:"-"` // 管理员可见备注
|
Note string `json:"-"` // 管理员可见备注
|
||||||
DisplayIndex int // 展示排序,越大越靠前
|
DisplayIndex int // 展示排序,越大越靠前
|
||||||
HideForGuest bool // 对游客隐藏
|
HideForGuest bool // 对游客隐藏
|
||||||
EnableDDNS bool // 是否启用DDNS 未在配置文件中启用DDNS 或 DDNS检查时间为0时此项无效
|
EnableDDNS bool `json:"-"` // 是否启用DDNS 未在配置文件中启用DDNS 或 DDNS检查时间为0时此项无效
|
||||||
EnableIPv4 bool // 是否启用DDNS IPv4
|
EnableIPv4 bool `json:"-"` // 是否启用DDNS IPv4
|
||||||
EnableIpv6 bool // 是否启用DDNS IPv6
|
EnableIpv6 bool `json:"-"` // 是否启用DDNS IPv6
|
||||||
DDNSDomain string // DDNS中的前缀 如基础域名为abc.oracle DDNSName为mjj 就会把mjj.abc.oracle解析服务器IP 为空则停用
|
DDNSDomain string `json:"-"` // DDNS中的前缀 如基础域名为abc.oracle DDNSName为mjj 就会把mjj.abc.oracle解析服务器IP 为空则停用
|
||||||
DDNSProfile string // DDNS配置
|
DDNSProfile string `json:"-"` // DDNS配置
|
||||||
|
|
||||||
Host *Host `gorm:"-"`
|
Host *Host `gorm:"-"`
|
||||||
State *HostState `gorm:"-"`
|
State *HostState `gorm:"-"`
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
stub
|
||||||
@@ -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")
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ var adminPage = map[string]bool{
|
|||||||
"/monitor": true,
|
"/monitor": true,
|
||||||
"/setting": true,
|
"/setting": true,
|
||||||
"/notification": true,
|
"/notification": true,
|
||||||
|
"/nat": true,
|
||||||
"/cron": true,
|
"/cron": true,
|
||||||
"/api": true,
|
"/api": true,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
package websocketx
|
package websocketx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ io.ReadWriteCloser = &Conn{}
|
||||||
|
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
*websocket.Conn
|
*websocket.Conn
|
||||||
writeLock *sync.Mutex
|
writeLock *sync.Mutex
|
||||||
@@ -25,6 +28,12 @@ func (conn *Conn) Write(data []byte) (int, error) {
|
|||||||
return len(data), nil
|
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) {
|
func (conn *Conn) Read(data []byte) (int, error) {
|
||||||
if len(conn.dataBuf) > 0 {
|
if len(conn.dataBuf) > 0 {
|
||||||
n := copy(data, conn.dataBuf)
|
n := copy(data, conn.dataBuf)
|
||||||
|
|||||||
+116
-40
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.34.2
|
// protoc-gen-go v1.34.1
|
||||||
// protoc v5.27.1
|
// protoc v5.26.1
|
||||||
// source: proto/nezha.proto
|
// source: proto/nezha.proto
|
||||||
|
|
||||||
package proto
|
package proto
|
||||||
@@ -35,7 +35,7 @@ type Host struct {
|
|||||||
Virtualization string `protobuf:"bytes,8,opt,name=virtualization,proto3" json:"virtualization,omitempty"`
|
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"`
|
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"`
|
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"`
|
Version string `protobuf:"bytes,12,opt,name=version,proto3" json:"version,omitempty"`
|
||||||
Gpu []string `protobuf:"bytes,13,rep,name=gpu,proto3" json:"gpu,omitempty"`
|
Gpu []string `protobuf:"bytes,13,rep,name=gpu,proto3" json:"gpu,omitempty"`
|
||||||
}
|
}
|
||||||
@@ -629,6 +629,61 @@ func (x *IOStreamData) GetData() []byte {
|
|||||||
return nil
|
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 protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_proto_nezha_proto_rawDesc = []byte{
|
var file_proto_nezha_proto_rawDesc = []byte{
|
||||||
@@ -712,26 +767,32 @@ var file_proto_nezha_proto_rawDesc = []byte{
|
|||||||
0x69, 0x70, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x64, 0x18, 0x01, 0x20,
|
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,
|
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,
|
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, 0x32,
|
0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22,
|
||||||
0x92, 0x02, 0x0a, 0x0c, 0x4e, 0x65, 0x7a, 0x68, 0x61, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
|
0x3a, 0x0a, 0x05, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01,
|
||||||
0x12, 0x33, 0x0a, 0x11, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d,
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e,
|
||||||
0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74,
|
0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
|
||||||
0x61, 0x74, 0x65, 0x1a, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x65,
|
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x32, 0xbf, 0x02, 0x0a, 0x0c,
|
||||||
0x69, 0x70, 0x74, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x10, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53,
|
0x4e, 0x65, 0x7a, 0x68, 0x61, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x11,
|
||||||
0x79, 0x73, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x53, 0x74, 0x61, 0x74,
|
||||||
0x6f, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52,
|
0x65, 0x12, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x1a,
|
||||||
0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x0a, 0x52, 0x65, 0x70, 0x6f,
|
0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x22,
|
||||||
0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54,
|
0x00, 0x12, 0x31, 0x0a, 0x10, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65,
|
||||||
0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x1a, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x6f,
|
||||||
0x6f, 0x2e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x22, 0x00, 0x12, 0x2b, 0x0a, 0x0b, 0x52,
|
0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x65, 0x69,
|
||||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x0b, 0x2e, 0x70, 0x72, 0x6f,
|
0x70, 0x74, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x0a, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61,
|
||||||
0x74, 0x6f, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
|
0x73, 0x6b, 0x12, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x52,
|
||||||
0x54, 0x61, 0x73, 0x6b, 0x22, 0x00, 0x30, 0x01, 0x12, 0x3a, 0x0a, 0x08, 0x49, 0x4f, 0x53, 0x74,
|
0x65, 0x73, 0x75, 0x6c, 0x74, 0x1a, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65,
|
||||||
0x72, 0x65, 0x61, 0x6d, 0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x4f, 0x53,
|
0x63, 0x65, 0x69, 0x70, 0x74, 0x22, 0x00, 0x12, 0x2b, 0x0a, 0x0b, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||||
0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
0x73, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48,
|
||||||
0x6f, 0x2e, 0x49, 0x4f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x22, 0x00,
|
0x6f, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x61, 0x73, 0x6b,
|
||||||
0x28, 0x01, 0x30, 0x01, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
0x22, 0x00, 0x30, 0x01, 0x12, 0x3a, 0x0a, 0x08, 0x49, 0x4f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d,
|
||||||
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
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 (
|
var (
|
||||||
@@ -746,8 +807,8 @@ func file_proto_nezha_proto_rawDescGZIP() []byte {
|
|||||||
return file_proto_nezha_proto_rawDescData
|
return file_proto_nezha_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_proto_nezha_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
|
var file_proto_nezha_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||||
var file_proto_nezha_proto_goTypes = []any{
|
var file_proto_nezha_proto_goTypes = []interface{}{
|
||||||
(*Host)(nil), // 0: proto.Host
|
(*Host)(nil), // 0: proto.Host
|
||||||
(*State)(nil), // 1: proto.State
|
(*State)(nil), // 1: proto.State
|
||||||
(*State_SensorTemperature)(nil), // 2: proto.State_SensorTemperature
|
(*State_SensorTemperature)(nil), // 2: proto.State_SensorTemperature
|
||||||
@@ -755,6 +816,7 @@ var file_proto_nezha_proto_goTypes = []any{
|
|||||||
(*TaskResult)(nil), // 4: proto.TaskResult
|
(*TaskResult)(nil), // 4: proto.TaskResult
|
||||||
(*Receipt)(nil), // 5: proto.Receipt
|
(*Receipt)(nil), // 5: proto.Receipt
|
||||||
(*IOStreamData)(nil), // 6: proto.IOStreamData
|
(*IOStreamData)(nil), // 6: proto.IOStreamData
|
||||||
|
(*GeoIP)(nil), // 7: proto.GeoIP
|
||||||
}
|
}
|
||||||
var file_proto_nezha_proto_depIdxs = []int32{
|
var file_proto_nezha_proto_depIdxs = []int32{
|
||||||
2, // 0: proto.State.temperatures:type_name -> proto.State_SensorTemperature
|
2, // 0: proto.State.temperatures:type_name -> proto.State_SensorTemperature
|
||||||
@@ -763,13 +825,15 @@ var file_proto_nezha_proto_depIdxs = []int32{
|
|||||||
4, // 3: proto.NezhaService.ReportTask:input_type -> proto.TaskResult
|
4, // 3: proto.NezhaService.ReportTask:input_type -> proto.TaskResult
|
||||||
0, // 4: proto.NezhaService.RequestTask:input_type -> proto.Host
|
0, // 4: proto.NezhaService.RequestTask:input_type -> proto.Host
|
||||||
6, // 5: proto.NezhaService.IOStream:input_type -> proto.IOStreamData
|
6, // 5: proto.NezhaService.IOStream:input_type -> proto.IOStreamData
|
||||||
5, // 6: proto.NezhaService.ReportSystemState:output_type -> proto.Receipt
|
7, // 6: proto.NezhaService.LookupGeoIP:input_type -> proto.GeoIP
|
||||||
5, // 7: proto.NezhaService.ReportSystemInfo:output_type -> proto.Receipt
|
5, // 7: proto.NezhaService.ReportSystemState:output_type -> proto.Receipt
|
||||||
5, // 8: proto.NezhaService.ReportTask:output_type -> proto.Receipt
|
5, // 8: proto.NezhaService.ReportSystemInfo:output_type -> proto.Receipt
|
||||||
3, // 9: proto.NezhaService.RequestTask:output_type -> proto.Task
|
5, // 9: proto.NezhaService.ReportTask:output_type -> proto.Receipt
|
||||||
6, // 10: proto.NezhaService.IOStream:output_type -> proto.IOStreamData
|
3, // 10: proto.NezhaService.RequestTask:output_type -> proto.Task
|
||||||
6, // [6:11] is the sub-list for method output_type
|
6, // 11: proto.NezhaService.IOStream:output_type -> proto.IOStreamData
|
||||||
1, // [1:6] is the sub-list for method input_type
|
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 type_name
|
||||||
1, // [1:1] is the sub-list for extension extendee
|
1, // [1:1] is the sub-list for extension extendee
|
||||||
0, // [0:1] is the sub-list for field type_name
|
0, // [0:1] is the sub-list for field type_name
|
||||||
@@ -781,7 +845,7 @@ func file_proto_nezha_proto_init() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !protoimpl.UnsafeEnabled {
|
if !protoimpl.UnsafeEnabled {
|
||||||
file_proto_nezha_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
file_proto_nezha_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*Host); i {
|
switch v := v.(*Host); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -793,7 +857,7 @@ func file_proto_nezha_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_proto_nezha_proto_msgTypes[1].Exporter = func(v any, i int) any {
|
file_proto_nezha_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*State); i {
|
switch v := v.(*State); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -805,7 +869,7 @@ func file_proto_nezha_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_proto_nezha_proto_msgTypes[2].Exporter = func(v any, i int) any {
|
file_proto_nezha_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*State_SensorTemperature); i {
|
switch v := v.(*State_SensorTemperature); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -817,7 +881,7 @@ func file_proto_nezha_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_proto_nezha_proto_msgTypes[3].Exporter = func(v any, i int) any {
|
file_proto_nezha_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*Task); i {
|
switch v := v.(*Task); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -829,7 +893,7 @@ func file_proto_nezha_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_proto_nezha_proto_msgTypes[4].Exporter = func(v any, i int) any {
|
file_proto_nezha_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*TaskResult); i {
|
switch v := v.(*TaskResult); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -841,7 +905,7 @@ func file_proto_nezha_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_proto_nezha_proto_msgTypes[5].Exporter = func(v any, i int) any {
|
file_proto_nezha_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*Receipt); i {
|
switch v := v.(*Receipt); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -853,7 +917,7 @@ func file_proto_nezha_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_proto_nezha_proto_msgTypes[6].Exporter = func(v any, i int) any {
|
file_proto_nezha_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*IOStreamData); i {
|
switch v := v.(*IOStreamData); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -865,6 +929,18 @@ func file_proto_nezha_proto_init() {
|
|||||||
return nil
|
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{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
@@ -872,7 +948,7 @@ func file_proto_nezha_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_proto_nezha_proto_rawDesc,
|
RawDescriptor: file_proto_nezha_proto_rawDesc,
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 7,
|
NumMessages: 8,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
+7
-1
@@ -9,6 +9,7 @@ service NezhaService {
|
|||||||
rpc ReportTask(TaskResult)returns(Receipt){}
|
rpc ReportTask(TaskResult)returns(Receipt){}
|
||||||
rpc RequestTask(Host)returns(stream Task){}
|
rpc RequestTask(Host)returns(stream Task){}
|
||||||
rpc IOStream(stream IOStreamData)returns(stream IOStreamData){}
|
rpc IOStream(stream IOStreamData)returns(stream IOStreamData){}
|
||||||
|
rpc LookupGeoIP(GeoIP)returns(GeoIP){}
|
||||||
}
|
}
|
||||||
|
|
||||||
message Host {
|
message Host {
|
||||||
@@ -22,7 +23,7 @@ message Host {
|
|||||||
string virtualization = 8;
|
string virtualization = 8;
|
||||||
uint64 boot_time = 9;
|
uint64 boot_time = 9;
|
||||||
string ip = 10;
|
string ip = 10;
|
||||||
string country_code = 11;
|
string country_code = 11; // deprecated
|
||||||
string version = 12;
|
string version = 12;
|
||||||
repeated string gpu = 13;
|
repeated string gpu = 13;
|
||||||
}
|
}
|
||||||
@@ -73,3 +74,8 @@ message Receipt{
|
|||||||
message IOStreamData {
|
message IOStreamData {
|
||||||
bytes data = 1;
|
bytes data = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message GeoIP {
|
||||||
|
string ip = 1;
|
||||||
|
string country_code = 2;
|
||||||
|
}
|
||||||
+38
-1
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.3.0
|
// - protoc-gen-go-grpc v1.3.0
|
||||||
// - protoc v5.27.1
|
// - protoc v5.26.1
|
||||||
// source: proto/nezha.proto
|
// source: proto/nezha.proto
|
||||||
|
|
||||||
package proto
|
package proto
|
||||||
@@ -24,6 +24,7 @@ const (
|
|||||||
NezhaService_ReportTask_FullMethodName = "/proto.NezhaService/ReportTask"
|
NezhaService_ReportTask_FullMethodName = "/proto.NezhaService/ReportTask"
|
||||||
NezhaService_RequestTask_FullMethodName = "/proto.NezhaService/RequestTask"
|
NezhaService_RequestTask_FullMethodName = "/proto.NezhaService/RequestTask"
|
||||||
NezhaService_IOStream_FullMethodName = "/proto.NezhaService/IOStream"
|
NezhaService_IOStream_FullMethodName = "/proto.NezhaService/IOStream"
|
||||||
|
NezhaService_LookupGeoIP_FullMethodName = "/proto.NezhaService/LookupGeoIP"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NezhaServiceClient is the client API for NezhaService service.
|
// NezhaServiceClient is the client API for NezhaService service.
|
||||||
@@ -35,6 +36,7 @@ type NezhaServiceClient interface {
|
|||||||
ReportTask(ctx context.Context, in *TaskResult, 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)
|
RequestTask(ctx context.Context, in *Host, opts ...grpc.CallOption) (NezhaService_RequestTaskClient, error)
|
||||||
IOStream(ctx context.Context, opts ...grpc.CallOption) (NezhaService_IOStreamClient, 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 {
|
type nezhaServiceClient struct {
|
||||||
@@ -135,6 +137,15 @@ func (x *nezhaServiceIOStreamClient) Recv() (*IOStreamData, error) {
|
|||||||
return m, nil
|
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.
|
// NezhaServiceServer is the server API for NezhaService service.
|
||||||
// All implementations should embed UnimplementedNezhaServiceServer
|
// All implementations should embed UnimplementedNezhaServiceServer
|
||||||
// for forward compatibility
|
// for forward compatibility
|
||||||
@@ -144,6 +155,7 @@ type NezhaServiceServer interface {
|
|||||||
ReportTask(context.Context, *TaskResult) (*Receipt, error)
|
ReportTask(context.Context, *TaskResult) (*Receipt, error)
|
||||||
RequestTask(*Host, NezhaService_RequestTaskServer) error
|
RequestTask(*Host, NezhaService_RequestTaskServer) error
|
||||||
IOStream(NezhaService_IOStreamServer) error
|
IOStream(NezhaService_IOStreamServer) error
|
||||||
|
LookupGeoIP(context.Context, *GeoIP) (*GeoIP, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnimplementedNezhaServiceServer should be embedded to have forward compatible implementations.
|
// UnimplementedNezhaServiceServer should be embedded to have forward compatible implementations.
|
||||||
@@ -165,6 +177,9 @@ func (UnimplementedNezhaServiceServer) RequestTask(*Host, NezhaService_RequestTa
|
|||||||
func (UnimplementedNezhaServiceServer) IOStream(NezhaService_IOStreamServer) error {
|
func (UnimplementedNezhaServiceServer) IOStream(NezhaService_IOStreamServer) error {
|
||||||
return status.Errorf(codes.Unimplemented, "method IOStream not implemented")
|
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.
|
// 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
|
// Use of this interface is not recommended, as added methods to NezhaServiceServer will
|
||||||
@@ -278,6 +293,24 @@ func (x *nezhaServiceIOStreamServer) Recv() (*IOStreamData, error) {
|
|||||||
return m, nil
|
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.
|
// NezhaService_ServiceDesc is the grpc.ServiceDesc for NezhaService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@@ -297,6 +330,10 @@ var NezhaService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "ReportTask",
|
MethodName: "ReportTask",
|
||||||
Handler: _NezhaService_ReportTask_Handler,
|
Handler: _NezhaService_ReportTask_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "LookupGeoIP",
|
||||||
|
Handler: _NezhaService_LookupGeoIP_Handler,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{
|
Streams: []grpc.StreamDesc{
|
||||||
{
|
{
|
||||||
|
|||||||
Vendored
+6
@@ -648,3 +648,9 @@ other = "Disable Switch Template in Frontend"
|
|||||||
|
|
||||||
[ServersOnWorldMap]
|
[ServersOnWorldMap]
|
||||||
other = "Servers On World Map"
|
other = "Servers On World Map"
|
||||||
|
|
||||||
|
[NAT]
|
||||||
|
other = "NAT"
|
||||||
|
|
||||||
|
[NetworkSpiterList]
|
||||||
|
other = "Network Monitor"
|
||||||
Vendored
+7
-1
@@ -647,4 +647,10 @@ other = "Temperatura"
|
|||||||
other = "Deshabilitar Cambio de Plantilla en Frontend"
|
other = "Deshabilitar Cambio de Plantilla en Frontend"
|
||||||
|
|
||||||
[ServersOnWorldMap]
|
[ServersOnWorldMap]
|
||||||
other = "Servidores en el mapa mundial"
|
other = "Servidores en el mapa mundial"
|
||||||
|
|
||||||
|
[NAT]
|
||||||
|
other = "NAT"
|
||||||
|
|
||||||
|
[NetworkSpiterList]
|
||||||
|
other = "Red Monitor"
|
||||||
Vendored
+6
@@ -648,3 +648,9 @@ other = "禁止前台切换模板"
|
|||||||
|
|
||||||
[ServersOnWorldMap]
|
[ServersOnWorldMap]
|
||||||
other = "服务器世界分布图"
|
other = "服务器世界分布图"
|
||||||
|
|
||||||
|
[NAT]
|
||||||
|
other = "内网穿透"
|
||||||
|
|
||||||
|
[NetworkSpiterList]
|
||||||
|
other = "网络监控"
|
||||||
|
|||||||
Vendored
+6
@@ -648,3 +648,9 @@ other = "禁止前台切換主題"
|
|||||||
|
|
||||||
[ServersOnWorldMap]
|
[ServersOnWorldMap]
|
||||||
other = "伺服器世界分布圖"
|
other = "伺服器世界分布圖"
|
||||||
|
|
||||||
|
[NAT]
|
||||||
|
other = "NAT"
|
||||||
|
|
||||||
|
[NetworkSpiterList]
|
||||||
|
other = "網絡監控"
|
||||||
@@ -91,6 +91,7 @@ function showFormModal(modelSelector, formID, URL, getData) {
|
|||||||
item.name.endsWith("_id") ||
|
item.name.endsWith("_id") ||
|
||||||
item.name === "id" ||
|
item.name === "id" ||
|
||||||
item.name === "ID" ||
|
item.name === "ID" ||
|
||||||
|
item.name === "ServerID" ||
|
||||||
item.name === "RequestType" ||
|
item.name === "RequestType" ||
|
||||||
item.name === "RequestMethod" ||
|
item.name === "RequestMethod" ||
|
||||||
item.name === "TriggerMode" ||
|
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) {
|
function connectToServer(id) {
|
||||||
post('/terminal', { Host: window.location.host, Protocol: window.location.protocol, ID: id })
|
post('/terminal', { Host: window.location.host, Protocol: window.location.protocol, ID: id })
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+87
-81
@@ -1,5 +1,6 @@
|
|||||||
body[theme="dark"] {
|
body[theme="dark"] {
|
||||||
background: #31363b;
|
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||||
|
background-color: rgba(49, 54, 59, 1);
|
||||||
color: #f1f1f1;
|
color: #f1f1f1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7,14 +8,9 @@ body[theme="dark"] .navbar .navbar-brand {
|
|||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .navbar .navbar-brand {
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
body[theme="dark"] .navbar .dropdown-menu {
|
body[theme="dark"] .navbar .dropdown-menu {
|
||||||
list-style-image: initial;
|
background-color: rgba(23, 26, 30, 1);
|
||||||
background-color: #171a1e;
|
border-color: rgba(49, 54, 59, 1);
|
||||||
border-color: #31363b;
|
|
||||||
box-shadow: rgba(0, 0, 0, 0.18) 0px 6px 12px;
|
box-shadow: rgba(0, 0, 0, 0.18) 0px 6px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,31 +20,25 @@ 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:focus,
|
||||||
body[theme="dark"] .navbar .dropdown-menu > li > a:hover {
|
body[theme="dark"] .navbar .dropdown-menu > li > a:hover {
|
||||||
background-color: #25282a;
|
background-color: rgba(37, 40, 42, 1);
|
||||||
background-image: linear-gradient(#1c1d26 0, #1c1d26 100%);
|
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,
|
body[theme="dark"] .navbar .navbar-nav > .open > a:focus,
|
||||||
body[theme="dark"] .navbar .navbar-nav > .open > a:hover,
|
body[theme="dark"] .navbar .navbar-nav > .open > a:hover,
|
||||||
body[theme="dark"] .navbar .navbar-nav > .active > a,
|
body[theme="dark"] .navbar .navbar-nav > .active > a,
|
||||||
body[theme="dark"] .navbar .navbar-nav > .open > a{
|
body[theme="dark"] .navbar .navbar-nav > .open > a{
|
||||||
background-image: none !important;
|
background-image: none;
|
||||||
box-shadow: none !important;
|
box-shadow: none;
|
||||||
background-color: transparent !important;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .navbar .navbar-nav .open .dropdown-menu>li>a {
|
body[theme="dark"] .navbar .navbar-nav .open .dropdown-menu > li > a {
|
||||||
color: #f1f1f1;
|
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 {
|
body[theme="dark"] .content {
|
||||||
background-image: initial;
|
background-color: rgba(28, 29, 38, 1);
|
||||||
background-color: #1c1d26;
|
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: rgba(0, 0, 0, 0.5) 0 0.625em 2em;
|
box-shadow: rgba(0, 0, 0, 0.5) 0 0.625em 2em;
|
||||||
-webkit-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;
|
||||||
@@ -56,58 +46,10 @@ body[theme="dark"] .content {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .table {
|
body[theme="dark"] .table {
|
||||||
background-image: initial;
|
background-color: rgba(28, 29, 38, 1);
|
||||||
background-color: #1c1d26;
|
|
||||||
border-color: #545b5e;
|
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 > tbody > tr > td:before,
|
||||||
body[theme="dark"] .table > tfoot > tr > td:before,
|
body[theme="dark"] .table > tfoot > tr > td:before,
|
||||||
body[theme="dark"] .table > thead > tr > td:before,
|
body[theme="dark"] .table > thead > tr > td:before,
|
||||||
@@ -115,25 +57,75 @@ body[theme="dark"] .table > thead > tr.node-group-cell > th:before{
|
|||||||
background-color: rgba(58, 62, 65, 1);
|
background-color: rgba(58, 62, 65, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .table > tbody > tr.expandRow > td:before,
|
body[theme="dark"] .table-striped tbody > tr,
|
||||||
body[theme="dark"] .table > thead > tr.node-group-tag > th:before{
|
body[theme="dark"] .table-striped tbody > tr.even > td,
|
||||||
background-color: #1c1d26;
|
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(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 {
|
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) {
|
/* expandRow展开部分样式 */
|
||||||
background-color: #1c1d26;
|
body[theme="dark"] .table > tbody > tr.expandRow.odd > td:before{
|
||||||
|
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(to right, rgba(77, 133, 58, 1) 0, rgba(54, 126, 54, 1) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .progress-bar-warning {
|
||||||
|
background-image: linear-gradient(to right, rgba(153, 95, 13, 1) 0, rgba(181, 113, 15, 1) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .progress-bar-danger {
|
||||||
|
background-image: linear-gradient(to right, rgba(145, 35, 32, 1) 0, rgba(161, 38, 35, 1) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .progress-offline .progress-bar-success {
|
||||||
|
background-image: linear-gradient(rgba(128, 128, 128, 1) 0, rgba(128, 128, 128, 1) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .progress-offline .progress-bar-warning {
|
||||||
|
background-image: linear-gradient(rgba(128, 128, 128, 1) 0, rgba(128, 128, 128, 1) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .progress-offline .progress-bar-danger {
|
||||||
|
background-image: linear-gradient(rgba(128, 128, 128, 1) 0, rgba(128, 128, 128, 1) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .node-cell.status .status-icon.online {
|
body[theme="dark"] .node-cell.status .status-icon.online {
|
||||||
background-image: linear-gradient(#4d853a 0, #367e36 100%);
|
background-image: linear-gradient(rgba(77, 133, 58, 1) 0, rgba(54, 126, 54, 1) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .node-cell.status .status-icon.offline {
|
body[theme="dark"] .node-cell.status .status-icon.offline {
|
||||||
background-image: linear-gradient(#9b2522 0, #9b2522 100%);
|
background-image: linear-gradient(rgba(155, 37, 34, 1) 0, rgba(161, 38, 35, 1) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .service-status .danger {
|
body[theme="dark"] .service-status .danger {
|
||||||
@@ -149,7 +141,7 @@ body[theme="dark"] .service-status .warning {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .service-day-status-icon {
|
body[theme="dark"] .service-day-status-icon {
|
||||||
background-color: grey;
|
background-color: rgba(128, 128, 128, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] footer p a,
|
body[theme="dark"] footer p a,
|
||||||
@@ -159,7 +151,12 @@ body[theme="dark"] footer p a:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .modal-content{
|
body[theme="dark"] .modal-content{
|
||||||
background-color: #1c1d26;
|
background-color: rgba(28, 29, 38, 1);
|
||||||
|
color: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] #earthChartBox .modal-content{
|
||||||
|
background-color: rgba(0, 0, 0, 1);
|
||||||
color: #f1f1f1;
|
color: #f1f1f1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,6 +164,15 @@ body[theme="dark"] .modal-header{
|
|||||||
border-bottom-color:#080808;
|
border-bottom-color:#080808;
|
||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .modal-header i{
|
body[theme="dark"] .modal-header i.xclose{
|
||||||
color: #f1f1f1;
|
color: #f1f1f1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .toolbox i{
|
||||||
|
color: rgba(241, 241, 241, 1);
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .network-box .network-box-header{
|
||||||
|
border-bottom: 1px solid rgba(110, 112, 121, 0.25);
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
body[theme="dark"]::before {
|
||||||
|
content: "";
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background-image: url(/static/theme-server-status/img/bg.jpg);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 50% 50%;
|
||||||
|
background-size: cover;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] {
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
color: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .navbar {
|
||||||
|
/** 顶部导航条 背景 **/
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
box-shadow: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .navbar .navbar-brand {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .navbar .dropdown-menu {
|
||||||
|
/** 二级导航下拉 背景 **/
|
||||||
|
background-color: rgba(0, 0, 0, 0.85);
|
||||||
|
border-top: none;
|
||||||
|
border-color: #31363b;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.18) 0px 6px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .navbar .dropdown-menu > li > a {
|
||||||
|
color: #c8c3bc;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .navbar .dropdown-menu > li > a:focus,
|
||||||
|
body[theme="dark"] .navbar .dropdown-menu > li > a:hover {
|
||||||
|
/** 二级导航鼠标悬停选中背景 **/
|
||||||
|
background-color: rgba(0, 0, 0, 0.95);
|
||||||
|
background-image: linear-gradient(#1c1d26 0, #1c1d26 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .navbar .navbar-nav .open .dropdown-menu > li > a {
|
||||||
|
color: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .table,
|
||||||
|
body[theme="dark"] .table-condensed > tbody > tr,
|
||||||
|
body[theme="dark"] .table-hover > tbody > tr,
|
||||||
|
body[theme="dark"] .table-hover > tbody > tr:hover,
|
||||||
|
body[theme="dark"] .table-striped tbody > tr.even,
|
||||||
|
body[theme="dark"] .table-striped tbody > tr.odd,
|
||||||
|
body[theme="dark"] .table-striped tbody > tr.even > td,
|
||||||
|
body[theme="dark"] .table-striped tbody > tr.even > th,
|
||||||
|
body[theme="dark"] .table-striped tbody > tr.odd > td,
|
||||||
|
body[theme="dark"] .table-striped tbody > tr.odd > th,
|
||||||
|
body[theme="dark"] .table-striped tbody > tr.even > td:hover,
|
||||||
|
body[theme="dark"] .table-striped tbody > tr.even > th:hover,
|
||||||
|
body[theme="dark"] .table-striped tbody > tr.odd > td:hover,
|
||||||
|
body[theme="dark"] .table-striped tbody > tr.odd > th:hover,
|
||||||
|
body[theme="dark"] .table-striped tbody > tr.expandRow:hover {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .content {
|
||||||
|
/** 主box 背景 **/
|
||||||
|
background-color: rgba(28, 29, 38, 0.8);
|
||||||
|
border: none;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.5) 0 0.625em 2em;
|
||||||
|
-webkit-box-shadow: rgba(0, 0, 0, 0.5) 0 0.625em 2em;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.5) 0 0.625em 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .table > thead > tr.node-group-tag > th,
|
||||||
|
body[theme="dark"] .table > thead > tr.node-group-tag > th:before {
|
||||||
|
background: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .table > tbody > tr > td:before,
|
||||||
|
body[theme="dark"] .table > tfoot > tr > td:before,
|
||||||
|
body[theme="dark"] .table > thead > tr > td:before,
|
||||||
|
body[theme="dark"] .table > thead > tr.node-group-cell > th:before{
|
||||||
|
/** border-bottom 颜色 **/
|
||||||
|
background-color: rgba(155, 155, 155, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .table-hover > tbody > tr:not(.expandRow):hover > td {
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* expandRow展开部分样式 */
|
||||||
|
body[theme="dark"] .table > tbody > tr.expandRow.odd > td:before{
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .table > tbody > tr.expandRow.even > td:before{
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
/* expandRow展开部分样式结束 */
|
||||||
|
|
||||||
|
body[theme="dark"] .progress {
|
||||||
|
background-image: none;
|
||||||
|
background-color: rgba(255, 255, 255, 0.075);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .progress-bar-success {
|
||||||
|
background-image: linear-gradient(to right, rgba(77, 133, 58, 0.9) 0, rgba(54, 126, 54, 0.9) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .progress-bar-warning {
|
||||||
|
background-image: linear-gradient(to right, rgba(153, 95, 13, 0.9) 0, rgba(181, 113, 15, 0.9) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .progress-bar-danger {
|
||||||
|
background-image: linear-gradient(to right, rgba(145, 35, 32, 0.8) 0, rgba(161, 38, 35, 0.8) 100%);
|
||||||
|
}
|
||||||
+108
-44
File diff suppressed because one or more lines are too long
@@ -0,0 +1,134 @@
|
|||||||
|
body[theme="light"]::before {
|
||||||
|
content: "";
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background-image: url(/static/theme-server-status/img/bg.jpg);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 50% 50%;
|
||||||
|
background-size: cover;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] {
|
||||||
|
background-image: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"]::after {
|
||||||
|
content: "";
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .navbar {
|
||||||
|
/** 顶部导航条 背景 **/
|
||||||
|
background-color: rgba(255, 255, 255, 0.9);
|
||||||
|
border-bottom: 1px #cecece solid;
|
||||||
|
box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .navbar .dropdown-menu {
|
||||||
|
/** 二级导航下拉 **/
|
||||||
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
|
border-color: rgba(0, 0, 0, .05);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
body[theme="light"] .navbar .dropdown-menu > li > a:focus,
|
||||||
|
body[theme="light"] .navbar .dropdown-menu > li > a:hover{
|
||||||
|
/** 二级导航鼠标悬停选中背景 **/
|
||||||
|
background-color: rgba(0, 0, 0, .05);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .navbar .navbar-collapse{
|
||||||
|
border-color:rgba(0, 0, 0, 0.085);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .navbar .navbar-toggle .icon-bar{
|
||||||
|
background-color: rgba(0, 0, 0, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .navbar .navbar-toggle:focus,
|
||||||
|
body[theme="light"] .navbar .navbar-toggle:hover {
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .table,
|
||||||
|
body[theme="light"] .table-condensed > tbody > tr,
|
||||||
|
body[theme="light"] .table-hover > tbody > tr,
|
||||||
|
body[theme="light"] .table-hover > tbody > tr:hover,
|
||||||
|
body[theme="light"] .table-striped tbody > tr.even,
|
||||||
|
body[theme="light"] .table-striped tbody > tr.odd,
|
||||||
|
body[theme="light"] .table-striped tbody > tr.even > td,
|
||||||
|
body[theme="light"] .table-striped tbody > tr.even > th,
|
||||||
|
body[theme="light"] .table-striped tbody > tr.odd > td,
|
||||||
|
body[theme="light"] .table-striped tbody > tr.odd > th,
|
||||||
|
body[theme="light"] .table-striped tbody > tr.even > td:hover,
|
||||||
|
body[theme="light"] .table-striped tbody > tr.even > th:hover,
|
||||||
|
body[theme="light"] .table-striped tbody > tr.odd > td:hover,
|
||||||
|
body[theme="light"] .table-striped tbody > tr.odd > th:hover,
|
||||||
|
body[theme="light"] .table-striped tbody > tr.expandRow:hover {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .content {
|
||||||
|
/** 主box 背景 **/
|
||||||
|
background-color: rgba(245, 245, 245, 0.8);
|
||||||
|
border: none;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.5) 0 0.625em 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .table > thead > tr.node-group-tag > th,
|
||||||
|
body[theme="light"] .table > thead > tr.node-group-tag > th:before {
|
||||||
|
background: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .table > tbody > tr > td:before,
|
||||||
|
body[theme="light"] .table > tfoot > tr > td:before,
|
||||||
|
body[theme="light"] .table > thead > tr > td:before,
|
||||||
|
body[theme="light"] .table > thead > tr.node-group-cell > th:before{
|
||||||
|
/** border-bottom 颜色 **/
|
||||||
|
background-color: rgba(0, 0, 0, 0.075);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .table-hover > tbody > tr:not(.expandRow):hover > td {
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* expandRow展开部分样式 */
|
||||||
|
body[theme="light"] .table > tbody > tr.expandRow.odd > td:before{
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .table > tbody > tr.expandRow.even > td:before{
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
/* expandRow展开部分样式结束 */
|
||||||
|
|
||||||
|
body[theme="light"] .progress {
|
||||||
|
background-image: none;
|
||||||
|
background-color: rgba(0, 0, 0, 0.015);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .progress-bar-success {
|
||||||
|
background-image: linear-gradient(to right, rgba(50, 205, 50, 0.6) 0, rgba(68, 157, 68, 0.6) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .progress-bar-warning {
|
||||||
|
background-image: linear-gradient(to right, rgba(236, 151, 31, 0.6) 0, rgba(255, 125, 80, 0.6) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .progress-bar-danger {
|
||||||
|
background-image: linear-gradient(to right, rgba(255, 70, 0, 0.6) 0, rgba(201, 48, 44,0.6) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .toolbox i{
|
||||||
|
background-color: rgba(255, 255, 255, .55);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 767px) {
|
||||||
|
body[theme="light"] .navbar .navbar-nav .open .dropdown-menu {
|
||||||
|
background-color: rgba(235, 235, 235, 0.75);
|
||||||
|
}
|
||||||
|
}
|
||||||
+185
-37
@@ -3,16 +3,22 @@ body {
|
|||||||
padding-bottom: 30px !important;
|
padding-bottom: 30px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.container-fluid{
|
||||||
|
max-width: 1680px;
|
||||||
|
width: 95vw;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nezha {
|
||||||
|
min-height: calc(100vh - 100px);
|
||||||
|
}
|
||||||
|
|
||||||
/* 导航部分 开始*/
|
/* 导航部分 开始*/
|
||||||
.navbar {
|
.navbar {
|
||||||
min-height: 40px !important;
|
min-height: 40px !important;
|
||||||
margin:0 auto;
|
margin:0 auto;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
z-index:99999999;
|
||||||
|
|
||||||
.navbar .container{
|
|
||||||
max-width: 95vw;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-inverse{
|
.navbar-inverse{
|
||||||
@@ -46,8 +52,8 @@ body {
|
|||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar .node-cell-expand-label {
|
.navbar .dropdown a{
|
||||||
/*margin-right: 5px;*/
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar .dropdown .dropdown-toggle {
|
.navbar .dropdown .dropdown-toggle {
|
||||||
@@ -59,7 +65,7 @@ body {
|
|||||||
margin:0px -15px;
|
margin:0px -15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar .navbar-nav>li>a {
|
.navbar .navbar-nav > li > a {
|
||||||
color:#f1f1f1;
|
color:#f1f1f1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,12 +76,10 @@ body {
|
|||||||
.navbar .navbar-collapse{
|
.navbar .navbar-collapse{
|
||||||
max-height: 500px;
|
max-height: 500px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 导航部分 结束 */
|
/* 导航部分 结束 */
|
||||||
|
|
||||||
|
|
||||||
/* toolbox 开始 */
|
/* toolbox 开始 */
|
||||||
|
|
||||||
.toolbox {
|
.toolbox {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom:20px;
|
bottom:20px;
|
||||||
@@ -116,7 +120,6 @@ body {
|
|||||||
.toolbox .showGoTop i.goTop {
|
.toolbox .showGoTop i.goTop {
|
||||||
font-size: 1.55rem;
|
font-size: 1.55rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* toolbox 结束 */
|
/* toolbox 结束 */
|
||||||
|
|
||||||
|
|
||||||
@@ -133,6 +136,10 @@ body {
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-responsive{
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
.table th, .table td {
|
.table th, .table td {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
@@ -162,6 +169,15 @@ body {
|
|||||||
height: 0.7px;
|
height: 0.7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.monitor-detail{
|
||||||
|
margin:5px 0px 10px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.accordion-toggle{
|
||||||
|
cursor:pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table .network-thead-tr th,
|
||||||
.table .node-group-tag th{
|
.table .node-group-tag th{
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
padding-bottom:15px;
|
padding-bottom:15px;
|
||||||
@@ -234,7 +250,7 @@ body {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*加快Agent详情下拉展示速度*/
|
/*agent详情下拉速度*/
|
||||||
.collapsing{
|
.collapsing{
|
||||||
-webkit-transition-property:height,visibility;
|
-webkit-transition-property:height,visibility;
|
||||||
-o-transition-property:height,visibility;
|
-o-transition-property:height,visibility;
|
||||||
@@ -246,13 +262,10 @@ body {
|
|||||||
-o-transition-timing-function: linear;
|
-o-transition-timing-function: linear;
|
||||||
transition-timing-function: linear
|
transition-timing-function: linear
|
||||||
}
|
}
|
||||||
|
|
||||||
/*正文结束*/
|
/*正文结束*/
|
||||||
|
|
||||||
/* 服务页 正文*/
|
/* 服务页 正文*/
|
||||||
.service-status {
|
.service-status {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.service-status .service-status-th{
|
.service-status .service-status-th{
|
||||||
min-width:60px;
|
min-width:60px;
|
||||||
@@ -298,61 +311,184 @@ body {
|
|||||||
.service-status .tooltip-inner {
|
.service-status .tooltip-inner {
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td.ping-network-quality {
|
||||||
|
width: 600px;
|
||||||
|
}
|
||||||
/* 服务页 正文结束 */
|
/* 服务页 正文结束 */
|
||||||
|
|
||||||
|
/* 网络页 正文*/
|
||||||
/* 服务器分布图 */
|
.network-box .btn-group.open .dropdown-toggle{
|
||||||
#mapChartBox{
|
box-shadow:unset;
|
||||||
z-index: 999999999;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content{
|
.network-box .network-box-header{
|
||||||
|
font-size: 18px;
|
||||||
|
padding: 5px 0px 15px 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-box .network-box-header .dropdown-menu {
|
||||||
|
max-height: 22.5em;
|
||||||
|
overflow-y: auto;
|
||||||
|
z-index:99999998;
|
||||||
|
min-width: 200px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-box .network-box-header .dropdown-menu::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-box .network-box-header .dropdown-menu li{
|
||||||
|
height: 2em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-box .network-box-header .dropdown-menu li.input-group{
|
||||||
|
margin: 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
position: sticky;
|
||||||
pointer-events: auto;
|
top: 0;
|
||||||
|
z-index: 99999997;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-box .network-box-header .dropdown-menu li.input-group input{
|
||||||
|
width: 100%;
|
||||||
|
height: 2em;
|
||||||
|
border-top: none;
|
||||||
|
border-left: none;
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-box .network-box-header .dropdown-menu li a {
|
||||||
|
padding: 5px 5px 5px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-box .network-box-header .dropdown-menu li.hidden {
|
||||||
|
height: 0;
|
||||||
|
display: none;
|
||||||
|
visibility:hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-box .network-box-header .dropdown-menu li.visible {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-box .chartTitle {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 18px 0px 15px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-box .chartTitle i.chartCountryCode{
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 12.5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 网络页 正文结束 */
|
||||||
|
|
||||||
|
/* 地图版服务器分布图 */
|
||||||
|
#mapChartBox{
|
||||||
|
z-index: 999999999;
|
||||||
|
padding-left: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-open .modal{
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-dialog-centered{
|
.modal-dialog-centered{
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 1134px;
|
width: 100%;
|
||||||
max-width: 1134px;
|
max-width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center; /* 垂直居中 */
|
align-items: center; /* 垂直居中 */
|
||||||
justify-content: center; /* 水平居中 */
|
justify-content: center; /* 水平居中 */
|
||||||
min-height: calc(100%) /* 确保模态框占据整个视口高度 */
|
min-height: calc(100%) /* 确保模态框占据整个视口高度 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-content{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
pointer-events: auto;
|
||||||
|
border-radius: unset;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
.modal-header{
|
.modal-header{
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header i{
|
.modal-header h4{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header i.xclose{
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
font-size: 2em;
|
font-size: 30px;
|
||||||
top: 13px;
|
top: 12px;
|
||||||
right: 13px;
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body{
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.template-item, a.cdn-item{
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer{
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer p{
|
||||||
|
text-align: center;
|
||||||
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 1200px) {
|
@media only screen and (max-width: 1200px) {
|
||||||
.accordian-body{
|
.accordian-body{
|
||||||
margin: 5px 0px 5px 10px;
|
margin: 5px 0px 5px 10px;
|
||||||
}
|
}
|
||||||
|
table.monitor-detail{
|
||||||
|
margin:0px 0px 2px 0px;
|
||||||
|
}
|
||||||
|
.table .network-thead-tr th,
|
||||||
.table .node-group-tag th{
|
.table .node-group-tag th{
|
||||||
font-size:16px;
|
font-size:16px;
|
||||||
padding-bottom:6px;
|
padding-bottom:6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 彩虹旗 */
|
||||||
|
.fi-rb {
|
||||||
|
background-image: url(/static/theme-server-status/img/rb.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@media only screen and (max-width: 767px) {
|
@media only screen and (max-width: 767px) {
|
||||||
body {
|
body {
|
||||||
font-size: 10px !important;
|
font-size: 10px !important;
|
||||||
padding-top:60px !important;
|
padding-top:60px !important;
|
||||||
}
|
}
|
||||||
|
.nezha {
|
||||||
|
min-height: calc(100vh - 80px);
|
||||||
|
}
|
||||||
.content {
|
.content {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
footer{
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.cycle-transfer-stats .dataTables_wrapper{
|
||||||
|
margin-bottom: -10px;
|
||||||
|
}
|
||||||
.navbar .navbar-nav .open .dropdown-menu>li>a {
|
.navbar .navbar-nav .open .dropdown-menu>li>a {
|
||||||
color: #f1f1f1;
|
color: #f1f1f1;
|
||||||
}
|
}
|
||||||
@@ -387,10 +523,17 @@ body {
|
|||||||
.accordian-body{
|
.accordian-body{
|
||||||
margin: 5px 0px 5px 10px;
|
margin: 5px 0px 5px 10px;
|
||||||
}
|
}
|
||||||
|
table.monitor-detail{
|
||||||
|
margin:0px 0px 2px 0px;
|
||||||
|
}
|
||||||
|
.table .network-thead-tr th,
|
||||||
.table .node-group-tag th{
|
.table .node-group-tag th{
|
||||||
font-size:16px;
|
font-size:14px;
|
||||||
padding-bottom:6px;
|
padding-bottom:6px;
|
||||||
}
|
}
|
||||||
|
.table tr.network td{
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
.service-status .service-status-th{
|
.service-status .service-status-th{
|
||||||
min-width:30px;
|
min-width:30px;
|
||||||
}
|
}
|
||||||
@@ -414,15 +557,20 @@ body {
|
|||||||
.toolbox {
|
.toolbox {
|
||||||
right: 18px;
|
right: 18px;
|
||||||
}
|
}
|
||||||
.modal-dialog-centered{
|
td.ping-network-quality {
|
||||||
width: 95%;
|
width: 110px;
|
||||||
}
|
}
|
||||||
.modal-title{
|
.network-box .network-box-header {
|
||||||
line-height: 0.5;
|
margin: 8px 0px 0px 8px;
|
||||||
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
.modal-header i{
|
.network-box .chartTitle {
|
||||||
top: 8px;
|
font-size: 16px;
|
||||||
right: 8px;
|
margin: 10px 0px 10px 0px;
|
||||||
|
}
|
||||||
|
.network-box .chartTitle i.chartCountryCode{
|
||||||
|
font-size: 15px;
|
||||||
|
border-radius: 12.5%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 262 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
-326
@@ -1,326 +0,0 @@
|
|||||||
/**
|
|
||||||
* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
|
|
||||||
*/
|
|
||||||
;(function(window, document) {
|
|
||||||
/*jshint evil:true */
|
|
||||||
/** version */
|
|
||||||
var version = '3.7.3';
|
|
||||||
|
|
||||||
/** Preset options */
|
|
||||||
var options = window.html5 || {};
|
|
||||||
|
|
||||||
/** Used to skip problem elements */
|
|
||||||
var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i;
|
|
||||||
|
|
||||||
/** Not all elements can be cloned in IE **/
|
|
||||||
var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i;
|
|
||||||
|
|
||||||
/** Detect whether the browser supports default html5 styles */
|
|
||||||
var supportsHtml5Styles;
|
|
||||||
|
|
||||||
/** Name of the expando, to work with multiple documents or to re-shiv one document */
|
|
||||||
var expando = '_html5shiv';
|
|
||||||
|
|
||||||
/** The id for the documents expando */
|
|
||||||
var expanID = 0;
|
|
||||||
|
|
||||||
/** Cached data for each document */
|
|
||||||
var expandoData = {};
|
|
||||||
|
|
||||||
/** Detect whether the browser supports unknown elements */
|
|
||||||
var supportsUnknownElements;
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
try {
|
|
||||||
var a = document.createElement('a');
|
|
||||||
a.innerHTML = '<xyz></xyz>';
|
|
||||||
//if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles
|
|
||||||
supportsHtml5Styles = ('hidden' in a);
|
|
||||||
|
|
||||||
supportsUnknownElements = a.childNodes.length == 1 || (function() {
|
|
||||||
// assign a false positive if unable to shiv
|
|
||||||
(document.createElement)('a');
|
|
||||||
var frag = document.createDocumentFragment();
|
|
||||||
return (
|
|
||||||
typeof frag.cloneNode == 'undefined' ||
|
|
||||||
typeof frag.createDocumentFragment == 'undefined' ||
|
|
||||||
typeof frag.createElement == 'undefined'
|
|
||||||
);
|
|
||||||
}());
|
|
||||||
} catch(e) {
|
|
||||||
// assign a false positive if detection fails => unable to shiv
|
|
||||||
supportsHtml5Styles = true;
|
|
||||||
supportsUnknownElements = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}());
|
|
||||||
|
|
||||||
/*--------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a style sheet with the given CSS text and adds it to the document.
|
|
||||||
* @private
|
|
||||||
* @param {Document} ownerDocument The document.
|
|
||||||
* @param {String} cssText The CSS text.
|
|
||||||
* @returns {StyleSheet} The style element.
|
|
||||||
*/
|
|
||||||
function addStyleSheet(ownerDocument, cssText) {
|
|
||||||
var p = ownerDocument.createElement('p'),
|
|
||||||
parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement;
|
|
||||||
|
|
||||||
p.innerHTML = 'x<style>' + cssText + '</style>';
|
|
||||||
return parent.insertBefore(p.lastChild, parent.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value of `html5.elements` as an array.
|
|
||||||
* @private
|
|
||||||
* @returns {Array} An array of shived element node names.
|
|
||||||
*/
|
|
||||||
function getElements() {
|
|
||||||
var elements = html5.elements;
|
|
||||||
return typeof elements == 'string' ? elements.split(' ') : elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extends the built-in list of html5 elements
|
|
||||||
* @memberOf html5
|
|
||||||
* @param {String|Array} newElements whitespace separated list or array of new element names to shiv
|
|
||||||
* @param {Document} ownerDocument The context document.
|
|
||||||
*/
|
|
||||||
function addElements(newElements, ownerDocument) {
|
|
||||||
var elements = html5.elements;
|
|
||||||
if(typeof elements != 'string'){
|
|
||||||
elements = elements.join(' ');
|
|
||||||
}
|
|
||||||
if(typeof newElements != 'string'){
|
|
||||||
newElements = newElements.join(' ');
|
|
||||||
}
|
|
||||||
html5.elements = elements +' '+ newElements;
|
|
||||||
shivDocument(ownerDocument);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the data associated to the given document
|
|
||||||
* @private
|
|
||||||
* @param {Document} ownerDocument The document.
|
|
||||||
* @returns {Object} An object of data.
|
|
||||||
*/
|
|
||||||
function getExpandoData(ownerDocument) {
|
|
||||||
var data = expandoData[ownerDocument[expando]];
|
|
||||||
if (!data) {
|
|
||||||
data = {};
|
|
||||||
expanID++;
|
|
||||||
ownerDocument[expando] = expanID;
|
|
||||||
expandoData[expanID] = data;
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns a shived element for the given nodeName and document
|
|
||||||
* @memberOf html5
|
|
||||||
* @param {String} nodeName name of the element
|
|
||||||
* @param {Document|DocumentFragment} ownerDocument The context document.
|
|
||||||
* @returns {Object} The shived element.
|
|
||||||
*/
|
|
||||||
function createElement(nodeName, ownerDocument, data){
|
|
||||||
if (!ownerDocument) {
|
|
||||||
ownerDocument = document;
|
|
||||||
}
|
|
||||||
if(supportsUnknownElements){
|
|
||||||
return ownerDocument.createElement(nodeName);
|
|
||||||
}
|
|
||||||
if (!data) {
|
|
||||||
data = getExpandoData(ownerDocument);
|
|
||||||
}
|
|
||||||
var node;
|
|
||||||
|
|
||||||
if (data.cache[nodeName]) {
|
|
||||||
node = data.cache[nodeName].cloneNode();
|
|
||||||
} else if (saveClones.test(nodeName)) {
|
|
||||||
node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode();
|
|
||||||
} else {
|
|
||||||
node = data.createElem(nodeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Avoid adding some elements to fragments in IE < 9 because
|
|
||||||
// * Attributes like `name` or `type` cannot be set/changed once an element
|
|
||||||
// is inserted into a document/fragment
|
|
||||||
// * Link elements with `src` attributes that are inaccessible, as with
|
|
||||||
// a 403 response, will cause the tab/window to crash
|
|
||||||
// * Script elements appended to fragments will execute when their `src`
|
|
||||||
// or `text` property is set
|
|
||||||
return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns a shived DocumentFragment for the given document
|
|
||||||
* @memberOf html5
|
|
||||||
* @param {Document} ownerDocument The context document.
|
|
||||||
* @returns {Object} The shived DocumentFragment.
|
|
||||||
*/
|
|
||||||
function createDocumentFragment(ownerDocument, data){
|
|
||||||
if (!ownerDocument) {
|
|
||||||
ownerDocument = document;
|
|
||||||
}
|
|
||||||
if(supportsUnknownElements){
|
|
||||||
return ownerDocument.createDocumentFragment();
|
|
||||||
}
|
|
||||||
data = data || getExpandoData(ownerDocument);
|
|
||||||
var clone = data.frag.cloneNode(),
|
|
||||||
i = 0,
|
|
||||||
elems = getElements(),
|
|
||||||
l = elems.length;
|
|
||||||
for(;i<l;i++){
|
|
||||||
clone.createElement(elems[i]);
|
|
||||||
}
|
|
||||||
return clone;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shivs the `createElement` and `createDocumentFragment` methods of the document.
|
|
||||||
* @private
|
|
||||||
* @param {Document|DocumentFragment} ownerDocument The document.
|
|
||||||
* @param {Object} data of the document.
|
|
||||||
*/
|
|
||||||
function shivMethods(ownerDocument, data) {
|
|
||||||
if (!data.cache) {
|
|
||||||
data.cache = {};
|
|
||||||
data.createElem = ownerDocument.createElement;
|
|
||||||
data.createFrag = ownerDocument.createDocumentFragment;
|
|
||||||
data.frag = data.createFrag();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ownerDocument.createElement = function(nodeName) {
|
|
||||||
//abort shiv
|
|
||||||
if (!html5.shivMethods) {
|
|
||||||
return data.createElem(nodeName);
|
|
||||||
}
|
|
||||||
return createElement(nodeName, ownerDocument, data);
|
|
||||||
};
|
|
||||||
|
|
||||||
ownerDocument.createDocumentFragment = Function('h,f', 'return function(){' +
|
|
||||||
'var n=f.cloneNode(),c=n.createElement;' +
|
|
||||||
'h.shivMethods&&(' +
|
|
||||||
// unroll the `createElement` calls
|
|
||||||
getElements().join().replace(/[\w\-:]+/g, function(nodeName) {
|
|
||||||
data.createElem(nodeName);
|
|
||||||
data.frag.createElement(nodeName);
|
|
||||||
return 'c("' + nodeName + '")';
|
|
||||||
}) +
|
|
||||||
');return n}'
|
|
||||||
)(html5, data.frag);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*--------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shivs the given document.
|
|
||||||
* @memberOf html5
|
|
||||||
* @param {Document} ownerDocument The document to shiv.
|
|
||||||
* @returns {Document} The shived document.
|
|
||||||
*/
|
|
||||||
function shivDocument(ownerDocument) {
|
|
||||||
if (!ownerDocument) {
|
|
||||||
ownerDocument = document;
|
|
||||||
}
|
|
||||||
var data = getExpandoData(ownerDocument);
|
|
||||||
|
|
||||||
if (html5.shivCSS && !supportsHtml5Styles && !data.hasCSS) {
|
|
||||||
data.hasCSS = !!addStyleSheet(ownerDocument,
|
|
||||||
// corrects block display not defined in IE6/7/8/9
|
|
||||||
'article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}' +
|
|
||||||
// adds styling not present in IE6/7/8/9
|
|
||||||
'mark{background:#FF0;color:#000}' +
|
|
||||||
// hides non-rendered elements
|
|
||||||
'template{display:none}'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!supportsUnknownElements) {
|
|
||||||
shivMethods(ownerDocument, data);
|
|
||||||
}
|
|
||||||
return ownerDocument;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*--------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `html5` object is exposed so that more elements can be shived and
|
|
||||||
* existing shiving can be detected on iframes.
|
|
||||||
* @type Object
|
|
||||||
* @example
|
|
||||||
*
|
|
||||||
* // options can be changed before the script is included
|
|
||||||
* html5 = { 'elements': 'mark section', 'shivCSS': false, 'shivMethods': false };
|
|
||||||
*/
|
|
||||||
var html5 = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An array or space separated string of node names of the elements to shiv.
|
|
||||||
* @memberOf html5
|
|
||||||
* @type Array|String
|
|
||||||
*/
|
|
||||||
'elements': options.elements || 'abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* current version of html5shiv
|
|
||||||
*/
|
|
||||||
'version': version,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A flag to indicate that the HTML5 style sheet should be inserted.
|
|
||||||
* @memberOf html5
|
|
||||||
* @type Boolean
|
|
||||||
*/
|
|
||||||
'shivCSS': (options.shivCSS !== false),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is equal to true if a browser supports creating unknown/HTML5 elements
|
|
||||||
* @memberOf html5
|
|
||||||
* @type boolean
|
|
||||||
*/
|
|
||||||
'supportsUnknownElements': supportsUnknownElements,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A flag to indicate that the document's `createElement` and `createDocumentFragment`
|
|
||||||
* methods should be overwritten.
|
|
||||||
* @memberOf html5
|
|
||||||
* @type Boolean
|
|
||||||
*/
|
|
||||||
'shivMethods': (options.shivMethods !== false),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A string to describe the type of `html5` object ("default" or "default print").
|
|
||||||
* @memberOf html5
|
|
||||||
* @type String
|
|
||||||
*/
|
|
||||||
'type': 'default',
|
|
||||||
|
|
||||||
// shivs the document according to the specified `html5` object options
|
|
||||||
'shivDocument': shivDocument,
|
|
||||||
|
|
||||||
//creates a shived element
|
|
||||||
createElement: createElement,
|
|
||||||
|
|
||||||
//creates a shived documentFragment
|
|
||||||
createDocumentFragment: createDocumentFragment,
|
|
||||||
|
|
||||||
//extends list of elements
|
|
||||||
addElements: addElements
|
|
||||||
};
|
|
||||||
|
|
||||||
/*--------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
// expose html5
|
|
||||||
window.html5 = html5;
|
|
||||||
|
|
||||||
// shiv the document
|
|
||||||
shivDocument(document);
|
|
||||||
|
|
||||||
if(typeof module == 'object' && module.exports){
|
|
||||||
module.exports = html5;
|
|
||||||
}
|
|
||||||
|
|
||||||
}(typeof window !== "undefined" ? window : this, document));
|
|
||||||
+29
-3
@@ -1,24 +1,31 @@
|
|||||||
const mixinsVue = {
|
const mixinsVue = {
|
||||||
data: {
|
data: {
|
||||||
cache: [],
|
cache: [],
|
||||||
|
isMobile: false,
|
||||||
theme: "light",
|
theme: "light",
|
||||||
isSystemTheme: false,
|
isSystemTheme: false,
|
||||||
showGroup: false,
|
showGroup: false,
|
||||||
showGoTop: false,
|
showGoTop: false,
|
||||||
|
showTools: false,
|
||||||
preferredTemplate: null,
|
preferredTemplate: null,
|
||||||
isMobile: false,
|
semiTransparent: false,
|
||||||
staticUrl: '/static/theme-server-status',
|
staticUrl: '/static/theme-server-status',
|
||||||
adaptedTemplates: [
|
adaptedTemplates: [
|
||||||
{ key: 'default', name: 'Default', icon: 'th large' },
|
{ key: 'default', name: 'Default', icon: 'th large' },
|
||||||
{ key: 'angel-kanade', name: 'AngelKanade', icon: 'square' },
|
{ key: 'angel-kanade', name: 'AngelKanade', icon: 'square' },
|
||||||
{ key: 'server-status', name: 'ServerStatus', icon: 'list' }
|
{ key: 'server-status', name: 'ServerStatus', icon: 'list' }
|
||||||
]
|
],
|
||||||
|
colors: [],
|
||||||
|
colorsDark: ['#4992FF', '#08C091', '#FDDD5F', '#FF6E76', '#58D9F9', '#7CFFB2', '#FF8A44', '#8D48E3', '#DD79FF', '#5470C6', '#3BA272', '#FAC758', '#EE6666', '#72C0DE', '#91CC76', '#FB8352', '#9A60B4', '#EA7BCC'],
|
||||||
|
colorsLight: ['#5470C6', '#3BA272', '#FAC758', '#EE6666', '#72C0DE', '#91CC76', '#FB8352', '#9A60B4', '#EA7BCC', '#4992FF', '#08C091', '#FDDD5F', '#FF6E76', '#58D9F9', '#7CFFB2', '#FF8A44', '#8D48E3', '#DD79FF'],
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.isMobile = this.checkIsMobile();
|
this.isMobile = this.checkIsMobile();
|
||||||
this.theme = this.initTheme();
|
this.theme = this.initTheme();
|
||||||
this.showGroup = this.initShowGroup();
|
this.showGroup = this.initShowGroup();
|
||||||
|
this.semiTransparent = this.initSemiTransparent();
|
||||||
this.preferredTemplate = this.getCookie('preferred_theme') ? this.getCookie('preferred_theme') : this.$root.defaultTemplate;
|
this.preferredTemplate = this.getCookie('preferred_theme') ? this.getCookie('preferred_theme') : this.$root.defaultTemplate;
|
||||||
|
this.colors = this.theme == "dark" ? this.colorsDark : this.colorsLight;
|
||||||
window.addEventListener('scroll', this.handleScroll);
|
window.addEventListener('scroll', this.handleScroll);
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
@@ -32,6 +39,9 @@ const mixinsVue = {
|
|||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
toggleShowTools() {
|
||||||
|
this.showTools = !this.showTools;
|
||||||
|
},
|
||||||
initTheme() {
|
initTheme() {
|
||||||
const storedTheme = localStorage.getItem("theme");
|
const storedTheme = localStorage.getItem("theme");
|
||||||
const theme = (storedTheme === 'dark' || storedTheme === 'light') ? storedTheme : (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
const theme = (storedTheme === 'dark' || storedTheme === 'light') ? storedTheme : (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
||||||
@@ -45,7 +55,7 @@ const mixinsVue = {
|
|||||||
// 重新赋值全局调色
|
// 重新赋值全局调色
|
||||||
this.colors = this.theme == "dark" ? this.colorsDark : this.colorsLight;
|
this.colors = this.theme == "dark" ? this.colorsDark : this.colorsLight;
|
||||||
|
|
||||||
if(this.$root.page == 'index') {
|
if(this.$root.page == 'index' || this.$root.page == 'network') {
|
||||||
this.reloadCharts(); // 重新载入echarts图表
|
this.reloadCharts(); // 重新载入echarts图表
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -64,6 +74,21 @@ const mixinsVue = {
|
|||||||
this.$root.initTooltip();
|
this.$root.initTooltip();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
initSemiTransparent() {
|
||||||
|
const storedSemiTransparent = localStorage.getItem("semiTransparent");
|
||||||
|
const semiTransparent = storedSemiTransparent !== null ? JSON.parse(storedSemiTransparent) : false;
|
||||||
|
if (storedSemiTransparent === null) {
|
||||||
|
localStorage.setItem("semiTransparent", semiTransparent);
|
||||||
|
}
|
||||||
|
return semiTransparent;
|
||||||
|
},
|
||||||
|
toggleSemiTransparent(){
|
||||||
|
this.semiTransparent = !this.semiTransparent;
|
||||||
|
localStorage.setItem("semiTransparent", this.semiTransparent);
|
||||||
|
if(this.$root.page == 'index' || this.$root.page == 'network') {
|
||||||
|
this.reloadCharts(); // 重新载入echarts图表
|
||||||
|
}
|
||||||
|
},
|
||||||
updateCookie(name, value) {
|
updateCookie(name, value) {
|
||||||
document.cookie = name + "=" + value +"; path=/";
|
document.cookie = name + "=" + value +"; path=/";
|
||||||
},
|
},
|
||||||
@@ -106,6 +131,7 @@ const mixinsVue = {
|
|||||||
},
|
},
|
||||||
handleScroll() {
|
handleScroll() {
|
||||||
this.showGoTop = window.scrollY >= 100;
|
this.showGoTop = window.scrollY >= 100;
|
||||||
|
if(this.showTools) this.showTools = false;
|
||||||
},
|
},
|
||||||
groupingData(data, field) {
|
groupingData(data, field) {
|
||||||
let map = new Map();
|
let map = new Map();
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
/*! Respond.js v1.4.2: min/max-width media query polyfill * Copyright 2013 Scott Jehl
|
|
||||||
* Licensed under https://github.com/scottjehl/Respond/blob/master/LICENSE-MIT
|
|
||||||
* */
|
|
||||||
|
|
||||||
!function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='­<style media="'+a+'"> #mq-test-1 { width: 42px; }</style>',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){u(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))};if(c.ajax=f,c.queue=d,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/,maxw:/\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var g,h,i,j=a.document,k=j.documentElement,l=[],m=[],n=[],o={},p=30,q=j.getElementsByTagName("head")[0]||k,r=j.getElementsByTagName("base")[0],s=q.getElementsByTagName("link"),t=function(){var a,b=j.createElement("div"),c=j.body,d=k.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=j.createElement("body"),c.style.background="none"),k.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&k.insertBefore(c,k.firstChild),a=b.offsetWidth,f?k.removeChild(c):c.removeChild(b),k.style.fontSize=d,e&&(c.style.fontSize=e),a=i=parseFloat(a)},u=function(b){var c="clientWidth",d=k[c],e="CSS1Compat"===j.compatMode&&d||j.body[c]||d,f={},o=s[s.length-1],r=(new Date).getTime();if(b&&g&&p>r-g)return a.clearTimeout(h),h=a.setTimeout(u,p),void 0;g=r;for(var v in l)if(l.hasOwnProperty(v)){var w=l[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?i||t():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?i||t():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(m[w.rules]))}for(var C in n)n.hasOwnProperty(C)&&n[C]&&n[C].parentNode===q&&q.removeChild(n[C]);n.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=j.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,q.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(j.createTextNode(F)),n.push(E)}},v=function(a,b,d){var e=a.replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var g=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},h=!f&&d;b.length&&(b+="/"),h&&(f=1);for(var i=0;f>i;i++){var j,k,n,o;h?(j=d,m.push(g(a))):(j=e[i].match(c.regex.findStyles)&&RegExp.$1,m.push(RegExp.$2&&g(RegExp.$2))),n=j.split(","),o=n.length;for(var p=0;o>p;p++)k=n[p],l.push({media:k.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:m.length-1,hasquery:k.indexOf("(")>-1,minw:k.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:k.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},w=function(){if(d.length){var b=d.shift();f(b.href,function(c){v(c,b.href,b.media),o[b.href]=!0,a.setTimeout(function(){w()},0)})}},x=function(){for(var b=0;b<s.length;b++){var c=s[b],e=c.href,f=c.media,g=c.rel&&"stylesheet"===c.rel.toLowerCase();e&&g&&!o[e]&&(c.styleSheet&&c.styleSheet.rawCssText?(v(c.styleSheet.rawCssText,e,f),o[e]=!0):(!/^([a-zA-Z:]*\/\/)/.test(e)&&!r||e.replace(RegExp.$1,"").split("/")[0]===a.location.host)&&("//"===e.substring(0,2)&&(e=a.location.protocol+e),d.push({href:e,media:f})))}w()};x(),c.update=x,c.getEmValue=t,a.addEventListener?a.addEventListener("resize",b,!1):a.attachEvent&&a.attachEvent("onresize",b)}}(this);
|
|
||||||
Vendored
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
<script src="https://unpkg.com/[email protected]/dist/semantic.min.js"></script>
|
<script src="https://unpkg.com/[email protected]/dist/semantic.min.js"></script>
|
||||||
<script src="/static/semantic-ui-alerts.min.js"></script>
|
<script src="/static/semantic-ui-alerts.min.js"></script>
|
||||||
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
|
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
|
||||||
<script src="/static/main.js?v20240330"></script>
|
<script src="/static/main.js?v20240714"></script>
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
updateLang({{.LANG }});
|
updateLang({{.LANG }});
|
||||||
|
|||||||
Vendored
+1
@@ -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 "/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 "/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 "/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">
|
<a class='item{{if eq .MatchedPath "/setting"}} active{{end}}' href="/setting">
|
||||||
<i class="settings icon"></i>{{tr "Settings"}}
|
<i class="settings icon"></i>{{tr "Settings"}}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
Vendored
+31
@@ -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}}
|
||||||
+54
@@ -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}}
|
||||||
+1
-1
@@ -60,7 +60,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="ui icon green mini button"
|
<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"}}">
|
data-tooltip="{{tr "ClickToCopy"}}">
|
||||||
<i class="linux icon"></i>
|
<i class="linux icon"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@
|
|||||||
v-if="server.Host.Virtualization">@#server.Host.Virtualization#@:</span>@#server.Host.Arch#@]<br />
|
v-if="server.Host.Virtualization">@#server.Host.Virtualization#@:</span>@#server.Host.Arch#@]<br />
|
||||||
CPU: @#server.Host.CPU#@<br />
|
CPU: @#server.Host.CPU#@<br />
|
||||||
<template v-if="server.Host.GPU">
|
<template v-if="server.Host.GPU">
|
||||||
GPU: @#server.Host.GPU#@
|
GPU: @#server.Host.GPU#@<br />
|
||||||
</template>
|
</template>
|
||||||
{{tr "DiskUsed"}}:
|
{{tr "DiskUsed"}}:
|
||||||
@#formatByteSize(server.State.DiskUsed)#@/@#formatByteSize(server.Host.DiskTotal)#@<br />
|
@#formatByteSize(server.State.DiskUsed)#@/@#formatByteSize(server.Host.DiskTotal)#@<br />
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
{{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>
|
|
||||||
<aside class="toolbox">
|
|
||||||
<span v-if="page=='index' && countryMapChartData.length!=0" class="showMapChart">
|
|
||||||
<i @click="showMapChart" data-toggle="modal" data-target="#mapChartBox" class="bi bi-geo-alt"></i>
|
|
||||||
</span>
|
|
||||||
<span class="toggleView">
|
|
||||||
<i v-if="showGroup" @click="toggleShowGroup" class="show-nogroup bi bi-justify"></i>
|
|
||||||
<i v-else @click="toggleShowGroup" class="show-group bi bi-view-stacked"></i>
|
|
||||||
</span>
|
|
||||||
<span class="setTheme">
|
|
||||||
<i v-if="theme === 'light'" @click="setTheme('dark')" class="setTheme-dark bi bi-moon-fill"></i>
|
|
||||||
<i v-else @click="setTheme('light')" class="setTheme-light bi bi-brightness-high-fill"></i>
|
|
||||||
</span>
|
|
||||||
<span v-if="showGoTop" class="showGoTop">
|
|
||||||
<i @click="goTop" class="goTop bi bi-arrow-up"></i>
|
|
||||||
</span>
|
|
||||||
</aside>
|
|
||||||
{{end}}
|
|
||||||
@@ -1,4 +1,38 @@
|
|||||||
{{define "theme-server-status/footer"}}
|
{{define "theme-server-status/footer"}}
|
||||||
|
</div>
|
||||||
|
<footer class="container-fluid">
|
||||||
|
<p>{{ .Conf.Site.Brand }} | Theme ServerStatus | Powered by <a target="_blank" href="https://github.com/naiba/nezha">{{tr "NezhaMonitoring"}}</a> {{.Version}}</p>
|
||||||
|
</footer>
|
||||||
|
<aside class="toolbox">
|
||||||
|
<template v-if="showTools">
|
||||||
|
<span v-if="page=='index' && countryMapChartData.length!=0" class="showMapChart">
|
||||||
|
<i @click="showMapChart" data-toggle="modal" data-target="#mapChartBox" class="bi bi-geo-alt"></i>
|
||||||
|
</span>
|
||||||
|
<span class="toggleView">
|
||||||
|
<i v-if="showGroup" @click="toggleShowGroup" class="show-nogroup bi bi-justify"></i>
|
||||||
|
<i v-else @click="toggleShowGroup" class="show-group bi bi-view-stacked"></i>
|
||||||
|
</span>
|
||||||
|
<span class="toggleSemiTransparent" @click="toggleSemiTransparent">
|
||||||
|
<i class="bi" :class="semiTransparent ? 'bi-droplet' : 'bi-droplet-half'"></i>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<span v-if="!showTools">
|
||||||
|
<i @click="toggleShowTools" class="bi bi-three-dots"></i>
|
||||||
|
</span>
|
||||||
|
<span class="setTheme">
|
||||||
|
<i v-if="theme === 'light'" @click="setTheme('dark')" class="setTheme-dark bi bi-moon-fill"></i>
|
||||||
|
<i v-else @click="setTheme('light')" class="setTheme-light bi bi-brightness-high-fill"></i>
|
||||||
|
</span>
|
||||||
|
<span v-if="showGoTop" class="showGoTop">
|
||||||
|
<i @click="goTop" class="goTop bi bi-arrow-up"></i>
|
||||||
|
</span>
|
||||||
|
</aside>
|
||||||
|
<template v-if="semiTransparent">
|
||||||
|
<link rel="stylesheet" href="/static/theme-server-status/css/light.plus.css?v20240807">
|
||||||
|
<link rel="stylesheet" href="/static/theme-server-status/css/dark.plus.css?v20240807">
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
{{if ts .CustomCode}}{{.CustomCode|safe}}{{end}}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
{{end}}
|
{{end}}
|
||||||
+5
-13
@@ -8,10 +8,6 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="shortcut icon" type="image/png" href="/static/logo.svg" />
|
<link rel="shortcut icon" type="image/png" href="/static/logo.svg" />
|
||||||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/css/bootstrap.min.css">
|
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/css/bootstrap.min.css">
|
||||||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/css/bootstrap-theme.min.css">
|
|
||||||
<link rel="stylesheet" href="/static/theme-server-status/css/main.css?v20240712">
|
|
||||||
<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://unpkg.com/[email protected]/font/bootstrap-icons.min.css">
|
<link rel="stylesheet" href="https://unpkg.com/[email protected]/font/bootstrap-icons.min.css">
|
||||||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/assets/font-logos.css">
|
<link rel="stylesheet" href="https://unpkg.com/[email protected]/assets/font-logos.css">
|
||||||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/css/flag-icons.min.css">
|
<link rel="stylesheet" href="https://unpkg.com/[email protected]/css/flag-icons.min.css">
|
||||||
@@ -20,17 +16,13 @@
|
|||||||
<script src="https://unpkg.com/[email protected]/dist/js/bootstrap.min.js"></script>
|
<script src="https://unpkg.com/[email protected]/dist/js/bootstrap.min.js"></script>
|
||||||
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
|
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
|
||||||
<script src="https://unpkg.com/[email protected]/dist/echarts.min.js"></script>
|
<script src="https://unpkg.com/[email protected]/dist/echarts.min.js"></script>
|
||||||
<script src="/static/theme-server-status/js/mixin.js?v20240711"></script>
|
<link rel="stylesheet" href="/static/theme-server-status/css/main.css?v202408011">
|
||||||
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
<link rel="stylesheet" href="/static/theme-server-status/css/dark.css?v202408011">
|
||||||
<!--[if lt IE 9]>
|
<link rel="stylesheet" href="/static/theme-server-status/css/light.css?v20240811">
|
||||||
<script src="/static/theme-server-status/js/html5shiv.js"></script>
|
<script src="/static/theme-server-status/js/mixin.js?v20240811"></script>
|
||||||
<script src="/static/theme-server-status/js/respond.min.js"></script>
|
|
||||||
<![endif]-->
|
|
||||||
{{if ts .CustomCode}}
|
|
||||||
{{.CustomCode|safe}}
|
|
||||||
{{end}}
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div id="app">
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+54
-46
@@ -1,34 +1,35 @@
|
|||||||
{{define "theme-server-status/home"}}
|
{{define "theme-server-status/home"}}
|
||||||
{{template "theme-server-status/header" .}}
|
{{template "theme-server-status/header" .}}
|
||||||
<div id="app">
|
{{template "theme-server-status/menu" .}}
|
||||||
{{template "theme-server-status/content-nav" .}}
|
<!-- showGroup true -->
|
||||||
<!-- showGroup true -->
|
<template v-if="showGroup">
|
||||||
<template v-if="showGroup">
|
<section class="container-fluid table-responsive content" v-for="group in nodesTag">
|
||||||
<section class="container table-responsive content" style="max-width: 95vw" v-for="group in nodesTag">
|
{{template "theme-server-status/home-group-true" .}}
|
||||||
{{template "theme-server-status/home-group-true" .}}
|
</section>
|
||||||
</section>
|
</template>
|
||||||
</template>
|
<!-- showGroup false -->
|
||||||
<!-- showGroup false -->
|
<template v-else>
|
||||||
<template v-else>
|
<section class="container-fluid table-responsive content">
|
||||||
<section class="container table-responsive content" style="max-width: 95vw">
|
{{template "theme-server-status/home-group-false" .}}
|
||||||
{{template "theme-server-status/home-group-false" .}}
|
</section>
|
||||||
</section>
|
</template>
|
||||||
</template>
|
<div class="modal fade" id="mapChartBox" tabindex="-1" role="dialog" aria-labelledby="mapChartTitle" aria-hidden="true" style="padding-left:0">
|
||||||
<div class="modal fade" id="mapChartBox" tabindex="-1" role="dialog" aria-labelledby="mapChartTitle" aria-hidden="true">
|
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
<div class="modal-content">
|
||||||
<div class="modal-content">
|
<div class="modal-header">
|
||||||
<div class="modal-header">
|
<h4 class="modal-title" id="mapChartTitle">
|
||||||
<h4 class="modal-title" id="mapChartTitle">{{tr "ServersOnWorldMap"}}</h4>
|
<i class="bi bi-geo-alt"></i>
|
||||||
<i class="bi bi-x" data-dismiss="modal" aria-label="Close"></i>
|
<span>{{tr "ServersOnWorldMap"}}</span>
|
||||||
</div>
|
</h4>
|
||||||
<div class="modal-body">
|
<i class="bi bi-x xclose" data-dismiss="modal" aria-label="Close"></i>
|
||||||
<div id="mapChart" style="width:100%;height:auto;"></div>
|
</div>
|
||||||
</div>
|
<div class="modal-body">
|
||||||
|
<div id="mapChart"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{template "theme-server-status/content-footer" .}}
|
|
||||||
</div>
|
</div>
|
||||||
|
{{template "theme-server-status/footer" .}}
|
||||||
<script>
|
<script>
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
@@ -154,14 +155,13 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const unit = this.language=='zh-CN' ? '台' : 'servers';
|
const unit = this.language=='zh-CN' ? '台' : 'servers';
|
||||||
const isMobile = this.checkIsMobile();
|
const width = window.innerWidth;
|
||||||
const width = isMobile ? 338 : 1102;
|
const height = 0.95 * window.innerHeight;
|
||||||
const height = isMobile ? 200 : 500;
|
const backgroundColor = this.theme == "dark" ? '' : '';
|
||||||
const backgroundColor = this.theme == "dark" ? '#1C1D26' : '#ffffff';
|
|
||||||
const inRangeColor = this.theme == "dark" ? '#D2B206' : '#FFDF32';
|
const inRangeColor = this.theme == "dark" ? '#D2B206' : '#FFDF32';
|
||||||
const tooltipBackgroundColor = this.theme == "dark" ? "#ffffff" : '#ffffff';
|
const tooltipBackgroundColor = this.theme == "dark" ? "#ffffff" : '#ffffff';
|
||||||
const tooltipBorderColor = this.theme == "dark" ? "#ffffff" : "#ffffff";
|
const tooltipBorderColor = this.theme == "dark" ? "#ffffff" : "#ffffff";
|
||||||
const fontSize = isMobile ? 10 : 12;
|
const fontSize = this.isMobile ? 10 : 12;
|
||||||
const fontColor = this.theme == "dark" ? "#000000" : "#000000";
|
const fontColor = this.theme == "dark" ? "#000000" : "#000000";
|
||||||
const chartContainer = document.getElementById('mapChart');
|
const chartContainer = document.getElementById('mapChart');
|
||||||
const mapChart = echarts.init(chartContainer, '', { // init图表
|
const mapChart = echarts.init(chartContainer, '', { // init图表
|
||||||
@@ -509,20 +509,21 @@
|
|||||||
}
|
}
|
||||||
// 定义图表参数值
|
// 定义图表参数值
|
||||||
const MaxTCPPingValue = {{.Conf.MaxTCPPingValue}} ? {{.Conf.MaxTCPPingValue}} : 300;
|
const MaxTCPPingValue = {{.Conf.MaxTCPPingValue}} ? {{.Conf.MaxTCPPingValue}} : 300;
|
||||||
const isMobile = this.checkIsMobile();
|
const fontSize = this.isMobile ? 10 : 14;
|
||||||
const fontSize = isMobile ? 10 : 14;
|
const gridLeft = this.isMobile ? 25 : 36;
|
||||||
const gridLeft = isMobile ? 25 : 36;
|
const gridRight = this.isMobile ? 5 : 20;
|
||||||
const gridRight = isMobile ? 5 : 20;
|
const legendLeft = this.isMobile ? 'center' : 'center';
|
||||||
const legendLeft = isMobile ? 'center' : 'center';
|
const legendTop = this.isMobile ? 5 : 5;
|
||||||
const legendTop = isMobile ? 5 : 5;
|
const legendPadding= this.isMobile ? [5,0,5,0] : [5,0,5,0];
|
||||||
const legendPadding= isMobile ? [5,0,5,0] : [5,0,5,0];
|
|
||||||
const systemDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
const systemDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||||
const theme = localStorage.getItem("theme") ? localStorage.getItem("theme") : systemDarkMode;
|
const theme = localStorage.getItem("theme") ? localStorage.getItem("theme") : systemDarkMode;
|
||||||
const chartTheme = theme == "dark" ? "dark" : "default";
|
const chartTheme = theme == "dark" ? "dark" : "default";
|
||||||
const fontColor = theme == "dark" ? "#f1f1f1" : "#000000";
|
const fontColor = theme == "dark" ? "#f1f1f1" : "#000000";
|
||||||
const backgroundColor = theme == "dark" ? "#1C1D26" : '';
|
const backgroundColor = theme == "dark" ? '' : '';
|
||||||
const tooltipBackgroundColor = theme == "dark" ? "#1C1D26" : '#ffffff';
|
const tooltipBackgroundColor = this.theme == "dark" ? (this.semiTransparent ? "rgba(28,29,38,0.85)" : "rgba(28,29,38,1)") : (this.semiTransparent ? "rgba(255,255,255,0.85)" : "rgba(255,255,255,1)");
|
||||||
const tooltipBorderColor = theme == "dark" ? "#31363B" : "#ffffff";
|
const tooltipBorderColor = this.theme == "dark" ? (this.semiTransparent ? "rgba(28,29,38,0.9)" : "rgba(28,29,38,1)") : (this.semiTransparent ? "rgba(255,255,255,0.9)" : "rgba(255,255,255,1)");
|
||||||
|
const lineStyleWidth = this.isMobile ? 1 : 2;
|
||||||
|
const splitLineWidth = this.isMobile ? 0.5 : 1;
|
||||||
// 渲染图表
|
// 渲染图表
|
||||||
const chart = echarts.init(chartContainer, chartTheme, {
|
const chart = echarts.init(chartContainer, chartTheme, {
|
||||||
renderer: 'canvas',
|
renderer: 'canvas',
|
||||||
@@ -549,19 +550,22 @@
|
|||||||
smooth: true,
|
smooth: true,
|
||||||
symbol: 'none',
|
symbol: 'none',
|
||||||
data: data,
|
data: data,
|
||||||
connectNulls: true
|
connectNulls: true,
|
||||||
|
lineStyle: {
|
||||||
|
width: lineStyleWidth
|
||||||
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const legendData = chartData.map(item => item.monitor_name);
|
const legendData = chartData.map(item => item.monitor_name);
|
||||||
const maxLegendsPerRowMobile = localStorage.getItem("maxLegendsPerRowMobile") ? localStorage.getItem("maxLegendsPerRowMobile") : 3;
|
const maxLegendsPerRowMobile = localStorage.getItem("maxLegendsPerRowMobile") ? localStorage.getItem("maxLegendsPerRowMobile") : 3;
|
||||||
const maxLegendsPerRowPc = localStorage.getItem("maxLegendsPerRowPc") ? localStorage.getItem("maxLegendsPerRowPc") : 6;
|
const maxLegendsPerRowPc = localStorage.getItem("maxLegendsPerRowPc") ? localStorage.getItem("maxLegendsPerRowPc") : 6;
|
||||||
const autoIncrement = Math.floor((legendData.length - 1) / (isMobile ? maxLegendsPerRowMobile : maxLegendsPerRowPc)) * (isMobile ? 20 : 28)
|
const autoIncrement = Math.floor((legendData.length - 1) / (this.isMobile ? maxLegendsPerRowMobile : maxLegendsPerRowPc)) * (this.isMobile ? 20 : 28)
|
||||||
const height = 300 + autoIncrement;
|
const height = 300 + autoIncrement;
|
||||||
const gridTop = 40 + autoIncrement;
|
const gridTop = 40 + autoIncrement;
|
||||||
const legendIcon = isMobile ? 'rect' : "";
|
const legendIcon = this.isMobile ? 'rect' : "";
|
||||||
const itemWidth = isMobile ? 10 : 25;
|
const itemWidth = this.isMobile ? 10 : 25;
|
||||||
const itemHeight = isMobile ? 10 : 14;
|
const itemHeight = this.isMobile ? 10 : 14;
|
||||||
chart.resize({
|
chart.resize({
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
height: height
|
height: height
|
||||||
@@ -611,6 +615,11 @@
|
|||||||
textStyle: {
|
textStyle: {
|
||||||
fontSize: fontSize
|
fontSize: fontSize
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
width: splitLineWidth
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dataZoom: [
|
dataZoom: [
|
||||||
@@ -645,5 +654,4 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
{{template "theme-server-status/footer" .}}
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
+4
-3
@@ -1,6 +1,6 @@
|
|||||||
{{define "theme-server-status/content-nav"}}
|
{{define "theme-server-status/menu"}}
|
||||||
<header role="navigation" class="navbar navbar-inverse navbar-fixed-top" style="z-index:99999999;">
|
<header role="navigation" class="navbar navbar-inverse navbar-fixed-top">
|
||||||
<div class="container">
|
<div class="container-fluid">
|
||||||
<div class="navbar-header">
|
<div class="navbar-header">
|
||||||
<button data-target=".navbar-collapse" data-toggle="collapse" class="navbar-toggle" type="button">
|
<button data-target=".navbar-collapse" data-toggle="collapse" class="navbar-toggle" type="button">
|
||||||
<span class="sr-only">Toggle navigation</span>
|
<span class="sr-only">Toggle navigation</span>
|
||||||
@@ -64,5 +64,6 @@
|
|||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
<div class="nezha">
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
+323
-204
@@ -1,243 +1,362 @@
|
|||||||
{{define "theme-server-status/network"}}
|
{{define "theme-server-status/network"}}
|
||||||
{{template "theme-server-status/header" .}}
|
{{template "theme-server-status/header" .}}
|
||||||
<div id="app">
|
{{template "theme-server-status/menu" .}}
|
||||||
{{template "theme-server-status/content-nav" .}}
|
<div class="container-fluid content network-box">
|
||||||
<div class="container table-responsive content" style="max-width: 95vw">
|
<div class="network-box-header btn-group">
|
||||||
<table class="table table-striped table-condensed table-hover">
|
<div class="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
<button class="ui nezha-primary-btn button"
|
<i class="bi bi-list-ul"></i> {{tr "NetworkSpiterList"}} <i class="bi bi-chevron-compact-down"></i>
|
||||||
v-for="server in servers"
|
</div>
|
||||||
style="margin-top: 3px"
|
<ul class="dropdown-menu">
|
||||||
@click="redirectNetwork(server.ID)">
|
<li class="input-group fixed-top">
|
||||||
@#server.Name#@ <i :class="'fi fi-' + server.Host.CountryCode"></i><span class="node-cell-location-text text-uppercase"> @#server.Host.CountryCode#@</span>
|
<input type="text" id="dropdown-search" class="form-control" placeholder="Search...">
|
||||||
</button>
|
</li>
|
||||||
</table>
|
<li class="dropdown-item" v-for="server in servers" @click="showCharts(server.ID)">
|
||||||
|
<a><i :class="'fi fi-' + (server.Host.CountryCode || 'rb')"></i> @#server.Name#@ <i v-if="server.ID == currentServerId" class="check icon"></i></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="container table-responsive content" style="max-width: 95vw">
|
<div class="chartTitle"><i class="chartCountryCode" :class="'fi fi-' + chartCountryCode"></i> @#chartTitle#@</div>
|
||||||
<div ref="chartDom" style="border-radius: 28px; margin-top: 15px;height: 520px;max-width: 1400px;overflow: hidden"></div>
|
<div id="chartbox" style="width:100%;height:auto;"></div>
|
||||||
</div>
|
|
||||||
{{template "theme-server-status/content-footer" .}}
|
|
||||||
</div>
|
</div>
|
||||||
|
{{template "theme-server-status/footer" .}}
|
||||||
<script>
|
<script>
|
||||||
const monitorInfo = JSON.parse('{{.MonitorInfos}}');
|
|
||||||
const initData = JSON.parse('{{.Servers}}').servers;
|
|
||||||
let MaxTCPPingValue = {{.Conf.MaxTCPPingValue}};
|
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
delimiters: ['@#', '#@'],
|
delimiters: ['@#', '#@'],
|
||||||
data: {
|
data: {
|
||||||
page: 'network',
|
page: 'network',
|
||||||
defaultTemplate: {{.Conf.Site.Theme}},
|
defaultTemplate: "{{.Conf.Site.Theme}}",
|
||||||
templates: {{.Themes}},
|
templates: "{{.Themes }}",
|
||||||
servers: initData,
|
servers: [],
|
||||||
option: {
|
chartDataList: [],
|
||||||
tooltip: {
|
chartTitle: '',
|
||||||
trigger: 'axis',
|
chartCountryCode: '',
|
||||||
position: function (pt) {
|
chart: null,
|
||||||
return [pt[0], '10%'];
|
currentServerId: ''
|
||||||
},
|
|
||||||
formatter: function(params){
|
|
||||||
let result = params[0].axisValueLabel + "<br />";
|
|
||||||
params.forEach(function(item){
|
|
||||||
result += item.marker + item.seriesName + ": " + item.value[1].toFixed(2) + " ms<br />";
|
|
||||||
})
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
confine: true,
|
|
||||||
transitionDuration: 0
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
left: 'center',
|
|
||||||
text: "",
|
|
||||||
textStyle: {}
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
top: '5%',
|
|
||||||
data: [],
|
|
||||||
textStyle: {
|
|
||||||
fontSize: 14
|
|
||||||
}
|
|
||||||
},
|
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.8)',
|
|
||||||
toolbox: {
|
|
||||||
feature: {
|
|
||||||
dataZoom: {
|
|
||||||
yAxisIndex: 'none'
|
|
||||||
},
|
|
||||||
restore: {},
|
|
||||||
saveAsImage: {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dataZoom: [
|
|
||||||
{
|
|
||||||
start: 0,
|
|
||||||
end: 100
|
|
||||||
}
|
|
||||||
],
|
|
||||||
xAxis: {
|
|
||||||
type: 'time',
|
|
||||||
boundaryGap: false
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'value',
|
|
||||||
boundaryGap: false
|
|
||||||
},
|
|
||||||
series: [],
|
|
||||||
},
|
|
||||||
chartOnOff: true,
|
|
||||||
},
|
},
|
||||||
mixins: [mixinsVue],
|
mixins: [mixinsVue],
|
||||||
|
created() {
|
||||||
|
this.servers = JSON.parse('{{.Servers}}').servers;
|
||||||
|
this.showCharts(this.servers[0].ID);
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.renderChart();
|
this.initSearch();
|
||||||
this.parseMonitorInfo(monitorInfo);
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getFontLogoClass(str) {
|
showCharts(id) {
|
||||||
if (["almalinux",
|
const chartContainer = document.getElementById('chartbox');
|
||||||
"alpine",
|
// 发起数据请求
|
||||||
"aosc",
|
const url = `/api/v1/monitor/${id}`;
|
||||||
"apple",
|
fetch(url)
|
||||||
"archlinux",
|
.then(response => response.json())
|
||||||
"archlabs",
|
.then(data => {
|
||||||
"artix",
|
if (data.result) { // 数据请求成功,更新数据并渲染图表
|
||||||
"budgie",
|
this.chartDataList[id] = data.result;
|
||||||
"centos",
|
this.$nextTick(() => {
|
||||||
"coreos",
|
this.renderCharts(id);
|
||||||
"debian",
|
});
|
||||||
"deepin",
|
} else {
|
||||||
"devuan",
|
console.log('this server (id:'+ id + ') has no monitor.');
|
||||||
"docker",
|
|
||||||
"elementary",
|
|
||||||
"fedora",
|
|
||||||
"ferris",
|
|
||||||
"flathub",
|
|
||||||
"freebsd",
|
|
||||||
"gentoo",
|
|
||||||
"gnu-guix",
|
|
||||||
"illumos",
|
|
||||||
"kali-linux",
|
|
||||||
"linuxmint",
|
|
||||||
"mageia",
|
|
||||||
"mandriva",
|
|
||||||
"manjaro",
|
|
||||||
"nixos",
|
|
||||||
"openbsd",
|
|
||||||
"opensuse",
|
|
||||||
"pop-os",
|
|
||||||
"raspberry-pi",
|
|
||||||
"redhat",
|
|
||||||
"rocky-linux",
|
|
||||||
"sabayon",
|
|
||||||
"slackware",
|
|
||||||
"snappy",
|
|
||||||
"solus",
|
|
||||||
"tux",
|
|
||||||
"ubuntu",
|
|
||||||
"void",
|
|
||||||
"zorin"].indexOf(str)
|
|
||||||
> -1) {
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
if (['openwrt', 'linux', "immortalwrt"].indexOf(str) > -1) {
|
|
||||||
return 'tux';
|
|
||||||
}
|
|
||||||
if (str == 'amazon') {
|
|
||||||
return 'redhat';
|
|
||||||
}
|
|
||||||
if (str == 'arch') {
|
|
||||||
return 'archlinux';
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
},
|
|
||||||
redirectNetwork(id) {
|
|
||||||
this.getMonitorHistory(id)
|
|
||||||
.then(function(monitorInfo) {
|
|
||||||
var vm = app.__vue__;
|
|
||||||
vm.parseMonitorInfo(monitorInfo);
|
|
||||||
})
|
|
||||||
.catch(function(error){
|
|
||||||
window.location.href = "/404";
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getMonitorHistory(id) {
|
|
||||||
return $.ajax({
|
|
||||||
url: "/api/v1/monitor/"+id,
|
|
||||||
method: "GET"
|
|
||||||
});
|
|
||||||
},
|
|
||||||
parseMonitorInfo(monitorInfo) {
|
|
||||||
let tSeries = [];
|
|
||||||
let tLegendData = [];
|
|
||||||
var lcolors = ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'];
|
|
||||||
for (let i = 0; i < monitorInfo.result.length; i++) {
|
|
||||||
var lcolor = lcolors[i % lcolors.length];
|
|
||||||
var rgbaColorMarker = 'rgba(' + parseInt(lcolor.slice(1, 3), 16) + ',' + parseInt(lcolor.slice(3, 5), 16) + ',' + parseInt(lcolor.slice(5, 7), 16) + ',0.5)';
|
|
||||||
var rgbaColorBar = 'rgba(' + parseInt(lcolor.slice(1, 3), 16) + ',' + parseInt(lcolor.slice(3, 5), 16) + ',' + parseInt(lcolor.slice(5, 7), 16) + ',0.35)';
|
|
||||||
let loss = 0;
|
|
||||||
let data = [];
|
|
||||||
let datal = [];
|
|
||||||
for (let j = 0; j < monitorInfo.result[i].created_at.length; j++) {
|
|
||||||
avgDelay = Math.round(monitorInfo.result[i].avg_delay[j]);
|
|
||||||
if (avgDelay > 0 && avgDelay < MaxTCPPingValue) {
|
|
||||||
data.push([monitorInfo.result[i].created_at[j], avgDelay]);
|
|
||||||
}
|
}
|
||||||
else {
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching data:', error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
renderCharts(id, reload = false) {
|
||||||
|
if (!this.chartDataList[id]) return;
|
||||||
|
this.disposeCharts();
|
||||||
|
this.currentServerId = id;
|
||||||
|
this.chartCountryCode = this.getServerCountryCode(id);
|
||||||
|
const chartData = this.chartDataList[id];
|
||||||
|
const chartContainer = document.getElementById('chartbox');
|
||||||
|
this.chartTitle = chartData[0].server_name;
|
||||||
|
if (reload) {
|
||||||
|
const existingChart = echarts.getInstanceByDom(chartContainer);
|
||||||
|
if (existingChart) existingChart.dispose();
|
||||||
|
}
|
||||||
|
// 定义图表参数值
|
||||||
|
const MaxTCPPingValue = {{.Conf.MaxTCPPingValue}} ? {{.Conf.MaxTCPPingValue}} : 300;
|
||||||
|
const autoheight = this.isMobile ? (window.innerHeight - 200) : (window.innerHeight - 250);
|
||||||
|
const fontSize = this.isMobile ? 10 : 14;
|
||||||
|
const gridLeft = this.isMobile ? 30 : 36;
|
||||||
|
const gridRight = this.isMobile ? 12 : 20;
|
||||||
|
const legendLeft = this.isMobile ? 'center' : 'center';
|
||||||
|
const legendTop = this.isMobile ? 5 : 5;
|
||||||
|
const legendPadding= this.isMobile ? [5,0,5,0] : [5,0,5,0];
|
||||||
|
const fontColor = this.theme == "dark" ? "#f1f1f1" : "#000000";
|
||||||
|
const backgroundColor = this.theme == "dark" ? '' : '';
|
||||||
|
const tooltipBackgroundColor = this.theme == "dark" ? (this.semiTransparent ? "rgba(28,29,38,0.85)" : "rgba(28,29,38,1)") : (this.semiTransparent ? "rgba(255,255,255,0.85)" : "rgba(255,255,255,1)");
|
||||||
|
const tooltipBorderColor = this.theme == "dark" ? (this.semiTransparent ? "rgba(28,29,38,0.9)" : "rgba(28,29,38,1)") : (this.semiTransparent ? "rgba(255,255,255,0.9)" : "rgba(255,255,255,1)");
|
||||||
|
const lineStyleWidth = this.isMobile ? 1 : 2;
|
||||||
|
const splitLineWidth = this.isMobile ? 0.5 : 1;
|
||||||
|
const splitLineColor = this.theme == "dark" ? "rgba(110, 112, 121, 0.95)" : "rgba(224, 230, 241, 0.95)";
|
||||||
|
const markPointLabelColor = this.theme == "dark" ? "#111111" : "#000000";
|
||||||
|
const markPointItemStyleOpacity = this.semiTransparent ? 1 : 0.75;
|
||||||
|
const markLineItemStyleOpacity = this.semiTransparent ? 1 : 0.75;
|
||||||
|
const markLineLineStyleWidth = this.isMobile ? 0.15 : 0.3;
|
||||||
|
this.chart = echarts.init(chartContainer, '', { // init图表
|
||||||
|
renderer: 'canvas',
|
||||||
|
useDirtyRect: false,
|
||||||
|
width: 'auto',
|
||||||
|
height: autoheight,
|
||||||
|
});
|
||||||
|
// 获取图表数据
|
||||||
|
let legendData = [];
|
||||||
|
let seriesData = [];
|
||||||
|
chartData.forEach((item,key)=> {
|
||||||
|
let loss = 0;
|
||||||
|
let totalLossRate = 0;
|
||||||
|
let legendName = '';
|
||||||
|
let data = { main: [], markLine: []};
|
||||||
|
item.avg_delay.forEach((avgDelay, index) => {
|
||||||
|
const threshold = 0.9 * MaxTCPPingValue; // 定义阀值,用于判断是否丢包
|
||||||
|
const filterAvgDelay = item.avg_delay.filter(value => value !== 0 && value !== MaxTCPPingValue);
|
||||||
|
const max = Math.max(...filterAvgDelay).toFixed(1);
|
||||||
|
const autoAvgDelay = 1.05 * max > 0.91 * MaxTCPPingValue ? 1.05 * max : 0.91 * MaxTCPPingValue;
|
||||||
|
// 定义丢包 1. avgDelay==0 2. avgDelay>=MaxTCPPingValue 3. avgDelay>=threshold
|
||||||
|
if(avgDelay == 0 || avgDelay >= MaxTCPPingValue){ //绝对丢包
|
||||||
loss += 1;
|
loss += 1;
|
||||||
datal.push({
|
const lossrate = 100 * loss / (index + 1);
|
||||||
xAxis: monitorInfo.result[i].created_at[j],
|
data['main'].push(
|
||||||
|
[item.created_at[index], autoAvgDelay, lossrate]
|
||||||
|
);
|
||||||
|
data['markLine'].push({
|
||||||
|
xAxis: item.created_at[index],
|
||||||
label: { show: false },
|
label: { show: false },
|
||||||
emphasis: { disabled: true },
|
emphasis: { disabled: true },
|
||||||
lineStyle: {
|
lineStyle: { type: "solid" }
|
||||||
type: "solid",
|
|
||||||
color: rgbaColorBar
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
} else if (avgDelay >= threshold && avgDelay < MaxTCPPingValue){ // 相对丢包
|
||||||
|
loss += 1;
|
||||||
|
const lossrate = 100 * loss / (index + 1);
|
||||||
|
data['main'].push(
|
||||||
|
[item.created_at[index], avgDelay, lossrate]
|
||||||
|
);
|
||||||
|
data['markLine'].push({
|
||||||
|
xAxis: item.created_at[index],
|
||||||
|
label: { show: false },
|
||||||
|
emphasis: { disabled: true },
|
||||||
|
lineStyle: { type: "solid" }
|
||||||
|
});
|
||||||
|
} else { // 未丢包
|
||||||
|
const lossrate = 100 * loss / (index + 1);
|
||||||
|
data['main'].push(
|
||||||
|
[item.created_at[index], avgDelay, lossrate]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
lossRate = ((loss / monitorInfo.result[i].created_at.length) * 100).toFixed(1);
|
// 处理legendData
|
||||||
if (lossRate > 99) {
|
totalLossRate = ((loss / item.created_at.length) * 100).toFixed(1);
|
||||||
datal = [];
|
legendName = `${item.monitor_name} ${totalLossRate}%`;
|
||||||
}
|
legendData.push(legendName);
|
||||||
legendName = monitorInfo.result[i].monitor_name +" "+ lossRate + "%";
|
// 处理seriesData
|
||||||
tLegendData.push(legendName);
|
seriesData.push(
|
||||||
tSeries.push({
|
{
|
||||||
name: legendName,
|
name: legendName,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
smooth: true,
|
smooth: true,
|
||||||
symbol: 'none',
|
symbol: 'none',
|
||||||
data: data,
|
connectNulls: true,
|
||||||
|
lineStyle: {
|
||||||
|
width: lineStyleWidth
|
||||||
|
},
|
||||||
|
data: data['main'],
|
||||||
markLine: {
|
markLine: {
|
||||||
symbol: "none",
|
symbol: "none",
|
||||||
symbolSize :0,
|
symbolSize :0,
|
||||||
data: datal
|
data: data['markLine'],
|
||||||
|
itemStyle: {
|
||||||
|
opacity: markLineItemStyleOpacity
|
||||||
|
},
|
||||||
|
lineStyle:{
|
||||||
|
width: markLineLineStyleWidth
|
||||||
|
}
|
||||||
},
|
},
|
||||||
markPoint: {
|
markPoint: {
|
||||||
data: [
|
data: [
|
||||||
{ type: 'max', symbol: 'pin', name: 'Max', itemStyle: { color: rgbaColorMarker }, symbolSize: 30, label: { fontSize: 8 } },
|
{
|
||||||
{ type: 'min', symbol: 'pin', name: 'Min', itemStyle: { color: rgbaColorMarker }, symbolSize: 30, label: { fontSize: 8, offset: [0, 7.5] }, symbolRotate: 180 }
|
name: 'Max',
|
||||||
|
type: 'max',
|
||||||
|
symbol: 'pin',
|
||||||
|
itemStyle: {
|
||||||
|
opacity: markPointItemStyleOpacity
|
||||||
|
},
|
||||||
|
symbolSize: 30,
|
||||||
|
label: {
|
||||||
|
fontSize: 8,
|
||||||
|
color: markPointLabelColor,
|
||||||
|
formatter: function (params) {
|
||||||
|
return Math.round(params.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Min',
|
||||||
|
type: 'min',
|
||||||
|
symbol: 'pin',
|
||||||
|
itemStyle: {
|
||||||
|
opacity: markPointItemStyleOpacity
|
||||||
|
},
|
||||||
|
symbolSize: 30,
|
||||||
|
label: {
|
||||||
|
fontSize: 8,
|
||||||
|
color: markPointLabelColor,
|
||||||
|
offset: [0, 8],
|
||||||
|
formatter: function (params) {
|
||||||
|
return Math.round(params.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
symbolRotate: 180
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const maxLegendsPerRowMobile = localStorage.getItem("maxLegendsPerRowMobile") ? localStorage.getItem("maxLegendsPerRowMobile") : 3;
|
||||||
|
const maxLegendsPerRowPc = localStorage.getItem("maxLegendsPerRowPc") ? localStorage.getItem("maxLegendsPerRowPc") : 6;
|
||||||
|
const autoIncrement = Math.floor((legendData.length - 1) / (this.isMobile ? maxLegendsPerRowMobile : maxLegendsPerRowPc)) * (this.isMobile ? 20 : 28)
|
||||||
|
const height = autoheight + autoIncrement;
|
||||||
|
const gridTop = 40 + autoIncrement;
|
||||||
|
const legendIcon = this.isMobile ? 'rect' : "";
|
||||||
|
const itemWidth = this.isMobile ? 10 : 25;
|
||||||
|
const itemHeight = this.isMobile ? 10 : 14;
|
||||||
|
this.chart.resize({
|
||||||
|
width: 'auto',
|
||||||
|
height: height
|
||||||
|
});
|
||||||
|
// 设置图表配置项
|
||||||
|
const option = {
|
||||||
|
// 全局调色盘
|
||||||
|
color: this.colors,
|
||||||
|
// 背景颜色
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
// 文字样式
|
||||||
|
textStyle: {
|
||||||
|
fontSize: fontSize,
|
||||||
|
color: fontColor
|
||||||
|
},
|
||||||
|
// 图表网格设置
|
||||||
|
grid: {
|
||||||
|
top: gridTop,
|
||||||
|
left: gridLeft,
|
||||||
|
right: gridRight
|
||||||
|
},
|
||||||
|
// 图表标题设置
|
||||||
|
title: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
// 图表系列数据设置
|
||||||
|
series: seriesData.flat(),
|
||||||
|
// X轴设置
|
||||||
|
xAxis: {
|
||||||
|
type: 'time',
|
||||||
|
axisLabel: {
|
||||||
|
textStyle: {
|
||||||
|
fontSize: fontSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Y轴设置
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
axisLabel: {
|
||||||
|
textStyle: {
|
||||||
|
fontSize: fontSize
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
width: splitLineWidth,
|
||||||
|
color: splitLineColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 图例设置
|
||||||
|
legend: {
|
||||||
|
data: legendData,
|
||||||
|
show: true,
|
||||||
|
icon: legendIcon,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: fontSize,
|
||||||
|
color: fontColor
|
||||||
|
},
|
||||||
|
top: legendTop,
|
||||||
|
bottom: 0,
|
||||||
|
left: legendLeft,
|
||||||
|
padding: legendPadding,
|
||||||
|
itemWidth: itemWidth,
|
||||||
|
itemHeight: itemHeight,
|
||||||
|
},
|
||||||
|
// 提示框设置
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
backgroundColor: tooltipBackgroundColor,
|
||||||
|
borderColor: tooltipBorderColor,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: fontSize,
|
||||||
|
color: fontColor
|
||||||
|
},
|
||||||
|
formatter: function (params) {
|
||||||
|
let tooltipContent = '';
|
||||||
|
const formattedTime = new Date(params[0].value[0]).toLocaleString();
|
||||||
|
tooltipContent += `<span style="line-height:2em">${formattedTime}</span><br>`;
|
||||||
|
params.forEach(param => {
|
||||||
|
const formattedTime = new Date(param.value[0]).toLocaleString();
|
||||||
|
if (!param.seriesName.includes('stack')) {
|
||||||
|
const name = param.seriesName.replace(/\s\d+(\.\d+)?%$/, '');
|
||||||
|
tooltipContent += `<span style="line-height:2em">${param.marker} ${name} ${param.value[2].toFixed(1)}% ${param.value[1].toFixed(2)}</span><br>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return tooltipContent;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 数据缩放设置
|
||||||
|
dataZoom: [
|
||||||
|
{
|
||||||
|
type: 'slider',
|
||||||
|
start: 0,
|
||||||
|
end: 100
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
// 设置图表的配置选项
|
||||||
|
this.chart.setOption(option);
|
||||||
|
},
|
||||||
|
reloadCharts() { // 重新加载所有图表
|
||||||
|
this.servers.forEach(node => {
|
||||||
|
const id = node.ID;
|
||||||
|
const chartData = this.chartDataList[id];
|
||||||
|
if (chartData) {
|
||||||
|
this.renderCharts(id,true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
disposeCharts(){
|
||||||
|
if(this.chart) {
|
||||||
|
this.chart.dispose();
|
||||||
|
this.chart = null;
|
||||||
}
|
}
|
||||||
this.option.title.text = monitorInfo.result[0].server_name;
|
|
||||||
this.option.series = tSeries;
|
|
||||||
this.option.legend.data = tLegendData;
|
|
||||||
this.myChart.clear();
|
|
||||||
this.myChart.setOption(this.option);
|
|
||||||
},
|
},
|
||||||
isWindowsPlatform(str) {
|
getServerCountryCode(id){
|
||||||
return str.includes('Windows')
|
const result = this.servers.find(item => item.ID == id);
|
||||||
|
return result.Host.CountryCode ? result.Host.CountryCode : 'rb';
|
||||||
},
|
},
|
||||||
renderChart() {
|
initSearch() {
|
||||||
this.myChart = echarts.init(this.$refs.chartDom);
|
$('#dropdown-search').on('keyup', function() {
|
||||||
this.myChart.setOption(this.option);
|
var searchTerm = $(this).val().toLowerCase();
|
||||||
},
|
$('.dropdown-menu .dropdown-item').each(function() {
|
||||||
},
|
var text = $(this).text().toLowerCase();
|
||||||
beforeDestroy() {
|
if (text.indexOf(searchTerm) > -1) {
|
||||||
this.myChart.dispose();
|
$(this).removeClass('hidden').addClass('visible'); // 显示元素
|
||||||
this.myChart = null;
|
} else {
|
||||||
},
|
$(this).removeClass('visible').addClass('hidden'); // 隐藏元素
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{{template "theme-server-status/footer" .}}
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
+68
-71
@@ -1,77 +1,75 @@
|
|||||||
{{define "theme-server-status/service"}}
|
{{define "theme-server-status/service"}}
|
||||||
{{template "theme-server-status/header" .}}
|
{{template "theme-server-status/header" .}}
|
||||||
<div id="app">
|
{{template "theme-server-status/menu" .}}
|
||||||
{{template "theme-server-status/content-nav" .}}
|
<!-- showGroup true -->
|
||||||
<!-- showGroup true -->
|
<template v-if="showGroup">
|
||||||
<template v-if="showGroup">
|
<section v-if="servicesTag.length === 0" class="container-fluid content" style="min-height: .01%;overflow-x: auto;">
|
||||||
<section v-if="servicesTag.length === 0" class="container content" style="max-width: 95vw; min-height: .01%;overflow-x: auto;">
|
<p>No Valid Service Monitor Configuration Entries Found. Please Verify in the <a href="/monitor">Admin Panel</a>.</p>
|
||||||
<p>No Valid Service Monitor Configuration Entries Found. Please Verify in the <a href="/monitor">Admin Panel</a>.</p>
|
</section>
|
||||||
</section>
|
<section v-else class="container-fluid content" style="min-height: .01%;overflow-x: auto;" v-for="group in servicesTag">
|
||||||
<section v-else class="container content" style="max-width: 95vw; min-height: .01%;overflow-x: auto;" v-for="group in servicesTag">
|
{{template "theme-server-status/service-group-true" .}}
|
||||||
{{template "theme-server-status/service-group-true" .}}
|
</section>
|
||||||
</section>
|
</template>
|
||||||
</template>
|
<!-- showGroup false -->
|
||||||
<!-- showGroup false -->
|
<template v-else>
|
||||||
<template v-else>
|
<section v-if="servicesNoTag.length === 0" class="container-fluid content" style="min-height: .01%;overflow-x: auto;">
|
||||||
<section v-if="servicesNoTag.length === 0" class="container content" style="max-width: 95vw; min-height: .01%;overflow-x: auto;">
|
<p>No Valid Service Monitor Configuration Entries Found. Please Verify in the <a href="/monitor">Admin Panel</a>.</p>
|
||||||
<p>No Valid Service Monitor Configuration Entries Found. Please Verify in the <a href="/monitor">Admin Panel</a>.</p>
|
</section>
|
||||||
</section>
|
<section v-else class="container-fluid content" style="min-height: .01%;overflow-x: auto;">
|
||||||
<section v-else class="container content" style="max-width: 95vw; min-height: .01%;overflow-x: auto;">
|
{{template "theme-server-status/service-group-false" .}}
|
||||||
{{template "theme-server-status/service-group-false" .}}
|
</section>
|
||||||
</section>
|
</template>
|
||||||
</template>
|
{{if .CycleTransferStats}}
|
||||||
{{if .CycleTransferStats}}
|
<section class="container-fluid content table-responsive">
|
||||||
<section class="container content table-responsive" style="max-width: 95vw">
|
<table class="table table-striped table-condensed table-hover">
|
||||||
<table class="table table-striped table-condensed table-hover">
|
<thead>
|
||||||
<thead>
|
<tr class="node-group-tag">
|
||||||
<tr class="node-group-tag">
|
<th colspan="16" style="border:none;">
|
||||||
<th colspan="16" style="border:none;">
|
{{tr "CycleTransferStats"}}
|
||||||
{{tr "CycleTransferStats"}}
|
</th>
|
||||||
</th>
|
</tr>
|
||||||
</tr>
|
<tr class="node-group-cell">
|
||||||
<tr class="node-group-cell">
|
<th class="node-cell center">ID</th>
|
||||||
<th class="node-cell center">ID</th>
|
<th class="node-cell center">{{tr "Rules"}}</th>
|
||||||
<th class="node-cell center">{{tr "Rules"}}</th>
|
<th class="node-cell center">{{tr "Server"}}</th>
|
||||||
<th class="node-cell center">{{tr "Server"}}</th>
|
<th class="node-cell center">{{tr "From"}}</th>
|
||||||
<th class="node-cell center">{{tr "From"}}</th>
|
<th class="node-cell center">{{tr "To"}}</th>
|
||||||
<th class="node-cell center">{{tr "To"}}</th>
|
<th class="node-cell center">MAX</th>
|
||||||
<th class="node-cell center">MAX</th>
|
<th class="node-cell center">MIN</th>
|
||||||
<th class="node-cell center">MIN</th>
|
<th class="node-cell center">{{tr "NextCheck"}}</th>
|
||||||
<th class="node-cell center">{{tr "NextCheck"}}</th>
|
<th class="node-cell center">{{tr "CurrentUsage"}}</th>
|
||||||
<th class="node-cell center">{{tr "CurrentUsage"}}</th>
|
<th class="node-cell center">{{tr "Transleft"}}</th>
|
||||||
<th class="node-cell center">{{tr "Transleft"}}</th>
|
</tr>
|
||||||
</tr>
|
</thead>
|
||||||
</thead>
|
<tbody>
|
||||||
<tbody>
|
{{range $id, $stats := .CycleTransferStats}}
|
||||||
{{range $id, $stats := .CycleTransferStats}}
|
{{range $innerId, $transfer := $stats.Transfer}}
|
||||||
{{range $innerId, $transfer := $stats.Transfer}}
|
{{$TransLeftPercent := TransLeftPercent (UintToFloat $transfer) (UintToFloat $stats.Max)}}
|
||||||
{{$TransLeftPercent := TransLeftPercent (UintToFloat $transfer) (UintToFloat $stats.Max)}}
|
<tr>
|
||||||
<tr>
|
<td class="node-cell center">{{$id}}</td>
|
||||||
<td class="node-cell center">{{$id}}</td>
|
<td class="node-cell center">{{$stats.Name}}</td>
|
||||||
<td class="node-cell center">{{$stats.Name}}</td>
|
<td class="node-cell center">{{index $stats.ServerName $innerId}}</td>
|
||||||
<td class="node-cell center">{{index $stats.ServerName $innerId}}</td>
|
<td class="node-cell center">{{$stats.From|tf}}</td>
|
||||||
<td class="node-cell center">{{$stats.From|tf}}</td>
|
<td class="node-cell center">{{$stats.To|tf}}</td>
|
||||||
<td class="node-cell center">{{$stats.To|tf}}</td>
|
<td class="node-cell center">{{$stats.Max|bf}}</td>
|
||||||
<td class="node-cell center">{{$stats.Max|bf}}</td>
|
<td class="node-cell center">{{$stats.Min|bf}}</td>
|
||||||
<td class="node-cell center">{{$stats.Min|bf}}</td>
|
<td class="node-cell center">{{(index $stats.NextUpdate $innerId)|sft}}</td>
|
||||||
<td class="node-cell center">{{(index $stats.NextUpdate $innerId)|sft}}</td>
|
<td class="node-cell center">{{$transfer|bf}}</td>
|
||||||
<td class="node-cell center">{{$transfer|bf}}</td>
|
<td class="node-cell center">
|
||||||
<td class="node-cell center">
|
<div class="progress">
|
||||||
<div class="progress">
|
<div style="width: {{$TransLeftPercent}}%" :class="'progress-bar progress-bar-' + toSSBar('{{TransClassName $TransLeftPercent}}')">
|
||||||
<div style="width: {{$TransLeftPercent}}%" :class="'progress-bar progress-bar-' + toSSBar('{{TransClassName $TransLeftPercent}}')">
|
<small style="display: inline-block;width: max-content;">{{TransLeft $stats.Max $transfer}} / {{$TransLeftPercent}} %</small>
|
||||||
<small style="display: inline-block;width: max-content;">{{TransLeft $stats.Max $transfer}} / {{$TransLeftPercent}} %</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</td>
|
||||||
{{end}}
|
</tr>
|
||||||
{{end}}
|
{{end}}
|
||||||
</tbody>
|
{{end}}
|
||||||
</table>
|
</tbody>
|
||||||
</section>
|
</table>
|
||||||
{{end}}
|
</section>
|
||||||
{{template "theme-server-status/content-footer" .}}
|
{{end}}
|
||||||
</div>
|
{{template "theme-server-status/footer" .}}
|
||||||
<script>
|
<script>
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
@@ -240,5 +238,4 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
{{template "theme-server-status/footer" .}}
|
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -28,8 +28,8 @@ pre_check() {
|
|||||||
|
|
||||||
## China_IP
|
## China_IP
|
||||||
if [[ -z "${CN}" ]]; then
|
if [[ -z "${CN}" ]]; then
|
||||||
if [[ $(curl -m 10 -s https://ipapi.co/json | grep 'China') != "" ]]; then
|
if [[ $(curl -m 10 -s http://ip-api.com/json |grep 'country' |grep -q 'China') != "" ]]; then
|
||||||
echo "According to the information provided by ipapi.co, the current IP may be in China"
|
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
|
read -e -r -p "Is the installation done with a Chinese Mirror? [Y/n] (Custom Mirror Input 3):" input
|
||||||
case $input in
|
case $input in
|
||||||
[yY][eE][sS] | [yY])
|
[yY][eE][sS] | [yY])
|
||||||
|
|||||||
+14
-2
@@ -49,8 +49,20 @@ if ([string]::IsNullOrWhiteSpace($agenttag)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#Region判断
|
#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"
|
$ipapi = ""
|
||||||
$region=$ipapi.cc
|
$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
|
echo $ipapi
|
||||||
if($region -ne "CN"){
|
if($region -ne "CN"){
|
||||||
$download = "https://github.com/$agentrepo/releases/download/$agenttag/$file"
|
$download = "https://github.com/$agentrepo/releases/download/$agenttag/$file"
|
||||||
|
|||||||
+45
-4
@@ -12,7 +12,7 @@ NZ_DASHBOARD_PATH="${NZ_BASE_PATH}/dashboard"
|
|||||||
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
|
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
|
||||||
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
|
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
|
||||||
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
|
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
|
||||||
NZ_VERSION="v0.17.1"
|
NZ_VERSION="v0.18.3"
|
||||||
|
|
||||||
red='\033[0;31m'
|
red='\033[0;31m'
|
||||||
green='\033[0;32m'
|
green='\033[0;32m'
|
||||||
@@ -48,6 +48,22 @@ err() {
|
|||||||
printf "${red}$*${plain}\n" >&2
|
printf "${red}$*${plain}\n" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
geo_check() {
|
||||||
|
api_list="https://blog.cloudflare.com/cdn-cgi/trace https://dash.cloudflare.com/cdn-cgi/trace https://cf-ns.com/cdn-cgi/trace"
|
||||||
|
ua="Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0"
|
||||||
|
set -- $api_list
|
||||||
|
for url in $api_list; do
|
||||||
|
text="$(curl -A "$ua" -m 10 -s $url)"
|
||||||
|
endpoint="$(echo $text | sed -n 's/.*h=\([^ ]*\).*/\1/p')"
|
||||||
|
if echo $text | grep -qw 'CN'; then
|
||||||
|
isCN=true
|
||||||
|
break
|
||||||
|
elif echo $url | grep -q $endpoint; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
pre_check() {
|
pre_check() {
|
||||||
## os_arch
|
## os_arch
|
||||||
if uname -m | grep -q 'x86_64'; then
|
if uname -m | grep -q 'x86_64'; then
|
||||||
@@ -66,8 +82,9 @@ pre_check() {
|
|||||||
|
|
||||||
## China_IP
|
## China_IP
|
||||||
if [ -z "$CN" ]; then
|
if [ -z "$CN" ]; then
|
||||||
if curl -m 10 -s https://ipapi.co/json | grep -q 'China'; then
|
geo_check
|
||||||
echo "根据ipapi.co提供的信息,当前IP可能在中国"
|
if [ ! -z "$isCN" ]; then
|
||||||
|
echo "根据geoip api提供的信息,当前IP可能在中国"
|
||||||
printf "是否选用中国镜像完成安装? [Y/n] (自定义镜像输入 3):"
|
printf "是否选用中国镜像完成安装? [Y/n] (自定义镜像输入 3):"
|
||||||
read -r input
|
read -r input
|
||||||
case $input in
|
case $input in
|
||||||
@@ -556,6 +573,17 @@ restart_and_update_docker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
restart_and_update_standalone() {
|
restart_and_update_standalone() {
|
||||||
|
local version=$(curl -m 10 -sL "https://api.github.com/repos/naiba/nezha/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/nezha/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/naiba/nezha/" | grep "option\.value" | awk -F "'" '{print $2}' | sed 's/naiba\/nezha@/v/g')
|
||||||
|
fi
|
||||||
|
if [ ! -n "$version" ]; then
|
||||||
|
version=$(curl -m 10 -sL "https://gcore.jsdelivr.net/gh/naiba/nezha/" | grep "option\.value" | awk -F "'" '{print $2}' | sed 's/naiba\/nezha@/v/g')
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$os_alpine" != 1 ]; then
|
if [ "$os_alpine" != 1 ]; then
|
||||||
sudo systemctl daemon-reload
|
sudo systemctl daemon-reload
|
||||||
sudo systemctl stop nezha-dashboard
|
sudo systemctl stop nezha-dashboard
|
||||||
@@ -563,7 +591,20 @@ restart_and_update_standalone() {
|
|||||||
sudo rc-service nezha-dashboard stop
|
sudo rc-service nezha-dashboard stop
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sudo wget -qO $NZ_DASHBOARD_PATH/app.zip https://${GITHUB_URL}/naiba/nezha/releases/latest/download/dashboard-linux-$os_arch.zip >/dev/null 2>&1 && sudo unzip -qq $NZ_DASHBOARD_PATH/app.zip -d $NZ_DASHBOARD_PATH && sudo mv $NZ_DASHBOARD_PATH/dist/dashboard-linux-$os_arch $NZ_DASHBOARD_PATH/app && sudo rm -r $NZ_DASHBOARD_PATH/app.zip $NZ_DASHBOARD_PATH/dist
|
if [ ! -n "$version" ]; then
|
||||||
|
err "获取版本号失败,请检查本机能否链接 https://api.github.com/repos/naiba/nezha/releases/latest"
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
echo "当前最新版本为: ${version}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$CN" ]; then
|
||||||
|
NZ_DASHBOARD_URL="https://${GITHUB_URL}/naiba/nezha/releases/download/$version/dashboard-linux-$os_arch.zip"
|
||||||
|
else
|
||||||
|
NZ_DASHBOARD_URL="https://${GITHUB_URL}/naibahq/nezha/releases/download/$version/dashboard-linux-$os_arch.zip"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo wget -qO $NZ_DASHBOARD_PATH/app.zip $NZ_DASHBOARD_URL >/dev/null 2>&1 && sudo unzip -qq $NZ_DASHBOARD_PATH/app.zip -d $NZ_DASHBOARD_PATH && sudo mv $NZ_DASHBOARD_PATH/dist/dashboard-linux-$os_arch $NZ_DASHBOARD_PATH/app && sudo rm -r $NZ_DASHBOARD_PATH/app.zip $NZ_DASHBOARD_PATH/dist
|
||||||
|
|
||||||
if [ "$os_alpine" != 1 ]; then
|
if [ "$os_alpine" != 1 ]; then
|
||||||
sudo systemctl enable nezha-dashboard
|
sudo systemctl enable nezha-dashboard
|
||||||
|
|||||||
+45
-4
@@ -12,7 +12,7 @@ NZ_DASHBOARD_PATH="${NZ_BASE_PATH}/dashboard"
|
|||||||
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
|
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
|
||||||
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
|
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
|
||||||
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
|
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
|
||||||
NZ_VERSION="v0.17.1"
|
NZ_VERSION="v0.18.3"
|
||||||
|
|
||||||
red='\033[0;31m'
|
red='\033[0;31m'
|
||||||
green='\033[0;32m'
|
green='\033[0;32m'
|
||||||
@@ -48,6 +48,22 @@ err() {
|
|||||||
printf "${red}$*${plain}\n" >&2
|
printf "${red}$*${plain}\n" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
geo_check() {
|
||||||
|
api_list="https://blog.cloudflare.com/cdn-cgi/trace https://dash.cloudflare.com/cdn-cgi/trace https://cf-ns.com/cdn-cgi/trace"
|
||||||
|
ua="Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0"
|
||||||
|
set -- $api_list
|
||||||
|
for url in $api_list; do
|
||||||
|
text="$(curl -A "$ua" -m 10 -s $url)"
|
||||||
|
endpoint="$(echo $text | sed -n 's/.*h=\([^ ]*\).*/\1/p')"
|
||||||
|
if echo $text | grep -qw 'CN'; then
|
||||||
|
isCN=true
|
||||||
|
break
|
||||||
|
elif echo $url | grep -q $endpoint; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
pre_check() {
|
pre_check() {
|
||||||
## os_arch
|
## os_arch
|
||||||
if uname -m | grep -q 'x86_64'; then
|
if uname -m | grep -q 'x86_64'; then
|
||||||
@@ -66,8 +82,9 @@ pre_check() {
|
|||||||
|
|
||||||
## China_IP
|
## China_IP
|
||||||
if [ -z "$CN" ]; then
|
if [ -z "$CN" ]; then
|
||||||
if curl -m 10 -s https://ipapi.co/json | grep -q 'China'; then
|
geo_check
|
||||||
echo "According to the information provided by ipapi.co, the current IP may be in China"
|
if [ ! -z "$isCN" ]; then
|
||||||
|
echo "According to the information provided by various geoip api, the current IP may be in China"
|
||||||
printf "Will the installation be done with a Chinese Mirror? [Y/n] (Custom Mirror Input 3): "
|
printf "Will the installation be done with a Chinese Mirror? [Y/n] (Custom Mirror Input 3): "
|
||||||
read -r input
|
read -r input
|
||||||
case $input in
|
case $input in
|
||||||
@@ -550,6 +567,24 @@ restart_and_update_docker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
restart_and_update_standalone() {
|
restart_and_update_standalone() {
|
||||||
|
local version=$(curl -m 10 -sL "https://api.github.com/repos/naiba/nezha/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/nezha/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/naiba/nezha/" | grep "option\.value" | awk -F "'" '{print $2}' | sed 's/naiba\/nezha@/v/g')
|
||||||
|
fi
|
||||||
|
if [ ! -n "$version" ]; then
|
||||||
|
version=$(curl -m 10 -sL "https://gcore.jsdelivr.net/gh/naiba/nezha/" | grep "option\.value" | awk -F "'" '{print $2}' | sed 's/naiba\/nezha@/v/g')
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -n "$version" ]; then
|
||||||
|
err "Fail to obtaine agent version, please check if the network can link https://api.github.com/repos/nezhahq/agent/releases/latest"
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
echo "The current latest version is: ${version}"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$os_alpine" != 1 ]; then
|
if [ "$os_alpine" != 1 ]; then
|
||||||
sudo systemctl daemon-reload
|
sudo systemctl daemon-reload
|
||||||
sudo systemctl stop nezha-dashboard
|
sudo systemctl stop nezha-dashboard
|
||||||
@@ -557,7 +592,13 @@ restart_and_update_standalone() {
|
|||||||
sudo rc-service nezha-dashboard stop
|
sudo rc-service nezha-dashboard stop
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sudo wget -qO $NZ_DASHBOARD_PATH/app.zip https://${GITHUB_URL}/naiba/nezha/releases/latest/download/dashboard-linux-$os_arch.zip >/dev/null 2>&1 && sudo unzip -qq $NZ_DASHBOARD_PATH/app.zip -d $NZ_DASHBOARD_PATH && sudo mv $NZ_DASHBOARD_PATH/dist/dashboard-linux-$os_arch $NZ_DASHBOARD_PATH/app && sudo rm -r $NZ_DASHBOARD_PATH/app.zip $NZ_DASHBOARD_PATH/dist
|
if [ -z "$CN" ]; then
|
||||||
|
NZ_DASHBOARD_URL="https://${GITHUB_URL}/naiba/nezha/releases/download/$version/dashboard-linux-$os_arch.zip"
|
||||||
|
else
|
||||||
|
NZ_DASHBOARD_URL="https://${GITHUB_URL}/naibahq/nezha/releases/download/$version/dashboard-linux-$os_arch.zip"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo wget -qO $NZ_DASHBOARD_PATH/app.zip $NZ_DASHBOARD_URL >/dev/null 2>&1 && sudo unzip -qq $NZ_DASHBOARD_PATH/app.zip -d $NZ_DASHBOARD_PATH && sudo mv $NZ_DASHBOARD_PATH/dist/dashboard-linux-$os_arch $NZ_DASHBOARD_PATH/app && sudo rm -r $NZ_DASHBOARD_PATH/app.zip $NZ_DASHBOARD_PATH/dist
|
||||||
|
|
||||||
if [ "$os_alpine" != 1 ]; then
|
if [ "$os_alpine" != 1 ]; then
|
||||||
sudo systemctl enable nezha-dashboard
|
sudo systemctl enable nezha-dashboard
|
||||||
|
|||||||
@@ -136,6 +136,5 @@ LOOP:
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
<-endCh
|
<-endCh
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-1
@@ -4,10 +4,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/naiba/nezha/pkg/ddns"
|
"github.com/naiba/nezha/pkg/ddns"
|
||||||
|
"github.com/naiba/nezha/pkg/geoip"
|
||||||
"github.com/naiba/nezha/pkg/grpcx"
|
"github.com/naiba/nezha/pkg/grpcx"
|
||||||
"github.com/naiba/nezha/pkg/utils"
|
"github.com/naiba/nezha/pkg/utils"
|
||||||
|
|
||||||
@@ -136,7 +138,6 @@ func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Rece
|
|||||||
singleton.ServerList[clientID].Host != nil &&
|
singleton.ServerList[clientID].Host != nil &&
|
||||||
host.IP != "" &&
|
host.IP != "" &&
|
||||||
singleton.ServerList[clientID].Host.IP != host.IP {
|
singleton.ServerList[clientID].Host.IP != host.IP {
|
||||||
|
|
||||||
serverDomain := singleton.ServerList[clientID].DDNSDomain
|
serverDomain := singleton.ServerList[clientID].DDNSDomain
|
||||||
if singleton.Conf.DDNS.Provider == "" {
|
if singleton.Conf.DDNS.Provider == "" {
|
||||||
provider, err = singleton.GetDDNSProviderFromProfile(singleton.ServerList[clientID].DDNSProfile)
|
provider, err = singleton.GetDDNSProviderFromProfile(singleton.ServerList[clientID].DDNSProfile)
|
||||||
@@ -189,6 +190,11 @@ 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)
|
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
|
singleton.ServerList[clientID].Host = &host
|
||||||
return &pb.Receipt{Proced: true}, nil
|
return &pb.Receipt{Proced: true}, nil
|
||||||
}
|
}
|
||||||
@@ -217,3 +223,30 @@ func (s *NezhaHandler) IOStream(stream pb.NezhaService_IOStreamServer) error {
|
|||||||
iw.Wait()
|
iw.Wait()
|
||||||
return nil
|
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
@@ -15,8 +15,6 @@ var (
|
|||||||
|
|
||||||
ServerAPI = &ServerAPIService{}
|
ServerAPI = &ServerAPIService{}
|
||||||
MonitorAPI = &MonitorAPIService{}
|
MonitorAPI = &MonitorAPIService{}
|
||||||
|
|
||||||
once = &sync.Once{}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServerAPIService struct{}
|
type ServerAPIService struct{}
|
||||||
@@ -28,13 +26,14 @@ type CommonResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CommonServerInfo struct {
|
type CommonServerInfo struct {
|
||||||
ID uint64 `json:"id"`
|
ID uint64 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Tag string `json:"tag"`
|
Tag string `json:"tag"`
|
||||||
LastActive int64 `json:"last_active"`
|
LastActive int64 `json:"last_active"`
|
||||||
IPV4 string `json:"ipv4"`
|
IPV4 string `json:"ipv4"`
|
||||||
IPV6 string `json:"ipv6"`
|
IPV6 string `json:"ipv6"`
|
||||||
ValidIP string `json:"valid_ip"`
|
ValidIP string `json:"valid_ip"`
|
||||||
|
DisplayIndex int `json:"display_index"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// StatusResponse 服务器状态子结构 包含服务器信息与状态信息
|
// StatusResponse 服务器状态子结构 包含服务器信息与状态信息
|
||||||
@@ -78,7 +77,7 @@ func InitAPI() {
|
|||||||
UserIDToApiTokenList = make(map[uint64][]string)
|
UserIDToApiTokenList = make(map[uint64][]string)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadAPI() {
|
func loadAPI() {
|
||||||
InitAPI()
|
InitAPI()
|
||||||
var tokenList []*model.ApiToken
|
var tokenList []*model.ApiToken
|
||||||
DB.Find(&tokenList)
|
DB.Find(&tokenList)
|
||||||
@@ -143,13 +142,14 @@ func (s *ServerAPIService) GetAllStatus() *ServerStatusResponse {
|
|||||||
}
|
}
|
||||||
ipv4, ipv6, validIP := utils.SplitIPAddr(host.IP)
|
ipv4, ipv6, validIP := utils.SplitIPAddr(host.IP)
|
||||||
info := CommonServerInfo{
|
info := CommonServerInfo{
|
||||||
ID: v.ID,
|
ID: v.ID,
|
||||||
Name: v.Name,
|
Name: v.Name,
|
||||||
Tag: v.Tag,
|
Tag: v.Tag,
|
||||||
LastActive: v.LastActive.Unix(),
|
LastActive: v.LastActive.Unix(),
|
||||||
IPV4: ipv4,
|
IPV4: ipv4,
|
||||||
IPV6: ipv6,
|
IPV6: ipv6,
|
||||||
ValidIP: validIP,
|
ValidIP: validIP,
|
||||||
|
DisplayIndex: v.DisplayIndex,
|
||||||
}
|
}
|
||||||
res.Result = append(res.Result, &StatusResponse{
|
res.Result = append(res.Result, &StatusResponse{
|
||||||
CommonServerInfo: info,
|
CommonServerInfo: info,
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ func InitCronTask() {
|
|||||||
Crons = make(map[uint64]*model.Cron)
|
Crons = make(map[uint64]*model.Cron)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadCronTasks 加载计划任务
|
// loadCronTasks 加载计划任务
|
||||||
func LoadCronTasks() {
|
func loadCronTasks() {
|
||||||
InitCronTask()
|
InitCronTask()
|
||||||
var crons []model.Cron
|
var crons []model.Cron
|
||||||
DB.Find(&crons)
|
DB.Find(&crons)
|
||||||
|
|||||||
@@ -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]
|
||||||
|
}
|
||||||
@@ -24,8 +24,8 @@ func InitNotification() {
|
|||||||
NotificationIDToTag = make(map[uint64]string)
|
NotificationIDToTag = make(map[uint64]string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadNotifications 从 DB 初始化通知方式相关参数
|
// loadNotifications 从 DB 初始化通知方式相关参数
|
||||||
func LoadNotifications() {
|
func loadNotifications() {
|
||||||
InitNotification()
|
InitNotification()
|
||||||
notificationsLock.Lock()
|
notificationsLock.Lock()
|
||||||
defer notificationsLock.Unlock()
|
defer notificationsLock.Unlock()
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ func InitServer() {
|
|||||||
ServerTagToIDList = make(map[string][]uint64)
|
ServerTagToIDList = make(map[string][]uint64)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadServers 加载服务器列表并根据ID排序
|
// loadServers 加载服务器列表并根据ID排序
|
||||||
func LoadServers() {
|
func loadServers() {
|
||||||
InitServer()
|
InitServer()
|
||||||
var servers []model.Server
|
var servers []model.Server
|
||||||
DB.Find(&servers)
|
DB.Find(&servers)
|
||||||
|
|||||||
@@ -34,10 +34,11 @@ func InitTimezoneAndCache() {
|
|||||||
|
|
||||||
// LoadSingleton 加载子服务并执行
|
// LoadSingleton 加载子服务并执行
|
||||||
func LoadSingleton() {
|
func LoadSingleton() {
|
||||||
LoadNotifications() // 加载通知服务
|
loadNotifications() // 加载通知服务
|
||||||
LoadServers() // 加载服务器列表
|
loadServers() // 加载服务器列表
|
||||||
LoadCronTasks() // 加载定时任务
|
loadCronTasks() // 加载定时任务
|
||||||
LoadAPI()
|
loadAPI()
|
||||||
|
initNAT()
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitConfigFromPath 从给出的文件路径中加载配置
|
// InitConfigFromPath 从给出的文件路径中加载配置
|
||||||
@@ -47,11 +48,11 @@ func InitConfigFromPath(path string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
ValidateConfig()
|
validateConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateConfig 验证配置文件有效性
|
// validateConfig 验证配置文件有效性
|
||||||
func ValidateConfig() {
|
func validateConfig() {
|
||||||
var err error
|
var err error
|
||||||
if Conf.DDNS.Provider == "" {
|
if Conf.DDNS.Provider == "" {
|
||||||
err = ValidateDDNSProvidersFromProfiles()
|
err = ValidateDDNSProvidersFromProfiles()
|
||||||
@@ -82,7 +83,8 @@ func InitDBFromPath(path string) {
|
|||||||
}
|
}
|
||||||
err = DB.AutoMigrate(model.Server{}, model.User{},
|
err = DB.AutoMigrate(model.Server{}, model.User{},
|
||||||
model.Notification{}, model.AlertRule{}, model.Monitor{},
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user