refactor: rename monitor -> service

This commit is contained in:
naiba
2024-10-25 00:13:45 +08:00
parent d4be2a0bcf
commit eae12d8df2
16 changed files with 260 additions and 657 deletions

View File

@@ -8,7 +8,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/hashicorp/go-uuid"
"github.com/jinzhu/copier"
"github.com/naiba/nezha/model"
"github.com/naiba/nezha/pkg/utils"
@@ -24,7 +23,6 @@ type commonPage struct {
func (cp *commonPage) serve() {
cr := cp.r.Group("")
cr.GET("/service", cp.service)
// TODO: 界面直接跳转使用该接口
cr.GET("/network/:id", cp.network)
cr.GET("/network", cp.network)
@@ -32,34 +30,9 @@ func (cp *commonPage) serve() {
cr.GET("/file/:id", cp.fm)
}
func (p *commonPage) service(c *gin.Context) {
res, _, _ := requestGroup.Do("servicePage", func() (interface{}, error) {
singleton.AlertsLock.RLock()
defer singleton.AlertsLock.RUnlock()
var stats map[uint64]model.ServiceItemResponse
var statsStore map[uint64]model.CycleTransferStats
copier.Copy(&stats, singleton.ServiceSentinelShared.LoadStats())
copier.Copy(&statsStore, singleton.AlertsCycleTransferStatsStore)
for k, service := range stats {
if !service.Monitor.EnableShowInService {
delete(stats, k)
}
}
return []interface {
}{
stats, statsStore,
}, nil
})
c.HTML(http.StatusOK, "", gin.H{
// "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "ServicesStatus"}),
"Services": res.([]interface{})[0],
"CycleTransferStats": res.([]interface{})[1],
})
}
func (cp *commonPage) network(c *gin.Context) {
var (
monitorHistory *model.MonitorHistory
monitorHistory *model.ServiceHistory
servers []model.Server
serverIdsWithMonitor []uint64
monitorInfos = []byte("{}")
@@ -68,7 +41,7 @@ func (cp *commonPage) network(c *gin.Context) {
if len(singleton.SortedServerList) > 0 {
id = singleton.SortedServerList[0].ID
}
if err := singleton.DB.Model(&model.MonitorHistory{}).Select("monitor_id, server_id").
if err := singleton.DB.Model(&model.ServiceHistory{}).Select("monitor_id, server_id").
Where("monitor_id != 0 and server_id != 0").Limit(1).First(&monitorHistory).Error; err != nil {
// mygin.ShowErrorPage(c, mygin.ErrInfo{
// Code: http.StatusForbidden,
@@ -114,12 +87,10 @@ func (cp *commonPage) network(c *gin.Context) {
return
}
}
monitorHistories := singleton.MonitorAPI.GetMonitorHistories(map[string]any{"server_id": id})
monitorInfos, _ = utils.Json.Marshal(monitorHistories)
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
var isViewPasswordVerfied bool
if err := singleton.DB.Model(&model.MonitorHistory{}).
if err := singleton.DB.Model(&model.ServiceHistory{}).
Select("distinct(server_id)").
Where("server_id != 0").
Find(&serverIdsWithMonitor).

View File

@@ -63,10 +63,10 @@ func routers(r *gin.Engine) {
auth.POST("/user", commonHandler(createUser))
auth.POST("/batch-delete/user", commonHandler(batchDeleteUser))
auth.GET("/monitor", commonHandler(listMonitor))
auth.POST("/monitor", commonHandler(createMonitor))
auth.PATCH("/monitor/:id", commonHandler(updateMonitor))
auth.POST("/batch-delete/monitor", commonHandler(batchDeleteMonitor))
auth.GET("/service", commonHandler(listService))
auth.POST("/service", commonHandler(createService))
auth.PATCH("/service/:id", commonHandler(updateService))
auth.POST("/batch-delete/service", commonHandler(batchDeleteService))
auth.POST("/server-group", commonHandler(createServerGroup))
auth.PATCH("/server-group/:id", commonHandler(updateServerGroup))

View File

@@ -43,121 +43,6 @@ func (ma *memberAPI) serve() {
mr.POST("/setting", ma.updateSetting)
mr.DELETE("/:model/:id", ma.delete)
mr.POST("/logout", ma.logout)
mr.GET("/token", ma.getToken)
mr.POST("/token", ma.issueNewToken)
mr.DELETE("/token/:token", ma.deleteToken)
}
type apiResult struct {
Token string `json:"token"`
Note string `json:"note"`
}
// getToken 获取 Token
func (ma *memberAPI) getToken(c *gin.Context) {
u := c.MustGet(model.CtxKeyAuthorizedUser).(*model.User)
singleton.ApiLock.RLock()
defer singleton.ApiLock.RUnlock()
tokenList := singleton.UserIDToApiTokenList[u.ID]
res := make([]*apiResult, len(tokenList))
for i, token := range tokenList {
res[i] = &apiResult{
Token: token,
Note: singleton.ApiTokenList[token].Note,
}
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"result": res,
})
}
type TokenForm struct {
Note string
}
// issueNewToken 生成新的 token
func (ma *memberAPI) issueNewToken(c *gin.Context) {
u := c.MustGet(model.CtxKeyAuthorizedUser).(*model.User)
tf := &TokenForm{}
err := c.ShouldBindJSON(tf)
if err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("请求错误:%s", err),
})
return
}
secureToken, err := utils.GenerateRandomString(32)
if err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("请求错误:%s", err),
})
return
}
token := &model.ApiToken{
UserID: u.ID,
Token: secureToken,
Note: tf.Note,
}
singleton.DB.Create(token)
singleton.ApiLock.Lock()
singleton.ApiTokenList[token.Token] = token
singleton.UserIDToApiTokenList[u.ID] = append(singleton.UserIDToApiTokenList[u.ID], token.Token)
singleton.ApiLock.Unlock()
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
Message: "success",
Result: map[string]string{
"token": token.Token,
"note": token.Note,
},
})
}
// deleteToken 删除 token
func (ma *memberAPI) deleteToken(c *gin.Context) {
token := c.Param("token")
if token == "" {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: "token 不能为空",
})
return
}
singleton.ApiLock.Lock()
defer singleton.ApiLock.Unlock()
if _, ok := singleton.ApiTokenList[token]; !ok {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: "token 不存在",
})
return
}
// 在数据库中删除该Token
singleton.DB.Unscoped().Delete(&model.ApiToken{}, "token = ?", token)
// 在UserIDToApiTokenList中删除该Token
for i, t := range singleton.UserIDToApiTokenList[singleton.ApiTokenList[token].UserID] {
if t == token {
singleton.UserIDToApiTokenList[singleton.ApiTokenList[token].UserID] = append(singleton.UserIDToApiTokenList[singleton.ApiTokenList[token].UserID][:i], singleton.UserIDToApiTokenList[singleton.ApiTokenList[token].UserID][i+1:]...)
break
}
}
if len(singleton.UserIDToApiTokenList[singleton.ApiTokenList[token].UserID]) == 0 {
delete(singleton.UserIDToApiTokenList, singleton.ApiTokenList[token].UserID)
}
// 在ApiTokenList中删除该Token
delete(singleton.ApiTokenList, token)
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
Message: "success",
})
}
func (ma *memberAPI) delete(c *gin.Context) {

View File

@@ -26,16 +26,6 @@ func (mp *memberPage) serve() {
mr.GET("/ddns", mp.ddns)
mr.GET("/nat", mp.nat)
mr.GET("/setting", mp.setting)
mr.GET("/api", mp.api)
}
func (mp *memberPage) api(c *gin.Context) {
singleton.ApiLock.RLock()
defer singleton.ApiLock.RUnlock()
c.HTML(http.StatusOK, "dashboard-", gin.H{
// "title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "ApiManagement"}),
"Tokens": singleton.ApiTokenList,
})
}
func (mp *memberPage) cron(c *gin.Context) {

View File

@@ -6,42 +6,67 @@ import (
"strings"
"github.com/gin-gonic/gin"
"github.com/jinzhu/copier"
"github.com/naiba/nezha/model"
"github.com/naiba/nezha/service/singleton"
"gorm.io/gorm"
)
// List monitor
// @Summary List monitor
// List service
// @Summary List service
// @Security BearerAuth
// @Schemes
// @Description List monitor
// @Description List service
// @Tags auth required
// @Produce json
// @Success 200 {object} model.CommonResponse[[]model.Monitor]
// @Router /monitor [get]
func listMonitor(c *gin.Context) ([]*model.Monitor, error) {
return singleton.ServiceSentinelShared.Monitors(), nil
// @Success 200 {object} model.CommonResponse[model.ServiceResponse]
// @Router /service [get]
func listService(c *gin.Context) (*model.ServiceResponse, error) {
res, err, _ := requestGroup.Do("list-service", func() (interface{}, error) {
singleton.AlertsLock.RLock()
defer singleton.AlertsLock.RUnlock()
var stats map[uint64]model.ServiceResponseItem
var statsStore map[uint64]model.CycleTransferStats
copier.Copy(&stats, singleton.ServiceSentinelShared.LoadStats())
copier.Copy(&statsStore, singleton.AlertsCycleTransferStatsStore)
for k, service := range stats {
if !service.Service.EnableShowInService {
delete(stats, k)
}
}
return []interface {
}{
stats, statsStore,
}, nil
})
if err != nil {
return nil, err
}
return &model.ServiceResponse{
Services: res.([]interface{})[0].(map[uint64]model.ServiceResponseItem),
CycleTransferStats: res.([]interface{})[1].(map[uint64]model.CycleTransferStats),
}, nil
}
// Create monitor
// @Summary Create monitor
// Create service
// @Summary Create service
// @Security BearerAuth
// @Schemes
// @Description Create monitor
// @Description Create service
// @Tags auth required
// @Accept json
// @param request body model.MonitorForm true "Monitor Request"
// @param request body model.ServiceForm true "Service Request"
// @Produce json
// @Success 200 {object} model.CommonResponse[uint64]
// @Router /monitor [post]
func createMonitor(c *gin.Context) (uint64, error) {
var mf model.MonitorForm
// @Router /service [post]
func createService(c *gin.Context) (uint64, error) {
var mf model.ServiceForm
if err := c.ShouldBindJSON(&mf); err != nil {
return 0, err
}
var m model.Monitor
var m model.Service
m.Name = mf.Name
m.Target = strings.TrimSpace(mf.Target)
m.Type = mf.Type
@@ -69,42 +94,42 @@ func createMonitor(c *gin.Context) (uint64, error) {
var err error
if m.Cover == 0 {
err = singleton.DB.Unscoped().Delete(&model.MonitorHistory{}, "monitor_id = ? and server_id in (?)", m.ID, skipServers).Error
err = singleton.DB.Unscoped().Delete(&model.ServiceHistory{}, "service_id = ? and server_id in (?)", m.ID, skipServers).Error
} else {
err = singleton.DB.Unscoped().Delete(&model.MonitorHistory{}, "monitor_id = ? and server_id not in (?)", m.ID, skipServers).Error
err = singleton.DB.Unscoped().Delete(&model.ServiceHistory{}, "service_id = ? and server_id not in (?)", m.ID, skipServers).Error
}
if err != nil {
return 0, err
}
return m.ID, singleton.ServiceSentinelShared.OnMonitorUpdate(m)
return m.ID, singleton.ServiceSentinelShared.OnServiceUpdate(m)
}
// Update monitor
// @Summary Update monitor
// Update service
// @Summary Update service
// @Security BearerAuth
// @Schemes
// @Description Update monitor
// @Description Update service
// @Tags auth required
// @Accept json
// @param id path uint true "Monitor ID"
// @param request body model.MonitorForm true "Monitor Request"
// @param id path uint true "Service ID"
// @param request body model.ServiceForm true "Service Request"
// @Produce json
// @Success 200 {object} model.CommonResponse[any]
// @Router /monitor/{id} [patch]
func updateMonitor(c *gin.Context) (any, error) {
// @Router /service/{id} [patch]
func updateService(c *gin.Context) (any, error) {
strID := c.Param("id")
id, err := strconv.ParseUint(strID, 10, 64)
if err != nil {
return nil, err
}
var mf model.MonitorForm
var mf model.ServiceForm
if err := c.ShouldBindJSON(&mf); err != nil {
return nil, err
}
var m model.Monitor
var m model.Service
if err := singleton.DB.First(&m, id).Error; err != nil {
return nil, fmt.Errorf("monitor id %d does not exist", id)
return nil, fmt.Errorf("service id %d does not exist", id)
}
m.Name = mf.Name
m.Target = strings.TrimSpace(mf.Target)
@@ -132,42 +157,42 @@ func updateMonitor(c *gin.Context) (any, error) {
}
if m.Cover == 0 {
err = singleton.DB.Unscoped().Delete(&model.MonitorHistory{}, "monitor_id = ? and server_id in (?)", m.ID, skipServers).Error
err = singleton.DB.Unscoped().Delete(&model.ServiceHistory{}, "service_id = ? and server_id in (?)", m.ID, skipServers).Error
} else {
err = singleton.DB.Unscoped().Delete(&model.MonitorHistory{}, "monitor_id = ? and server_id not in (?)", m.ID, skipServers).Error
err = singleton.DB.Unscoped().Delete(&model.ServiceHistory{}, "service_id = ? and server_id not in (?)", m.ID, skipServers).Error
}
if err != nil {
return nil, err
}
return nil, singleton.ServiceSentinelShared.OnMonitorUpdate(m)
return nil, singleton.ServiceSentinelShared.OnServiceUpdate(m)
}
// Batch delete monitor
// @Summary Batch delete monitor
// Batch delete service
// @Summary Batch delete service
// @Security BearerAuth
// @Schemes
// @Description Batch delete monitor
// @Description Batch delete service
// @Tags auth required
// @Accept json
// @param request body []uint true "id list"
// @Produce json
// @Success 200 {object} model.CommonResponse[any]
// @Router /batch-delete/monitor [post]
func batchDeleteMonitor(c *gin.Context) (any, error) {
// @Router /batch-delete/service [post]
func batchDeleteService(c *gin.Context) (any, error) {
var ids []uint64
if err := c.ShouldBindJSON(&ids); err != nil {
return nil, err
}
err := singleton.DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Unscoped().Delete(&model.Monitor{}, "id in (?)", ids).Error; err != nil {
if err := tx.Unscoped().Delete(&model.Service{}, "id in (?)", ids).Error; err != nil {
return err
}
return tx.Unscoped().Delete(&model.MonitorHistory{}, "monitor_id in (?)", ids).Error
return tx.Unscoped().Delete(&model.ServiceHistory{}, "service_id in (?)", ids).Error
})
if err != nil {
return nil, err
}
singleton.ServiceSentinelShared.OnMonitorDelete(ids)
singleton.ServiceSentinelShared.OnServiceDelete(ids)
return nil, nil
}

View File

@@ -71,7 +71,7 @@ func initSystem() {
singleton.LoadSingleton()
// 每天的3:30 对 监控记录 和 流量记录 进行清理
if _, err := singleton.Cron.AddFunc("0 30 3 * * *", singleton.CleanMonitorHistory); err != nil {
if _, err := singleton.Cron.AddFunc("0 30 3 * * *", singleton.CleanServiceHistory); err != nil {
panic(err)
}
@@ -113,8 +113,8 @@ func main() {
log.Fatal(err)
}
singleton.CleanMonitorHistory()
serviceSentinelDispatchBus := make(chan model.Monitor) // 用于传递服务监控任务信息的channel
singleton.CleanServiceHistory()
serviceSentinelDispatchBus := make(chan model.Service) // 用于传递服务监控任务信息的channel
go rpc.DispatchTask(serviceSentinelDispatchBus)
go rpc.DispatchKeepalive()
go singleton.AlertSentinelStart()

View File

@@ -22,7 +22,7 @@ func ServeRPC() *grpc.Server {
return server
}
func DispatchTask(serviceSentinelDispatchBus <-chan model.Monitor) {
func DispatchTask(serviceSentinelDispatchBus <-chan model.Service) {
workedServerIndex := 0
for task := range serviceSentinelDispatchBus {
round := 0
@@ -42,17 +42,17 @@ func DispatchTask(serviceSentinelDispatchBus <-chan model.Monitor) {
continue
}
// 如果此任务不可使用此服务器请求,跳过这个服务器(有些 IPv6 only 开了 NAT64 的机器请求 IPv4 总会出问题)
if (task.Cover == model.MonitorCoverAll && task.SkipServers[singleton.SortedServerList[workedServerIndex].ID]) ||
(task.Cover == model.MonitorCoverIgnoreAll && !task.SkipServers[singleton.SortedServerList[workedServerIndex].ID]) {
if (task.Cover == model.ServiceCoverAll && task.SkipServers[singleton.SortedServerList[workedServerIndex].ID]) ||
(task.Cover == model.ServiceCoverIgnoreAll && !task.SkipServers[singleton.SortedServerList[workedServerIndex].ID]) {
workedServerIndex++
continue
}
if task.Cover == model.MonitorCoverIgnoreAll && task.SkipServers[singleton.SortedServerList[workedServerIndex].ID] {
if task.Cover == model.ServiceCoverIgnoreAll && task.SkipServers[singleton.SortedServerList[workedServerIndex].ID] {
singleton.SortedServerList[workedServerIndex].TaskStream.Send(task.PB())
workedServerIndex++
continue
}
if task.Cover == model.MonitorCoverAll && !task.SkipServers[singleton.SortedServerList[workedServerIndex].ID] {
if task.Cover == model.ServiceCoverAll && !task.SkipServers[singleton.SortedServerList[workedServerIndex].ID] {
singleton.SortedServerList[workedServerIndex].TaskStream.Send(task.PB())
workedServerIndex++
continue