mirror of
https://github.com/nezhahq/nezha.git
synced 2025-01-22 20:58:14 -05:00
add cron, nat api & refactor alert rule (#459)
* add cron api & refactor alert rule * add nat api * fix swagger * remove unnecessary steps
This commit is contained in:
parent
ebc4fad9bc
commit
68d7e16773
@ -64,13 +64,12 @@ func createAlertRule(c *gin.Context) (uint64, error) {
|
|||||||
enable := arf.Enable
|
enable := arf.Enable
|
||||||
r.TriggerMode = arf.TriggerMode
|
r.TriggerMode = arf.TriggerMode
|
||||||
r.Enable = &enable
|
r.Enable = &enable
|
||||||
r.ID = arf.ID
|
|
||||||
|
|
||||||
if err := singleton.DB.Create(&r).Error; err != nil {
|
if err := singleton.DB.Create(&r).Error; err != nil {
|
||||||
return 0, newGormError("%v", err)
|
return 0, newGormError("%v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
singleton.OnRefreshOrAddAlert(r)
|
singleton.OnRefreshOrAddAlert(&r)
|
||||||
return r.ID, nil
|
return r.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,13 +114,12 @@ func updateAlertRule(c *gin.Context) (any, error) {
|
|||||||
enable := arf.Enable
|
enable := arf.Enable
|
||||||
r.TriggerMode = arf.TriggerMode
|
r.TriggerMode = arf.TriggerMode
|
||||||
r.Enable = &enable
|
r.Enable = &enable
|
||||||
r.ID = arf.ID
|
|
||||||
|
|
||||||
if err := singleton.DB.Save(&r).Error; err != nil {
|
if err := singleton.DB.Save(&r).Error; err != nil {
|
||||||
return 0, newGormError("%v", err)
|
return 0, newGormError("%v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
singleton.OnRefreshOrAddAlert(r)
|
singleton.OnRefreshOrAddAlert(&r)
|
||||||
return r.ID, nil
|
return r.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,7 +141,7 @@ func batchDeleteAlertRule(c *gin.Context) (any, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := singleton.DB.Unscoped().Delete(&model.DDNSProfile{}, "id in (?)", ar).Error; err != nil {
|
if err := singleton.DB.Unscoped().Delete(&model.AlertRule{}, "id in (?)", ar).Error; err != nil {
|
||||||
return nil, newGormError("%v", err)
|
return nil, newGormError("%v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,12 +97,23 @@ func routers(r *gin.Engine) {
|
|||||||
auth.PATCH("/alert-rule/:id", commonHandler(updateAlertRule))
|
auth.PATCH("/alert-rule/:id", commonHandler(updateAlertRule))
|
||||||
auth.POST("/batch-delete/alert-rule", commonHandler(batchDeleteAlertRule))
|
auth.POST("/batch-delete/alert-rule", commonHandler(batchDeleteAlertRule))
|
||||||
|
|
||||||
|
auth.GET("/cron", commonHandler(listCron))
|
||||||
|
auth.POST("/cron", commonHandler(createCron))
|
||||||
|
auth.PATCH("/cron/:id", commonHandler(updateCron))
|
||||||
|
auth.GET("/cron/:id/manual", commonHandler(manualTriggerCron))
|
||||||
|
auth.POST("/batch-delete/cron", commonHandler(batchDeleteCron))
|
||||||
|
|
||||||
auth.GET("/ddns", commonHandler(listDDNS))
|
auth.GET("/ddns", commonHandler(listDDNS))
|
||||||
auth.GET("/ddns/providers", commonHandler(listProviders))
|
auth.GET("/ddns/providers", commonHandler(listProviders))
|
||||||
auth.POST("/ddns", commonHandler(createDDNS))
|
auth.POST("/ddns", commonHandler(createDDNS))
|
||||||
auth.PATCH("/ddns/:id", commonHandler(updateDDNS))
|
auth.PATCH("/ddns/:id", commonHandler(updateDDNS))
|
||||||
auth.POST("/batch-delete/ddns", commonHandler(batchDeleteDDNS))
|
auth.POST("/batch-delete/ddns", commonHandler(batchDeleteDDNS))
|
||||||
|
|
||||||
|
auth.GET("/nat", commonHandler(listNAT))
|
||||||
|
auth.POST("/nat", commonHandler(createNAT))
|
||||||
|
auth.PATCH("/nat/:id", commonHandler(updateNAT))
|
||||||
|
auth.POST("/batch-delete/nat", commonHandler(batchDeleteNAT))
|
||||||
|
|
||||||
r.NoRoute(fallbackToFrontend)
|
r.NoRoute(fallbackToFrontend)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
193
cmd/dashboard/controller/cron.go
Normal file
193
cmd/dashboard/controller/cron.go
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/jinzhu/copier"
|
||||||
|
|
||||||
|
"github.com/naiba/nezha/model"
|
||||||
|
"github.com/naiba/nezha/service/singleton"
|
||||||
|
)
|
||||||
|
|
||||||
|
// List schedule tasks
|
||||||
|
// @Summary List schedule tasks
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Schemes
|
||||||
|
// @Description List schedule tasks
|
||||||
|
// @Tags auth required
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} model.CommonResponse[[]model.Cron]
|
||||||
|
// @Router /cron [get]
|
||||||
|
func listCron(c *gin.Context) ([]*model.Cron, error) {
|
||||||
|
singleton.CronLock.RLock()
|
||||||
|
defer singleton.CronLock.RUnlock()
|
||||||
|
|
||||||
|
var cr []*model.Cron
|
||||||
|
if err := copier.Copy(&cr, &singleton.CronList); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new schedule task
|
||||||
|
// @Summary Create new schedule task
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Schemes
|
||||||
|
// @Description Create new schedule task
|
||||||
|
// @Tags auth required
|
||||||
|
// @Accept json
|
||||||
|
// @param request body model.CronForm true "CronForm"
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} model.CommonResponse[uint64]
|
||||||
|
// @Router /cron [post]
|
||||||
|
func createCron(c *gin.Context) (uint64, error) {
|
||||||
|
var cf model.CronForm
|
||||||
|
var cr model.Cron
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&cf); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cr.TaskType = cf.TaskType
|
||||||
|
cr.Name = cf.Name
|
||||||
|
cr.Scheduler = cf.Scheduler
|
||||||
|
cr.Command = cf.Command
|
||||||
|
cr.Servers = cf.Servers
|
||||||
|
cr.PushSuccessful = cf.PushSuccessful
|
||||||
|
cr.NotificationGroupID = cf.NotificationGroupID
|
||||||
|
cr.Cover = cf.Cover
|
||||||
|
|
||||||
|
if cr.TaskType == model.CronTypeCronTask && cr.Cover == model.CronCoverAlertTrigger {
|
||||||
|
return 0, errors.New("计划任务类型不得使用触发服务器执行方式")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对于计划任务类型,需要更新CronJob
|
||||||
|
var err error
|
||||||
|
if cf.TaskType == model.CronTypeCronTask {
|
||||||
|
if cr.CronJobID, err = singleton.Cron.AddFunc(cr.Scheduler, singleton.CronTrigger(&cr)); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = singleton.DB.Create(&cr).Error; err != nil {
|
||||||
|
return 0, newGormError("%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
singleton.OnRefreshOrAddCron(&cr)
|
||||||
|
singleton.UpdateCronList()
|
||||||
|
return cr.ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update schedule task
|
||||||
|
// @Summary Update schedule task
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Schemes
|
||||||
|
// @Description Update schedule task
|
||||||
|
// @Tags auth required
|
||||||
|
// @Accept json
|
||||||
|
// @param id path uint true "Task ID"
|
||||||
|
// @param request body model.CronForm true "CronForm"
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} model.CommonResponse[any]
|
||||||
|
// @Router /cron/{id} [patch]
|
||||||
|
func updateCron(c *gin.Context) (any, error) {
|
||||||
|
idStr := c.Param("id")
|
||||||
|
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var cf model.CronForm
|
||||||
|
if err := c.ShouldBindJSON(&cf); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var cr model.Cron
|
||||||
|
if err := singleton.DB.First(&cr, id).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("task id %d does not exist", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
cr.TaskType = cf.TaskType
|
||||||
|
cr.Name = cf.Name
|
||||||
|
cr.Scheduler = cf.Scheduler
|
||||||
|
cr.Command = cf.Command
|
||||||
|
cr.Servers = cf.Servers
|
||||||
|
cr.PushSuccessful = cf.PushSuccessful
|
||||||
|
cr.NotificationGroupID = cf.NotificationGroupID
|
||||||
|
cr.Cover = cf.Cover
|
||||||
|
|
||||||
|
if cr.TaskType == model.CronTypeCronTask && cr.Cover == model.CronCoverAlertTrigger {
|
||||||
|
return nil, errors.New("计划任务类型不得使用触发服务器执行方式")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对于计划任务类型,需要更新CronJob
|
||||||
|
if cf.TaskType == model.CronTypeCronTask {
|
||||||
|
if cr.CronJobID, err = singleton.Cron.AddFunc(cr.Scheduler, singleton.CronTrigger(&cr)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = singleton.DB.Save(&cr).Error; err != nil {
|
||||||
|
return nil, newGormError("%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
singleton.OnRefreshOrAddCron(&cr)
|
||||||
|
singleton.UpdateCronList()
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger schedule task
|
||||||
|
// @Summary Trigger schedule task
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Schemes
|
||||||
|
// @Description Trigger schedule task
|
||||||
|
// @Tags auth required
|
||||||
|
// @Accept json
|
||||||
|
// @param id path uint true "Task ID"
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} model.CommonResponse[any]
|
||||||
|
// @Router /cron/{id}/manual [get]
|
||||||
|
func manualTriggerCron(c *gin.Context) (any, error) {
|
||||||
|
idStr := c.Param("id")
|
||||||
|
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var cr model.Cron
|
||||||
|
if err := singleton.DB.First(&cr, id).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("task id %d does not exist", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
singleton.ManualTrigger(&cr)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batch delete schedule tasks
|
||||||
|
// @Summary Batch delete schedule tasks
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Schemes
|
||||||
|
// @Description Batch delete schedule tasks
|
||||||
|
// @Tags auth required
|
||||||
|
// @Accept json
|
||||||
|
// @param request body []uint64 true "id list"
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} model.CommonResponse[any]
|
||||||
|
// @Router /batch-delete/cron [post]
|
||||||
|
func batchDeleteCron(c *gin.Context) (any, error) {
|
||||||
|
var cr []uint64
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&cr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := singleton.DB.Unscoped().Delete(&model.Cron{}, "id in (?)", cr).Error; err != nil {
|
||||||
|
return nil, newGormError("%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
singleton.OnDeleteCron(cr)
|
||||||
|
return nil, nil
|
||||||
|
}
|
@ -128,7 +128,6 @@ func updateDDNS(c *gin.Context) (any, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
p.Name = df.Name
|
p.Name = df.Name
|
||||||
p.ID = id
|
|
||||||
enableIPv4 := df.EnableIPv4
|
enableIPv4 := df.EnableIPv4
|
||||||
enableIPv6 := df.EnableIPv6
|
enableIPv6 := df.EnableIPv6
|
||||||
p.EnableIPv4 = &enableIPv4
|
p.EnableIPv4 = &enableIPv4
|
||||||
|
@ -58,12 +58,6 @@ func (ma *memberAPI) delete(c *gin.Context) {
|
|||||||
var err error
|
var err error
|
||||||
switch c.Param("model") {
|
switch c.Param("model") {
|
||||||
|
|
||||||
case "nat":
|
|
||||||
err = singleton.DB.Unscoped().Delete(&model.NAT{}, "id = ?", id).Error
|
|
||||||
if err == nil {
|
|
||||||
singleton.OnNATUpdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
case "cron":
|
case "cron":
|
||||||
err = singleton.DB.Unscoped().Delete(&model.Cron{}, "id = ?", id).Error
|
err = singleton.DB.Unscoped().Delete(&model.Cron{}, "id = ?", id).Error
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -139,12 +133,6 @@ func (ma *memberAPI) addOrEditCron(c *gin.Context) {
|
|||||||
err = tx.Save(&cr).Error
|
err = tx.Save(&cr).Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err == nil {
|
|
||||||
// 对于计划任务类型,需要更新CronJob
|
|
||||||
if cf.TaskType == model.CronTypeCronTask {
|
|
||||||
cr.CronJobID, err = singleton.Cron.AddFunc(cr.Scheduler, singleton.CronTrigger(cr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = tx.Commit().Error
|
err = tx.Commit().Error
|
||||||
} else {
|
} else {
|
||||||
@ -183,7 +171,7 @@ func (ma *memberAPI) manualTrigger(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
singleton.ManualTrigger(cr)
|
//singleton.ManualTrigger(cr)
|
||||||
|
|
||||||
c.JSON(http.StatusOK, model.Response{
|
c.JSON(http.StatusOK, model.Response{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
@ -452,7 +440,7 @@ func (ma *memberAPI) addOrEditNAT(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
singleton.OnNATUpdate()
|
//singleton.OnNATUpdate()
|
||||||
c.JSON(http.StatusOK, model.Response{
|
c.JSON(http.StatusOK, model.Response{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
})
|
})
|
||||||
@ -538,7 +526,7 @@ func (ma *memberAPI) addOrEditAlertRule(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
singleton.OnRefreshOrAddAlert(r)
|
//singleton.OnRefreshOrAddAlert(r)
|
||||||
c.JSON(http.StatusOK, model.Response{
|
c.JSON(http.StatusOK, model.Response{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
})
|
})
|
||||||
|
138
cmd/dashboard/controller/nat.go
Normal file
138
cmd/dashboard/controller/nat.go
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/jinzhu/copier"
|
||||||
|
|
||||||
|
"github.com/naiba/nezha/model"
|
||||||
|
"github.com/naiba/nezha/service/singleton"
|
||||||
|
)
|
||||||
|
|
||||||
|
// List NAT Profiles
|
||||||
|
// @Summary List NAT profiles
|
||||||
|
// @Schemes
|
||||||
|
// @Description List NAT profiles
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Tags auth required
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} model.CommonResponse[[]model.NAT]
|
||||||
|
// @Router /nat [get]
|
||||||
|
func listNAT(c *gin.Context) ([]*model.NAT, error) {
|
||||||
|
var n []*model.NAT
|
||||||
|
|
||||||
|
singleton.NATCacheRwLock.RLock()
|
||||||
|
defer singleton.NATCacheRwLock.RUnlock()
|
||||||
|
|
||||||
|
if err := copier.Copy(&n, &singleton.NATList); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add NAT profile
|
||||||
|
// @Summary Add NAT profile
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Schemes
|
||||||
|
// @Description Add NAT profile
|
||||||
|
// @Tags auth required
|
||||||
|
// @Accept json
|
||||||
|
// @param request body model.NATForm true "NAT Request"
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} model.CommonResponse[uint64]
|
||||||
|
// @Router /nat [post]
|
||||||
|
func createNAT(c *gin.Context) (uint64, error) {
|
||||||
|
var nf model.NATForm
|
||||||
|
var n model.NAT
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&nf); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
n.Name = nf.Name
|
||||||
|
n.Domain = nf.Domain
|
||||||
|
n.Host = nf.Host
|
||||||
|
n.ServerID = nf.ServerID
|
||||||
|
|
||||||
|
if err := singleton.DB.Create(&n).Error; err != nil {
|
||||||
|
return 0, newGormError("%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
singleton.OnNATUpdate(&n)
|
||||||
|
singleton.UpdateNATList()
|
||||||
|
return n.ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edit NAT profile
|
||||||
|
// @Summary Edit NAT profile
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Schemes
|
||||||
|
// @Description Edit NAT profile
|
||||||
|
// @Tags auth required
|
||||||
|
// @Accept json
|
||||||
|
// @param id path uint true "Profile ID"
|
||||||
|
// @param request body model.NATForm true "NAT Request"
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} model.CommonResponse[any]
|
||||||
|
// @Router /nat/{id} [patch]
|
||||||
|
func updateNAT(c *gin.Context) (any, error) {
|
||||||
|
idStr := c.Param("id")
|
||||||
|
|
||||||
|
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var nf model.NATForm
|
||||||
|
if err := c.ShouldBindJSON(&nf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var n model.NAT
|
||||||
|
if err = singleton.DB.First(&n, id).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("profile id %d does not exist", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
n.Name = nf.Name
|
||||||
|
n.Domain = nf.Domain
|
||||||
|
n.Host = nf.Host
|
||||||
|
n.ServerID = nf.ServerID
|
||||||
|
|
||||||
|
if err := singleton.DB.Save(&n).Error; err != nil {
|
||||||
|
return 0, newGormError("%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
singleton.OnNATUpdate(&n)
|
||||||
|
singleton.UpdateNATList()
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batch delete NAT configurations
|
||||||
|
// @Summary Batch delete NAT configurations
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Schemes
|
||||||
|
// @Description Batch delete NAT configurations
|
||||||
|
// @Tags auth required
|
||||||
|
// @Accept json
|
||||||
|
// @param request body []uint64 true "id list"
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} model.CommonResponse[any]
|
||||||
|
// @Router /batch-delete/nat [post]
|
||||||
|
func batchDeleteNAT(c *gin.Context) (any, error) {
|
||||||
|
var n []uint64
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&n); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := singleton.DB.Unscoped().Delete(&model.NAT{}, "id in (?)", n).Error; err != nil {
|
||||||
|
return nil, newGormError("%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
singleton.OnNATDelete(n)
|
||||||
|
singleton.UpdateNATList()
|
||||||
|
return nil, nil
|
||||||
|
}
|
@ -57,7 +57,7 @@ func createNotification(c *gin.Context) (uint64, error) {
|
|||||||
n.URL = nf.URL
|
n.URL = nf.URL
|
||||||
verifySSL := nf.VerifySSL
|
verifySSL := nf.VerifySSL
|
||||||
n.VerifySSL = &verifySSL
|
n.VerifySSL = &verifySSL
|
||||||
n.ID = nf.ID
|
|
||||||
ns := model.NotificationServerBundle{
|
ns := model.NotificationServerBundle{
|
||||||
Notification: &n,
|
Notification: &n,
|
||||||
Server: nil,
|
Server: nil,
|
||||||
@ -115,7 +115,7 @@ func updateNotification(c *gin.Context) (any, error) {
|
|||||||
n.URL = nf.URL
|
n.URL = nf.URL
|
||||||
verifySSL := nf.VerifySSL
|
verifySSL := nf.VerifySSL
|
||||||
n.VerifySSL = &verifySSL
|
n.VerifySSL = &verifySSL
|
||||||
n.ID = nf.ID
|
|
||||||
ns := model.NotificationServerBundle{
|
ns := model.NotificationServerBundle{
|
||||||
Notification: &n,
|
Notification: &n,
|
||||||
Server: nil,
|
Server: nil,
|
||||||
|
@ -17,7 +17,7 @@ import (
|
|||||||
// @Schemes
|
// @Schemes
|
||||||
// @Description List notification group
|
// @Description List notification group
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth
|
||||||
// @Tags common
|
// @Tags auth required
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} model.CommonResponse[[]model.NotificationGroupResponseItem]
|
// @Success 200 {object} model.CommonResponse[[]model.NotificationGroupResponseItem]
|
||||||
// @Router /notification-group [get]
|
// @Router /notification-group [get]
|
||||||
|
@ -62,7 +62,6 @@ func updateServer(c *gin.Context) (any, error) {
|
|||||||
|
|
||||||
s.Name = sf.Name
|
s.Name = sf.Name
|
||||||
s.DisplayIndex = sf.DisplayIndex
|
s.DisplayIndex = sf.DisplayIndex
|
||||||
s.ID = id
|
|
||||||
s.Note = sf.Note
|
s.Note = sf.Note
|
||||||
s.PublicNote = sf.PublicNote
|
s.PublicNote = sf.PublicNote
|
||||||
s.HideForGuest = sf.HideForGuest
|
s.HideForGuest = sf.HideForGuest
|
||||||
|
@ -61,55 +61,55 @@ func (r *AlertRule) Enabled() bool {
|
|||||||
return r.Enable != nil && *r.Enable
|
return r.Enable != nil && *r.Enable
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot 对传入的Server进行该报警规则下所有type的检查 返回包含每项检查结果的空接口
|
// Snapshot 对传入的Server进行该报警规则下所有type的检查 返回每项检查结果
|
||||||
func (r *AlertRule) Snapshot(cycleTransferStats *CycleTransferStats, server *Server, db *gorm.DB) []interface{} {
|
func (r *AlertRule) Snapshot(cycleTransferStats *CycleTransferStats, server *Server, db *gorm.DB) []bool {
|
||||||
var point []interface{}
|
point := make([]bool, 0, len(r.Rules))
|
||||||
for i := 0; i < len(r.Rules); i++ {
|
for _, rule := range r.Rules {
|
||||||
point = append(point, r.Rules[i].Snapshot(cycleTransferStats, server, db))
|
point = append(point, rule.Snapshot(cycleTransferStats, server, db))
|
||||||
}
|
}
|
||||||
return point
|
return point
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check 传入包含当前报警规则下所有type检查结果的空接口 返回报警持续时间与是否通过报警检查(通过则返回true)
|
// Check 传入包含当前报警规则下所有type检查结果 返回报警持续时间与是否通过报警检查(通过则返回true)
|
||||||
func (r *AlertRule) Check(points [][]interface{}) (int, bool) {
|
func (r *AlertRule) Check(points [][]bool) (maxDuration int, passed bool) {
|
||||||
var maxNum int // 报警持续时间
|
failCount := 0 // 检查未通过的个数
|
||||||
var count int // 检查未通过的个数
|
|
||||||
for i := 0; i < len(r.Rules); i++ {
|
for i, rule := range r.Rules {
|
||||||
if r.Rules[i].IsTransferDurationRule() {
|
if rule.IsTransferDurationRule() {
|
||||||
// 循环区间流量报警
|
// 循环区间流量报警
|
||||||
if maxNum < 1 {
|
if maxDuration < 1 {
|
||||||
maxNum = 1
|
maxDuration = 1
|
||||||
}
|
}
|
||||||
for j := len(points[i]) - 1; j >= 0; j-- {
|
for j := len(points[i]) - 1; j >= 0; j-- {
|
||||||
if points[i][j] != nil {
|
if !points[i][j] {
|
||||||
count++
|
failCount++
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 常规报警
|
// 常规报警
|
||||||
total := 0.0
|
duration := int(rule.Duration)
|
||||||
fail := 0.0
|
if duration > maxDuration {
|
||||||
num := int(r.Rules[i].Duration)
|
maxDuration = duration
|
||||||
if num > maxNum {
|
|
||||||
maxNum = num
|
|
||||||
}
|
}
|
||||||
if len(points) < num {
|
if len(points) < duration {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for j := len(points) - 1; j >= 0 && len(points)-num <= j; j-- {
|
|
||||||
|
total, fail := 0.0, 0.0
|
||||||
|
for j := len(points) - duration; j < len(points); j++ {
|
||||||
total++
|
total++
|
||||||
if points[j][i] != nil {
|
if !points[j][i] {
|
||||||
fail++
|
fail++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 当70%以上的采样点未通过规则判断时 才认为当前检查未通过
|
// 当70%以上的采样点未通过规则判断时 才认为当前检查未通过
|
||||||
if fail/total > 0.7 {
|
if fail/total > 0.7 {
|
||||||
count++
|
failCount++
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 仅当所有检查均未通过时 返回false
|
// 仅当所有检查均未通过时 返回false
|
||||||
return maxNum, count != len(r.Rules)
|
return maxDuration, failCount != len(r.Rules)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
type AlertRuleForm struct {
|
type AlertRuleForm struct {
|
||||||
ID uint64 `json:"id"`
|
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Rules []Rule `json:"rules"`
|
Rules []Rule `json:"rules"`
|
||||||
FailTriggerTasks []uint64 `json:"fail_trigger_tasks"` // 失败时触发的任务id
|
FailTriggerTasks []uint64 `json:"fail_trigger_tasks"` // 失败时触发的任务id
|
||||||
|
@ -18,19 +18,19 @@ const (
|
|||||||
|
|
||||||
type Cron struct {
|
type Cron struct {
|
||||||
Common
|
Common
|
||||||
Name string
|
Name string `json:"name,omitempty"`
|
||||||
TaskType uint8 `gorm:"default:0"` // 0:计划任务 1:触发任务
|
TaskType uint8 `gorm:"default:0" json:"task_type,omitempty"` // 0:计划任务 1:触发任务
|
||||||
Scheduler string //分钟 小时 天 月 星期
|
Scheduler string `json:"scheduler,omitempty"` // 分钟 小时 天 月 星期
|
||||||
Command string
|
Command string `json:"command,omitempty"`
|
||||||
Servers []uint64 `gorm:"-"`
|
Servers []uint64 `gorm:"-" json:"servers,omitempty"`
|
||||||
PushSuccessful bool // 推送成功的通知
|
PushSuccessful bool `json:"push_successful,omitempty"` // 推送成功的通知
|
||||||
NotificationGroupID uint64 // 指定通知方式的分组
|
NotificationGroupID uint64 `json:"notification_group_id,omitempty"` // 指定通知方式的分组
|
||||||
LastExecutedAt time.Time // 最后一次执行时间
|
LastExecutedAt time.Time `json:"last_executed_at,omitempty"` // 最后一次执行时间
|
||||||
LastResult bool // 最后一次执行结果
|
LastResult bool `json:"last_result,omitempty"` // 最后一次执行结果
|
||||||
Cover uint8 // 计划任务覆盖范围 (0:仅覆盖特定服务器 1:仅忽略特定服务器 2:由触发该计划任务的服务器执行)
|
Cover uint8 `json:"cover,omitempty"` // 计划任务覆盖范围 (0:仅覆盖特定服务器 1:仅忽略特定服务器 2:由触发该计划任务的服务器执行)
|
||||||
|
|
||||||
CronJobID cron.EntryID `gorm:"-"`
|
CronJobID cron.EntryID `gorm:"-" json:"cron_job_id,omitempty"`
|
||||||
ServersRaw string
|
ServersRaw string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cron) AfterFind(tx *gorm.DB) error {
|
func (c *Cron) AfterFind(tx *gorm.DB) error {
|
||||||
|
13
model/cron_api.go
Normal file
13
model/cron_api.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type CronForm struct {
|
||||||
|
ID uint64 `json:"id,omitempty"`
|
||||||
|
TaskType uint8 `json:"task_type,omitempty"` // 0:计划任务 1:触发任务
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Scheduler string `json:"scheduler,omitempty"`
|
||||||
|
Command string `json:"command,omitempty"`
|
||||||
|
Servers []uint64 `json:"servers,omitempty"`
|
||||||
|
Cover uint8 `json:"cover,omitempty"`
|
||||||
|
PushSuccessful bool `json:"push_successful,omitempty"`
|
||||||
|
NotificationGroupID uint64 `json:"notification_group_id,omitempty"`
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
type DDNSForm struct {
|
type DDNSForm struct {
|
||||||
ID uint64 `json:"id,omitempty"`
|
|
||||||
MaxRetries uint64 `json:"max_retries,omitempty"`
|
MaxRetries uint64 `json:"max_retries,omitempty"`
|
||||||
EnableIPv4 bool `json:"enable_ipv4,omitempty"`
|
EnableIPv4 bool `json:"enable_ipv4,omitempty"`
|
||||||
EnableIPv6 bool `json:"enable_ipv6,omitempty"`
|
EnableIPv6 bool `json:"enable_ipv6,omitempty"`
|
||||||
|
8
model/nat_api.go
Normal file
8
model/nat_api.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type NATForm struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
ServerID uint64 `json:"server_id,omitempty"`
|
||||||
|
Host string `json:"host,omitempty"`
|
||||||
|
Domain string `json:"domain,omitempty"`
|
||||||
|
}
|
@ -41,18 +41,6 @@ type Notification struct {
|
|||||||
VerifySSL *bool `json:"verify_ssl,omitempty"`
|
VerifySSL *bool `json:"verify_ssl,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NotificationForm struct {
|
|
||||||
ID uint64 `json:"id,omitempty"`
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
URL string `json:"url,omitempty"`
|
|
||||||
RequestMethod int `json:"request_method,omitempty"`
|
|
||||||
RequestType int `json:"request_type,omitempty"`
|
|
||||||
RequestHeader string `json:"request_header,omitempty"`
|
|
||||||
RequestBody string `json:"request_body,omitempty"`
|
|
||||||
VerifySSL bool `json:"verify_ssl,omitempty"`
|
|
||||||
SkipCheck bool `json:"skip_check,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *NotificationServerBundle) reqURL(message string) string {
|
func (ns *NotificationServerBundle) reqURL(message string) string {
|
||||||
n := ns.Notification
|
n := ns.Notification
|
||||||
return ns.replaceParamsInString(n.URL, message, func(msg string) string {
|
return ns.replaceParamsInString(n.URL, message, func(msg string) string {
|
||||||
|
12
model/notification_api.go
Normal file
12
model/notification_api.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type NotificationForm struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
RequestMethod int `json:"request_method,omitempty"`
|
||||||
|
RequestType int `json:"request_type,omitempty"`
|
||||||
|
RequestHeader string `json:"request_header,omitempty"`
|
||||||
|
RequestBody string `json:"request_body,omitempty"`
|
||||||
|
VerifySSL bool `json:"verify_ssl,omitempty"`
|
||||||
|
SkipCheck bool `json:"skip_check,omitempty"`
|
||||||
|
}
|
@ -34,8 +34,8 @@ type Rule struct {
|
|||||||
Ignore map[uint64]bool `json:"ignore,omitempty"` // 覆盖范围的排除
|
Ignore map[uint64]bool `json:"ignore,omitempty"` // 覆盖范围的排除
|
||||||
|
|
||||||
// 只作为缓存使用,记录下次该检测的时间
|
// 只作为缓存使用,记录下次该检测的时间
|
||||||
NextTransferAt map[uint64]time.Time `json:"-"`
|
NextTransferAt map[uint64]time.Time `json:"-"`
|
||||||
LastCycleStatus map[uint64]interface{} `json:"-"`
|
LastCycleStatus map[uint64]bool `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func percentage(used, total uint64) float64 {
|
func percentage(used, total uint64) float64 {
|
||||||
@ -45,15 +45,15 @@ func percentage(used, total uint64) float64 {
|
|||||||
return float64(used) * 100 / float64(total)
|
return float64(used) * 100 / float64(total)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot 未通过规则返回 struct{}{}, 通过返回 nil
|
// Snapshot 未通过规则返回 false, 通过返回 true
|
||||||
func (u *Rule) Snapshot(cycleTransferStats *CycleTransferStats, server *Server, db *gorm.DB) interface{} {
|
func (u *Rule) Snapshot(cycleTransferStats *CycleTransferStats, server *Server, db *gorm.DB) bool {
|
||||||
// 监控全部但是排除了此服务器
|
// 监控全部但是排除了此服务器
|
||||||
if u.Cover == RuleCoverAll && u.Ignore[server.ID] {
|
if u.Cover == RuleCoverAll && u.Ignore[server.ID] {
|
||||||
return nil
|
return true
|
||||||
}
|
}
|
||||||
// 忽略全部但是指定监控了此服务器
|
// 忽略全部但是指定监控了此服务器
|
||||||
if u.Cover == RuleCoverIgnoreAll && !u.Ignore[server.ID] {
|
if u.Cover == RuleCoverIgnoreAll && !u.Ignore[server.ID] {
|
||||||
return nil
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 循环区间流量检测 · 短期无需重复检测
|
// 循环区间流量检测 · 短期无需重复检测
|
||||||
@ -147,13 +147,13 @@ func (u *Rule) Snapshot(cycleTransferStats *CycleTransferStats, server *Server,
|
|||||||
u.NextTransferAt = make(map[uint64]time.Time)
|
u.NextTransferAt = make(map[uint64]time.Time)
|
||||||
}
|
}
|
||||||
if u.LastCycleStatus == nil {
|
if u.LastCycleStatus == nil {
|
||||||
u.LastCycleStatus = make(map[uint64]interface{})
|
u.LastCycleStatus = make(map[uint64]bool)
|
||||||
}
|
}
|
||||||
u.NextTransferAt[server.ID] = time.Now().Add(time.Second * time.Duration(seconds))
|
u.NextTransferAt[server.ID] = time.Now().Add(time.Second * time.Duration(seconds))
|
||||||
if (u.Max > 0 && src > u.Max) || (u.Min > 0 && src < u.Min) {
|
if (u.Max > 0 && src > u.Max) || (u.Min > 0 && src < u.Min) {
|
||||||
u.LastCycleStatus[server.ID] = struct{}{}
|
u.LastCycleStatus[server.ID] = false
|
||||||
} else {
|
} else {
|
||||||
u.LastCycleStatus[server.ID] = nil
|
u.LastCycleStatus[server.ID] = true
|
||||||
}
|
}
|
||||||
if cycleTransferStats.ServerName[server.ID] != server.Name {
|
if cycleTransferStats.ServerName[server.ID] != server.Name {
|
||||||
cycleTransferStats.ServerName[server.ID] = server.Name
|
cycleTransferStats.ServerName[server.ID] = server.Name
|
||||||
@ -166,12 +166,12 @@ func (u *Rule) Snapshot(cycleTransferStats *CycleTransferStats, server *Server,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if u.Type == "offline" && float64(time.Now().Unix())-src > 6 {
|
if u.Type == "offline" && float64(time.Now().Unix())-src > 6 {
|
||||||
return struct{}{}
|
return false
|
||||||
} else if (u.Max > 0 && src > u.Max) || (u.Min > 0 && src < u.Min) {
|
} else if (u.Max > 0 && src > u.Max) || (u.Min > 0 && src < u.Min) {
|
||||||
return struct{}{}
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsTransferDurationRule 判断该规则是否属于周期流量规则 属于则返回true
|
// IsTransferDurationRule 判断该规则是否属于周期流量规则 属于则返回true
|
||||||
|
@ -26,9 +26,9 @@ type NotificationHistory struct {
|
|||||||
var (
|
var (
|
||||||
AlertsLock sync.RWMutex
|
AlertsLock sync.RWMutex
|
||||||
Alerts []*model.AlertRule
|
Alerts []*model.AlertRule
|
||||||
alertsStore map[uint64]map[uint64][][]interface{} // [alert_id][server_id] -> 对应报警规则的检查结果
|
alertsStore map[uint64]map[uint64][][]bool // [alert_id][server_id] -> 对应报警规则的检查结果
|
||||||
alertsPrevState map[uint64]map[uint64]uint8 // [alert_id][server_id] -> 对应报警规则的上一次报警状态
|
alertsPrevState map[uint64]map[uint64]uint8 // [alert_id][server_id] -> 对应报警规则的上一次报警状态
|
||||||
AlertsCycleTransferStatsStore map[uint64]*model.CycleTransferStats // [alert_id] -> 对应报警规则的周期流量统计
|
AlertsCycleTransferStatsStore map[uint64]*model.CycleTransferStats // [alert_id] -> 对应报警规则的周期流量统计
|
||||||
)
|
)
|
||||||
|
|
||||||
// addCycleTransferStatsInfo 向AlertsCycleTransferStatsStore中添加周期流量报警统计信息
|
// addCycleTransferStatsInfo 向AlertsCycleTransferStatsStore中添加周期流量报警统计信息
|
||||||
@ -59,7 +59,7 @@ func addCycleTransferStatsInfo(alert *model.AlertRule) {
|
|||||||
|
|
||||||
// AlertSentinelStart 报警器启动
|
// AlertSentinelStart 报警器启动
|
||||||
func AlertSentinelStart() {
|
func AlertSentinelStart() {
|
||||||
alertsStore = make(map[uint64]map[uint64][][]interface{})
|
alertsStore = make(map[uint64]map[uint64][][]bool)
|
||||||
alertsPrevState = make(map[uint64]map[uint64]uint8)
|
alertsPrevState = make(map[uint64]map[uint64]uint8)
|
||||||
AlertsCycleTransferStatsStore = make(map[uint64]*model.CycleTransferStats)
|
AlertsCycleTransferStatsStore = make(map[uint64]*model.CycleTransferStats)
|
||||||
AlertsLock.Lock()
|
AlertsLock.Lock()
|
||||||
@ -67,7 +67,7 @@ func AlertSentinelStart() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
for _, alert := range Alerts {
|
for _, alert := range Alerts {
|
||||||
alertsStore[alert.ID] = make(map[uint64][][]interface{})
|
alertsStore[alert.ID] = make(map[uint64][][]bool)
|
||||||
alertsPrevState[alert.ID] = make(map[uint64]uint8)
|
alertsPrevState[alert.ID] = make(map[uint64]uint8)
|
||||||
addCycleTransferStatsInfo(alert)
|
addCycleTransferStatsInfo(alert)
|
||||||
}
|
}
|
||||||
@ -91,7 +91,7 @@ func AlertSentinelStart() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func OnRefreshOrAddAlert(alert model.AlertRule) {
|
func OnRefreshOrAddAlert(alert *model.AlertRule) {
|
||||||
AlertsLock.Lock()
|
AlertsLock.Lock()
|
||||||
defer AlertsLock.Unlock()
|
defer AlertsLock.Unlock()
|
||||||
delete(alertsStore, alert.ID)
|
delete(alertsStore, alert.ID)
|
||||||
@ -99,17 +99,17 @@ func OnRefreshOrAddAlert(alert model.AlertRule) {
|
|||||||
var isEdit bool
|
var isEdit bool
|
||||||
for i := 0; i < len(Alerts); i++ {
|
for i := 0; i < len(Alerts); i++ {
|
||||||
if Alerts[i].ID == alert.ID {
|
if Alerts[i].ID == alert.ID {
|
||||||
Alerts[i] = &alert
|
Alerts[i] = alert
|
||||||
isEdit = true
|
isEdit = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !isEdit {
|
if !isEdit {
|
||||||
Alerts = append(Alerts, &alert)
|
Alerts = append(Alerts, alert)
|
||||||
}
|
}
|
||||||
alertsStore[alert.ID] = make(map[uint64][][]interface{})
|
alertsStore[alert.ID] = make(map[uint64][][]bool)
|
||||||
alertsPrevState[alert.ID] = make(map[uint64]uint8)
|
alertsPrevState[alert.ID] = make(map[uint64]uint8)
|
||||||
delete(AlertsCycleTransferStatsStore, alert.ID)
|
delete(AlertsCycleTransferStatsStore, alert.ID)
|
||||||
addCycleTransferStatsInfo(&alert)
|
addCycleTransferStatsInfo(alert)
|
||||||
}
|
}
|
||||||
|
|
||||||
func OnDeleteAlert(id []uint64) {
|
func OnDeleteAlert(id []uint64) {
|
||||||
|
@ -3,6 +3,7 @@ package singleton
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
@ -15,8 +16,10 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
Cron *cron.Cron
|
Cron *cron.Cron
|
||||||
Crons map[uint64]*model.Cron // [CrondID] -> *model.Cron
|
Crons map[uint64]*model.Cron // [CronID] -> *model.Cron
|
||||||
CronLock sync.RWMutex
|
CronLock sync.RWMutex
|
||||||
|
|
||||||
|
CronList []*model.Cron
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitCronTask() {
|
func InitCronTask() {
|
||||||
@ -27,29 +30,28 @@ func InitCronTask() {
|
|||||||
// loadCronTasks 加载计划任务
|
// loadCronTasks 加载计划任务
|
||||||
func loadCronTasks() {
|
func loadCronTasks() {
|
||||||
InitCronTask()
|
InitCronTask()
|
||||||
var crons []model.Cron
|
DB.Find(&CronList)
|
||||||
DB.Find(&crons)
|
|
||||||
var err error
|
var err error
|
||||||
var notificationGroupList []uint64
|
var notificationGroupList []uint64
|
||||||
notificationMsgMap := make(map[uint64]*bytes.Buffer)
|
notificationMsgMap := make(map[uint64]*bytes.Buffer)
|
||||||
for i := 0; i < len(crons); i++ {
|
for i := 0; i < len(CronList); i++ {
|
||||||
// 触发任务类型无需注册
|
// 触发任务类型无需注册
|
||||||
if crons[i].TaskType == model.CronTypeTriggerTask {
|
if CronList[i].TaskType == model.CronTypeTriggerTask {
|
||||||
Crons[crons[i].ID] = &crons[i]
|
Crons[CronList[i].ID] = CronList[i]
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// 注册计划任务
|
// 注册计划任务
|
||||||
crons[i].CronJobID, err = Cron.AddFunc(crons[i].Scheduler, CronTrigger(crons[i]))
|
CronList[i].CronJobID, err = Cron.AddFunc(CronList[i].Scheduler, CronTrigger(CronList[i]))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
Crons[crons[i].ID] = &crons[i]
|
Crons[CronList[i].ID] = CronList[i]
|
||||||
} else {
|
} else {
|
||||||
// 当前通知组首次出现 将其加入通知组列表并初始化通知组消息缓存
|
// 当前通知组首次出现 将其加入通知组列表并初始化通知组消息缓存
|
||||||
if _, ok := notificationMsgMap[crons[i].NotificationGroupID]; !ok {
|
if _, ok := notificationMsgMap[CronList[i].NotificationGroupID]; !ok {
|
||||||
notificationGroupList = append(notificationGroupList, crons[i].NotificationGroupID)
|
notificationGroupList = append(notificationGroupList, CronList[i].NotificationGroupID)
|
||||||
notificationMsgMap[crons[i].NotificationGroupID] = bytes.NewBufferString("")
|
notificationMsgMap[CronList[i].NotificationGroupID] = bytes.NewBufferString("")
|
||||||
notificationMsgMap[crons[i].NotificationGroupID].WriteString("调度失败的计划任务:[")
|
notificationMsgMap[CronList[i].NotificationGroupID].WriteString("调度失败的计划任务:[")
|
||||||
}
|
}
|
||||||
notificationMsgMap[crons[i].NotificationGroupID].WriteString(fmt.Sprintf("%d,", crons[i].ID))
|
notificationMsgMap[CronList[i].NotificationGroupID].WriteString(fmt.Sprintf("%d,", CronList[i].ID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 向注册错误的计划任务所在通知组发送通知
|
// 向注册错误的计划任务所在通知组发送通知
|
||||||
@ -60,7 +62,49 @@ func loadCronTasks() {
|
|||||||
Cron.Start()
|
Cron.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
func ManualTrigger(c model.Cron) {
|
func OnRefreshOrAddCron(c *model.Cron) {
|
||||||
|
CronLock.Lock()
|
||||||
|
defer CronLock.Unlock()
|
||||||
|
crOld := Crons[c.ID]
|
||||||
|
if crOld != nil && crOld.CronJobID != 0 {
|
||||||
|
Cron.Remove(crOld.CronJobID)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(Crons, c.ID)
|
||||||
|
Crons[c.ID] = c
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateCronList() {
|
||||||
|
CronLock.RLock()
|
||||||
|
defer CronLock.RUnlock()
|
||||||
|
|
||||||
|
CronList = make([]*model.Cron, 0, len(Crons))
|
||||||
|
for _, c := range Crons {
|
||||||
|
CronList = append(CronList, c)
|
||||||
|
}
|
||||||
|
slices.SortFunc(CronList, func(a, b *model.Cron) int {
|
||||||
|
if a.ID < b.ID {
|
||||||
|
return -1
|
||||||
|
} else if a.ID == b.ID {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func OnDeleteCron(id []uint64) {
|
||||||
|
CronLock.Lock()
|
||||||
|
defer CronLock.Unlock()
|
||||||
|
for _, i := range id {
|
||||||
|
cr := Crons[i]
|
||||||
|
if cr != nil && cr.CronJobID != 0 {
|
||||||
|
Cron.Remove(cr.CronJobID)
|
||||||
|
}
|
||||||
|
delete(Crons, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ManualTrigger(c *model.Cron) {
|
||||||
CronTrigger(c)()
|
CronTrigger(c)()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,11 +120,11 @@ func SendTriggerTasks(taskIDs []uint64, triggerServer uint64) {
|
|||||||
|
|
||||||
// 依次调用CronTrigger发送任务
|
// 依次调用CronTrigger发送任务
|
||||||
for _, c := range cronLists {
|
for _, c := range cronLists {
|
||||||
go CronTrigger(*c, triggerServer)()
|
go CronTrigger(c, triggerServer)()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func CronTrigger(cr model.Cron, triggerServer ...uint64) func() {
|
func CronTrigger(cr *model.Cron, triggerServer ...uint64) func() {
|
||||||
crIgnoreMap := make(map[uint64]bool)
|
crIgnoreMap := make(map[uint64]bool)
|
||||||
for j := 0; j < len(cr.Servers); j++ {
|
for j := 0; j < len(cr.Servers); j++ {
|
||||||
crIgnoreMap[cr.Servers[j]] = true
|
crIgnoreMap[cr.Servers[j]] = true
|
||||||
|
@ -21,16 +21,14 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func initDDNS() {
|
func initDDNS() {
|
||||||
var ddns []*model.DDNSProfile
|
DB.Find(&DDNSList)
|
||||||
DB.Find(&ddns)
|
|
||||||
DDNSCacheLock.Lock()
|
DDNSCacheLock.Lock()
|
||||||
DDNSCache = make(map[uint64]*model.DDNSProfile)
|
DDNSCache = make(map[uint64]*model.DDNSProfile)
|
||||||
for i := 0; i < len(ddns); i++ {
|
for i := 0; i < len(DDNSList); i++ {
|
||||||
DDNSCache[ddns[i].ID] = ddns[i]
|
DDNSCache[DDNSList[i].ID] = DDNSList[i]
|
||||||
}
|
}
|
||||||
DDNSCacheLock.Unlock()
|
DDNSCacheLock.Unlock()
|
||||||
|
|
||||||
UpdateDDNSList()
|
|
||||||
OnNameserverUpdate()
|
OnNameserverUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,31 +1,75 @@
|
|||||||
package singleton
|
package singleton
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/naiba/nezha/model"
|
"github.com/naiba/nezha/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
var natCache = make(map[string]*model.NAT)
|
var (
|
||||||
var natCacheRwLock = new(sync.RWMutex)
|
NATCache = make(map[string]*model.NAT)
|
||||||
|
NATCacheRwLock sync.RWMutex
|
||||||
|
|
||||||
|
NATIDToDomain = make(map[uint64]string)
|
||||||
|
NATList []*model.NAT
|
||||||
|
)
|
||||||
|
|
||||||
func initNAT() {
|
func initNAT() {
|
||||||
OnNATUpdate()
|
DB.Find(&NATList)
|
||||||
}
|
NATCacheRwLock.Lock()
|
||||||
|
defer NATCacheRwLock.Unlock()
|
||||||
func OnNATUpdate() {
|
NATCache = make(map[string]*model.NAT)
|
||||||
natCacheRwLock.Lock()
|
for i := 0; i < len(NATList); i++ {
|
||||||
defer natCacheRwLock.Unlock()
|
NATCache[NATList[i].Domain] = NATList[i]
|
||||||
var nats []*model.NAT
|
NATIDToDomain[NATList[i].ID] = NATList[i].Domain
|
||||||
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 {
|
func OnNATUpdate(n *model.NAT) {
|
||||||
natCacheRwLock.RLock()
|
NATCacheRwLock.Lock()
|
||||||
defer natCacheRwLock.RUnlock()
|
defer NATCacheRwLock.Unlock()
|
||||||
return natCache[domain]
|
|
||||||
|
if oldDomain, ok := NATIDToDomain[n.ID]; ok && oldDomain != n.Domain {
|
||||||
|
delete(NATCache, oldDomain)
|
||||||
|
}
|
||||||
|
|
||||||
|
NATCache[n.Domain] = n
|
||||||
|
NATIDToDomain[n.ID] = n.Domain
|
||||||
|
}
|
||||||
|
|
||||||
|
func OnNATDelete(id []uint64) {
|
||||||
|
NATCacheRwLock.Lock()
|
||||||
|
defer NATCacheRwLock.Unlock()
|
||||||
|
|
||||||
|
for _, i := range id {
|
||||||
|
if domain, ok := NATIDToDomain[i]; ok {
|
||||||
|
delete(NATCache, domain)
|
||||||
|
delete(NATIDToDomain, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateNATList() {
|
||||||
|
NATCacheRwLock.RLock()
|
||||||
|
defer NATCacheRwLock.RUnlock()
|
||||||
|
|
||||||
|
NATList = make([]*model.NAT, 0, len(NATCache))
|
||||||
|
for _, n := range NATCache {
|
||||||
|
NATList = append(NATList, n)
|
||||||
|
}
|
||||||
|
slices.SortFunc(NATList, func(a, b *model.NAT) int {
|
||||||
|
if a.ID < b.ID {
|
||||||
|
return -1
|
||||||
|
} else if a.ID == b.ID {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNATConfigByDomain(domain string) *model.NAT {
|
||||||
|
NATCacheRwLock.RLock()
|
||||||
|
defer NATCacheRwLock.RUnlock()
|
||||||
|
return NATCache[domain]
|
||||||
}
|
}
|
||||||
|
@ -49,14 +49,13 @@ func loadNotifications() {
|
|||||||
groupNotifications[n.NotificationGroupID] = append(groupNotifications[n.NotificationGroupID], n.NotificationID)
|
groupNotifications[n.NotificationGroupID] = append(groupNotifications[n.NotificationGroupID], n.NotificationID)
|
||||||
}
|
}
|
||||||
|
|
||||||
var notifications []model.Notification
|
if err := DB.Find(&NotificationListSorted).Error; err != nil {
|
||||||
if err := DB.Find(¬ifications).Error; err != nil {
|
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationMap = make(map[uint64]*model.Notification, len(notifications))
|
NotificationMap = make(map[uint64]*model.Notification, len(NotificationListSorted))
|
||||||
for i := range notifications {
|
for i := range NotificationListSorted {
|
||||||
NotificationMap[notifications[i].ID] = ¬ifications[i]
|
NotificationMap[NotificationListSorted[i].ID] = NotificationListSorted[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
for gid, nids := range groupNotifications {
|
for gid, nids := range groupNotifications {
|
||||||
@ -75,7 +74,6 @@ func loadNotifications() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NotificationsLock.Unlock()
|
NotificationsLock.Unlock()
|
||||||
UpdateNotificationList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateNotificationList() {
|
func UpdateNotificationList() {
|
||||||
|
@ -45,8 +45,8 @@ func ReSortServer() {
|
|||||||
SortedServerLock.Lock()
|
SortedServerLock.Lock()
|
||||||
defer SortedServerLock.Unlock()
|
defer SortedServerLock.Unlock()
|
||||||
|
|
||||||
SortedServerList = []*model.Server{}
|
SortedServerList = make([]*model.Server, 0, len(ServerList))
|
||||||
SortedServerListForGuest = []*model.Server{}
|
var SortedServerListForGuest []*model.Server
|
||||||
for _, s := range ServerList {
|
for _, s := range ServerList {
|
||||||
SortedServerList = append(SortedServerList, s)
|
SortedServerList = append(SortedServerList, s)
|
||||||
if !s.HideForGuest {
|
if !s.HideForGuest {
|
||||||
|
Loading…
Reference in New Issue
Block a user