Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f95191c8af | ||
|
|
34fedd91d2 | ||
|
|
f675b884f5 | ||
|
|
0b13fec029 | ||
|
|
67b788a969 | ||
|
|
b63f693661 | ||
|
|
ac64aa7355 | ||
|
|
05d69d5c07 |
@@ -108,3 +108,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/workflows/sync.py
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import os
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
import hashlib
|
||||||
|
from github import Github
|
||||||
|
|
||||||
|
|
||||||
|
def get_github_latest_release():
|
||||||
|
g = Github()
|
||||||
|
repo = g.get_repo("naiba/nezha")
|
||||||
|
release = repo.get_latest_release()
|
||||||
|
if release:
|
||||||
|
print(f"Latest release tag is: {release.tag_name}")
|
||||||
|
print(f"Latest release info is: {release.body}")
|
||||||
|
files = []
|
||||||
|
for asset in release.get_assets():
|
||||||
|
url = asset.browser_download_url
|
||||||
|
name = asset.name
|
||||||
|
|
||||||
|
response = requests.get(url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
with open(name, 'wb') as f:
|
||||||
|
f.write(response.content)
|
||||||
|
print(f"Downloaded {name}")
|
||||||
|
else:
|
||||||
|
print(f"Failed to download {name}")
|
||||||
|
file_abs_path = get_abs_path(asset.name)
|
||||||
|
files.append(file_abs_path)
|
||||||
|
sync_to_gitee(release.tag_name, release.body, files)
|
||||||
|
else:
|
||||||
|
print("No releases found.")
|
||||||
|
|
||||||
|
|
||||||
|
def sync_to_gitee(tag: str, body: str, files: slice):
|
||||||
|
release_id = ""
|
||||||
|
owner = "naibahq"
|
||||||
|
repo = "nezha"
|
||||||
|
release_api_uri = f"https://gitee.com/api/v5/repos/{owner}/{repo}/releases"
|
||||||
|
api_client = requests.Session()
|
||||||
|
api_client.headers.update({
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
})
|
||||||
|
|
||||||
|
access_token = os.environ['GITEE_TOKEN']
|
||||||
|
release_data = {
|
||||||
|
'access_token': access_token,
|
||||||
|
'tag_name': tag,
|
||||||
|
'name': tag,
|
||||||
|
'body': body,
|
||||||
|
'prerelease': False,
|
||||||
|
'target_commitish': 'master'
|
||||||
|
}
|
||||||
|
release_api_response = api_client.post(release_api_uri, json=release_data)
|
||||||
|
if release_api_response.status_code == 201:
|
||||||
|
release_info = release_api_response.json()
|
||||||
|
release_id = release_info.get('id')
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
f"Request failed with status code {release_api_response.status_code}")
|
||||||
|
|
||||||
|
print(f"Gitee release id: {release_id}")
|
||||||
|
asset_api_uri = f"{release_api_uri}/{release_id}/attach_files"
|
||||||
|
|
||||||
|
for file_path in files:
|
||||||
|
files = {
|
||||||
|
'file': open(file_path, 'rb')
|
||||||
|
}
|
||||||
|
|
||||||
|
asset_api_response = requests.post(
|
||||||
|
asset_api_uri, params={'access_token': access_token}, files=files)
|
||||||
|
|
||||||
|
if asset_api_response.status_code == 201:
|
||||||
|
asset_info = asset_api_response.json()
|
||||||
|
asset_name = asset_info.get('name')
|
||||||
|
print(f"Successfully uploaded {asset_name}!")
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
f"Request failed with status code {asset_api_response.status_code}")
|
||||||
|
|
||||||
|
api_client.close()
|
||||||
|
print("Sync is completed!")
|
||||||
|
|
||||||
|
|
||||||
|
def get_abs_path(path: str):
|
||||||
|
wd = os.getcwd()
|
||||||
|
return os.path.join(wd, path)
|
||||||
|
|
||||||
|
|
||||||
|
get_github_latest_release()
|
||||||
@@ -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.1-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>
|
||||||
|
|||||||
@@ -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"}),
|
||||||
|
|||||||
+7
-1
@@ -21,12 +21,18 @@ const (
|
|||||||
TaskTypeUpgrade
|
TaskTypeUpgrade
|
||||||
TaskTypeKeepalive
|
TaskTypeKeepalive
|
||||||
TaskTypeTerminalGRPC
|
TaskTypeTerminalGRPC
|
||||||
|
TaskTypeNAT
|
||||||
)
|
)
|
||||||
|
|
||||||
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 +120,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"`
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
Vendored
+3
@@ -648,3 +648,6 @@ other = "Disable Switch Template in Frontend"
|
|||||||
|
|
||||||
[ServersOnWorldMap]
|
[ServersOnWorldMap]
|
||||||
other = "Servers On World Map"
|
other = "Servers On World Map"
|
||||||
|
|
||||||
|
[NAT]
|
||||||
|
other = "NAT"
|
||||||
Vendored
+3
@@ -648,3 +648,6 @@ other = "Deshabilitar Cambio de Plantilla en Frontend"
|
|||||||
|
|
||||||
[ServersOnWorldMap]
|
[ServersOnWorldMap]
|
||||||
other = "Servidores en el mapa mundial"
|
other = "Servidores en el mapa mundial"
|
||||||
|
|
||||||
|
[NAT]
|
||||||
|
other = "NAT"
|
||||||
Vendored
+3
@@ -648,3 +648,6 @@ other = "禁止前台切换模板"
|
|||||||
|
|
||||||
[ServersOnWorldMap]
|
[ServersOnWorldMap]
|
||||||
other = "服务器世界分布图"
|
other = "服务器世界分布图"
|
||||||
|
|
||||||
|
[NAT]
|
||||||
|
other = "内网穿透"
|
||||||
Vendored
+3
@@ -648,3 +648,6 @@ other = "禁止前台切換主題"
|
|||||||
|
|
||||||
[ServersOnWorldMap]
|
[ServersOnWorldMap]
|
||||||
other = "伺服器世界分布圖"
|
other = "伺服器世界分布圖"
|
||||||
|
|
||||||
|
[NAT]
|
||||||
|
other = "NAT"
|
||||||
@@ -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 })
|
||||||
}
|
}
|
||||||
|
|||||||
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}}
|
||||||
+26
-2
@@ -12,7 +12,7 @@ NZ_DASHBOARD_PATH="${NZ_BASE_PATH}/dashboard"
|
|||||||
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
|
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
|
||||||
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
|
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
|
||||||
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
|
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
|
||||||
NZ_VERSION="v0.17.1"
|
NZ_VERSION="v0.18.1"
|
||||||
|
|
||||||
red='\033[0;31m'
|
red='\033[0;31m'
|
||||||
green='\033[0;32m'
|
green='\033[0;32m'
|
||||||
@@ -556,6 +556,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 +574,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
|
||||||
|
|||||||
+26
-2
@@ -12,7 +12,7 @@ NZ_DASHBOARD_PATH="${NZ_BASE_PATH}/dashboard"
|
|||||||
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
|
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
|
||||||
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
|
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
|
||||||
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
|
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
|
||||||
NZ_VERSION="v0.17.1"
|
NZ_VERSION="v0.18.1"
|
||||||
|
|
||||||
red='\033[0;31m'
|
red='\033[0;31m'
|
||||||
green='\033[0;32m'
|
green='\033[0;32m'
|
||||||
@@ -550,6 +550,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 +575,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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ var (
|
|||||||
|
|
||||||
ServerAPI = &ServerAPIService{}
|
ServerAPI = &ServerAPIService{}
|
||||||
MonitorAPI = &MonitorAPIService{}
|
MonitorAPI = &MonitorAPIService{}
|
||||||
|
|
||||||
once = &sync.Once{}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServerAPIService struct{}
|
type ServerAPIService struct{}
|
||||||
@@ -78,7 +76,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)
|
||||||
|
|||||||
@@ -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