nezha/cmd/dashboard/controller/service.go

337 lines
9.5 KiB
Go
Raw Normal View History

2024-10-23 11:06:11 -04:00
package controller
import (
"strconv"
"strings"
"time"
2024-10-23 11:06:11 -04:00
"github.com/gin-gonic/gin"
2024-10-24 12:13:45 -04:00
"github.com/jinzhu/copier"
2024-11-28 06:38:54 -05:00
"github.com/nezhahq/nezha/model"
"github.com/nezhahq/nezha/service/singleton"
2024-10-23 11:06:11 -04:00
"gorm.io/gorm"
)
// Show service
// @Summary Show service
2024-10-23 11:06:11 -04:00
// @Security BearerAuth
// @Schemes
// @Description Show service
// @Tags common
2024-10-23 11:06:11 -04:00
// @Produce json
2024-10-24 12:13:45 -04:00
// @Success 200 {object} model.CommonResponse[model.ServiceResponse]
// @Router /service [get]
func showService(c *gin.Context) (*model.ServiceResponse, error) {
2024-10-24 12:13:45 -04:00
res, err, _ := requestGroup.Do("list-service", func() (interface{}, error) {
singleton.AlertsLock.RLock()
defer singleton.AlertsLock.RUnlock()
stats := singleton.ServiceSentinelShared.CopyStats()
2024-12-01 08:57:13 -05:00
var cycleTransferStats map[uint64]model.CycleTransferStats
copier.Copy(&cycleTransferStats, singleton.AlertsCycleTransferStatsStore)
2024-10-24 12:13:45 -04:00
return []interface {
}{
2024-12-01 08:57:13 -05:00
stats, cycleTransferStats,
2024-10-24 12:13:45 -04:00
}, 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
2024-10-23 11:06:11 -04:00
}
// List service
// @Summary List service
// @Security BearerAuth
// @Schemes
// @Description List service
// @Tags auth required
// @Produce json
// @Success 200 {object} model.CommonResponse[[]model.Service]
// @Router /service [get]
func listService(c *gin.Context) ([]*model.Service, error) {
singleton.ServiceSentinelShared.ServicesLock.RLock()
defer singleton.ServiceSentinelShared.ServicesLock.RUnlock()
var ss []*model.Service
if err := copier.Copy(&ss, singleton.ServiceSentinelShared.ServiceList); err != nil {
return nil, err
}
return ss, nil
}
// List service histories by server id
// @Summary List service histories by server id
// @Security BearerAuth
// @Schemes
// @Description List service histories by server id
// @Tags common
// @param id path uint true "Server ID"
// @Produce json
// @Success 200 {object} model.CommonResponse[[]model.ServiceInfos]
// @Router /service/{id} [get]
func listServiceHistory(c *gin.Context) ([]*model.ServiceInfos, error) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
return nil, err
}
singleton.ServerLock.RLock()
server, ok := singleton.ServerList[id]
if !ok {
2024-10-31 17:07:04 -04:00
return nil, singleton.Localizer.ErrorT("server not found")
}
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
authorized := isMember // TODO || isViewPasswordVerfied
if server.HideForGuest && !authorized {
2024-10-31 17:07:04 -04:00
return nil, singleton.Localizer.ErrorT("unauthorized")
}
singleton.ServerLock.RUnlock()
var serviceHistories []*model.ServiceHistory
if err := singleton.DB.Model(&model.ServiceHistory{}).Select("service_id, created_at, server_id, avg_delay").
Where("server_id = ?", id).Where("created_at >= ?", time.Now().Add(-24*time.Hour)).Order("service_id, created_at").
Scan(&serviceHistories).Error; err != nil {
return nil, err
}
singleton.ServiceSentinelShared.ServicesLock.RLock()
defer singleton.ServiceSentinelShared.ServicesLock.RUnlock()
singleton.ServerLock.RLock()
defer singleton.ServerLock.RUnlock()
var sortedServiceIDs []uint64
resultMap := make(map[uint64]*model.ServiceInfos)
for _, history := range serviceHistories {
infos, ok := resultMap[history.ServiceID]
if !ok {
infos = &model.ServiceInfos{
2024-10-27 02:49:33 -04:00
ServiceID: history.ServiceID,
ServerID: history.ServerID,
ServiceName: singleton.ServiceSentinelShared.Services[history.ServiceID].Name,
ServerName: singleton.ServerList[history.ServerID].Name,
}
resultMap[history.ServiceID] = infos
sortedServiceIDs = append(sortedServiceIDs, history.ServiceID)
}
infos.CreatedAt = append(infos.CreatedAt, history.CreatedAt.Truncate(time.Minute).Unix()*1000)
infos.AvgDelay = append(infos.AvgDelay, history.AvgDelay)
}
ret := make([]*model.ServiceInfos, 0, len(sortedServiceIDs))
for _, id := range sortedServiceIDs {
ret = append(ret, resultMap[id])
}
return ret, nil
}
// List server with service
// @Summary List server with service
// @Security BearerAuth
// @Schemes
// @Description List server with service
// @Tags common
// @Produce json
// @Success 200 {object} model.CommonResponse[[]uint64]
// @Router /service/server [get]
func listServerWithServices(c *gin.Context) ([]uint64, error) {
var serverIdsWithService []uint64
if err := singleton.DB.Model(&model.ServiceHistory{}).
Select("distinct(server_id)").
Where("server_id != 0").
Find(&serverIdsWithService).Error; err != nil {
return nil, newGormError("%v", err)
}
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
authorized := isMember // TODO || isViewPasswordVerfied
var ret []uint64
for _, id := range serverIdsWithService {
singleton.ServerLock.RLock()
server, ok := singleton.ServerList[id]
if !ok {
singleton.ServerLock.RUnlock()
2024-10-31 17:07:04 -04:00
return nil, singleton.Localizer.ErrorT("server not found")
}
if !server.HideForGuest || authorized {
ret = append(ret, id)
}
singleton.ServerLock.RUnlock()
}
return ret, nil
}
2024-10-24 12:13:45 -04:00
// Create service
// @Summary Create service
2024-10-23 11:06:11 -04:00
// @Security BearerAuth
// @Schemes
2024-10-24 12:13:45 -04:00
// @Description Create service
2024-10-23 11:06:11 -04:00
// @Tags auth required
// @Accept json
2024-10-24 12:13:45 -04:00
// @param request body model.ServiceForm true "Service Request"
2024-10-23 11:06:11 -04:00
// @Produce json
// @Success 200 {object} model.CommonResponse[uint64]
2024-10-24 12:13:45 -04:00
// @Router /service [post]
func createService(c *gin.Context) (uint64, error) {
var mf model.ServiceForm
2024-10-23 11:06:11 -04:00
if err := c.ShouldBindJSON(&mf); err != nil {
return 0, err
}
2024-12-16 10:38:31 -05:00
uid := getUid(c)
2024-10-24 12:13:45 -04:00
var m model.Service
2024-12-16 10:38:31 -05:00
m.UserID = uid
2024-10-23 11:06:11 -04:00
m.Name = mf.Name
m.Target = strings.TrimSpace(mf.Target)
m.Type = mf.Type
m.SkipServers = mf.SkipServers
m.Cover = mf.Cover
m.Notify = mf.Notify
m.NotificationGroupID = mf.NotificationGroupID
m.Duration = mf.Duration
m.LatencyNotify = mf.LatencyNotify
m.MinLatency = mf.MinLatency
m.MaxLatency = mf.MaxLatency
m.EnableShowInService = mf.EnableShowInService
m.EnableTriggerTask = mf.EnableTriggerTask
m.RecoverTriggerTasks = mf.RecoverTriggerTasks
m.FailTriggerTasks = mf.FailTriggerTasks
if err := singleton.DB.Create(&m).Error; err != nil {
2024-10-31 17:07:04 -04:00
return 0, newGormError("%v", err)
2024-10-23 11:06:11 -04:00
}
var skipServers []uint64
for k := range m.SkipServers {
skipServers = append(skipServers, k)
}
var err error
if m.Cover == 0 {
2024-10-24 12:13:45 -04:00
err = singleton.DB.Unscoped().Delete(&model.ServiceHistory{}, "service_id = ? and server_id in (?)", m.ID, skipServers).Error
2024-10-23 11:06:11 -04:00
} else {
2024-10-24 12:13:45 -04:00
err = singleton.DB.Unscoped().Delete(&model.ServiceHistory{}, "service_id = ? and server_id not in (?)", m.ID, skipServers).Error
2024-10-23 11:06:11 -04:00
}
if err != nil {
return 0, err
}
if err := singleton.ServiceSentinelShared.OnServiceUpdate(m); err != nil {
return 0, err
}
singleton.ServiceSentinelShared.UpdateServiceList()
return m.ID, nil
2024-10-23 11:06:11 -04:00
}
2024-10-24 12:13:45 -04:00
// Update service
// @Summary Update service
2024-10-23 11:06:11 -04:00
// @Security BearerAuth
// @Schemes
2024-10-24 12:13:45 -04:00
// @Description Update service
2024-10-23 11:06:11 -04:00
// @Tags auth required
// @Accept json
2024-10-24 12:13:45 -04:00
// @param id path uint true "Service ID"
// @param request body model.ServiceForm true "Service Request"
2024-10-23 11:06:11 -04:00
// @Produce json
// @Success 200 {object} model.CommonResponse[any]
2024-10-24 12:13:45 -04:00
// @Router /service/{id} [patch]
func updateService(c *gin.Context) (any, error) {
2024-10-23 11:06:11 -04:00
strID := c.Param("id")
id, err := strconv.ParseUint(strID, 10, 64)
if err != nil {
return nil, err
}
2024-10-24 12:13:45 -04:00
var mf model.ServiceForm
2024-10-23 11:06:11 -04:00
if err := c.ShouldBindJSON(&mf); err != nil {
return nil, err
}
2024-10-24 12:13:45 -04:00
var m model.Service
2024-10-23 11:06:11 -04:00
if err := singleton.DB.First(&m, id).Error; err != nil {
2024-10-31 17:07:04 -04:00
return nil, singleton.Localizer.ErrorT("service id %d does not exist", id)
2024-10-23 11:06:11 -04:00
}
m.Name = mf.Name
m.Target = strings.TrimSpace(mf.Target)
m.Type = mf.Type
m.SkipServers = mf.SkipServers
m.Cover = mf.Cover
m.Notify = mf.Notify
m.NotificationGroupID = mf.NotificationGroupID
m.Duration = mf.Duration
m.LatencyNotify = mf.LatencyNotify
m.MinLatency = mf.MinLatency
m.MaxLatency = mf.MaxLatency
m.EnableShowInService = mf.EnableShowInService
m.EnableTriggerTask = mf.EnableTriggerTask
m.RecoverTriggerTasks = mf.RecoverTriggerTasks
m.FailTriggerTasks = mf.FailTriggerTasks
if err := singleton.DB.Save(&m).Error; err != nil {
2024-10-31 17:07:04 -04:00
return nil, newGormError("%v", err)
2024-10-23 11:06:11 -04:00
}
var skipServers []uint64
for k := range m.SkipServers {
skipServers = append(skipServers, k)
}
if m.Cover == 0 {
2024-10-24 12:13:45 -04:00
err = singleton.DB.Unscoped().Delete(&model.ServiceHistory{}, "service_id = ? and server_id in (?)", m.ID, skipServers).Error
2024-10-23 11:06:11 -04:00
} else {
2024-10-24 12:13:45 -04:00
err = singleton.DB.Unscoped().Delete(&model.ServiceHistory{}, "service_id = ? and server_id not in (?)", m.ID, skipServers).Error
2024-10-23 11:06:11 -04:00
}
if err != nil {
return nil, err
}
if err := singleton.ServiceSentinelShared.OnServiceUpdate(m); err != nil {
return nil, err
}
singleton.ServiceSentinelShared.UpdateServiceList()
return nil, nil
2024-10-23 11:06:11 -04:00
}
2024-10-24 12:13:45 -04:00
// Batch delete service
// @Summary Batch delete service
2024-10-23 11:06:11 -04:00
// @Security BearerAuth
// @Schemes
2024-10-24 12:13:45 -04:00
// @Description Batch delete service
2024-10-23 11:06:11 -04:00
// @Tags auth required
// @Accept json
// @param request body []uint true "id list"
// @Produce json
// @Success 200 {object} model.CommonResponse[any]
2024-10-24 12:13:45 -04:00
// @Router /batch-delete/service [post]
func batchDeleteService(c *gin.Context) (any, error) {
2024-10-23 11:06:11 -04:00
var ids []uint64
if err := c.ShouldBindJSON(&ids); err != nil {
return nil, err
}
err := singleton.DB.Transaction(func(tx *gorm.DB) error {
2024-10-24 12:13:45 -04:00
if err := tx.Unscoped().Delete(&model.Service{}, "id in (?)", ids).Error; err != nil {
2024-10-23 11:06:11 -04:00
return err
}
2024-10-24 12:13:45 -04:00
return tx.Unscoped().Delete(&model.ServiceHistory{}, "service_id in (?)", ids).Error
2024-10-23 11:06:11 -04:00
})
if err != nil {
return nil, err
}
2024-10-24 12:13:45 -04:00
singleton.ServiceSentinelShared.OnServiceDelete(ids)
singleton.ServiceSentinelShared.UpdateServiceList()
2024-10-23 11:06:11 -04:00
return nil, nil
}