nezha/cmd/dashboard/controller/server.go

340 lines
8.6 KiB
Go
Raw Normal View History

2024-10-20 11:23:04 -04:00
package controller
import (
"strconv"
"sync"
"time"
2024-10-20 11:23:04 -04:00
"github.com/gin-gonic/gin"
2024-10-23 22:21:59 -04:00
"github.com/jinzhu/copier"
"gorm.io/gorm"
2024-10-21 04:22:30 -04:00
2024-11-28 06:38:54 -05:00
"github.com/nezhahq/nezha/model"
"github.com/nezhahq/nezha/pkg/utils"
pb "github.com/nezhahq/nezha/proto"
"github.com/nezhahq/nezha/service/singleton"
2024-10-20 11:23:04 -04:00
)
2024-10-23 05:34:15 -04:00
// List server
// @Summary List server
// @Security BearerAuth
// @Schemes
// @Description List server
// @Tags auth required
// @Param id query uint false "Resource ID"
2024-10-23 05:34:15 -04:00
// @Produce json
2024-10-24 02:11:06 -04:00
// @Success 200 {object} model.CommonResponse[[]model.Server]
2024-10-23 05:34:15 -04:00
// @Router /server [get]
2024-10-23 11:06:11 -04:00
func listServer(c *gin.Context) ([]*model.Server, error) {
singleton.SortedServerLock.RLock()
defer singleton.SortedServerLock.RUnlock()
2024-10-23 22:21:59 -04:00
var ssl []*model.Server
if err := copier.Copy(&ssl, &singleton.SortedServerList); err != nil {
return nil, err
}
return ssl, nil
2024-10-23 05:34:15 -04:00
}
2024-10-20 11:23:04 -04:00
// Edit server
// @Summary Edit server
// @Security BearerAuth
// @Schemes
// @Description Edit server
// @Tags auth required
2024-10-21 11:00:51 -04:00
// @Accept json
// @Param id path uint true "Server ID"
// @Param body body model.ServerForm true "ServerForm"
2024-10-20 11:23:04 -04:00
// @Produce json
// @Success 200 {object} model.CommonResponse[any]
// @Router /server/{id} [patch]
2024-10-23 05:56:51 -04:00
func updateServer(c *gin.Context) (any, error) {
2024-10-20 11:23:04 -04:00
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
2024-10-23 05:56:51 -04:00
return nil, err
2024-10-20 11:23:04 -04:00
}
2024-10-21 11:00:51 -04:00
var sf model.ServerForm
2024-10-20 11:23:04 -04:00
if err := c.ShouldBindJSON(&sf); err != nil {
2024-10-23 05:56:51 -04:00
return nil, err
2024-10-20 11:23:04 -04:00
}
2024-10-21 11:00:51 -04:00
singleton.DDNSCacheLock.RLock()
for _, pid := range sf.DDNSProfiles {
if p, ok := singleton.DDNSCache[pid]; ok {
if !p.HasPermission(c) {
singleton.DDNSCacheLock.RUnlock()
return nil, singleton.Localizer.ErrorT("permission denied")
}
}
}
singleton.DDNSCacheLock.RUnlock()
2024-10-21 11:00:51 -04:00
var s model.Server
if err := singleton.DB.First(&s, id).Error; err != nil {
2024-10-31 17:07:04 -04:00
return nil, singleton.Localizer.ErrorT("server id %d does not exist", id)
2024-10-21 11:00:51 -04:00
}
if !s.HasPermission(c) {
return nil, singleton.Localizer.ErrorT("permission denied")
}
2024-10-20 11:23:04 -04:00
s.Name = sf.Name
s.DisplayIndex = sf.DisplayIndex
s.Note = sf.Note
s.PublicNote = sf.PublicNote
s.HideForGuest = sf.HideForGuest
s.EnableDDNS = sf.EnableDDNS
s.DDNSProfiles = sf.DDNSProfiles
s.OverrideDDNSDomains = sf.OverrideDDNSDomains
2024-10-20 11:23:04 -04:00
ddnsProfilesRaw, err := utils.Json.Marshal(s.DDNSProfiles)
if err != nil {
2024-10-23 05:56:51 -04:00
return nil, err
2024-10-20 11:23:04 -04:00
}
s.DDNSProfilesRaw = string(ddnsProfilesRaw)
overrideDomainsRaw, err := utils.Json.Marshal(sf.OverrideDDNSDomains)
if err != nil {
return nil, err
}
s.OverrideDDNSDomainsRaw = string(overrideDomainsRaw)
2024-10-20 11:23:04 -04:00
if err := singleton.DB.Save(&s).Error; err != nil {
2024-10-23 05:56:51 -04:00
return nil, newGormError("%v", err)
2024-10-20 11:23:04 -04:00
}
singleton.ServerLock.Lock()
s.CopyFromRunningServer(singleton.ServerList[s.ID])
singleton.ServerList[s.ID] = &s
singleton.ServerLock.Unlock()
singleton.ReSortServer()
2024-10-23 05:56:51 -04:00
return nil, nil
2024-10-20 11:23:04 -04:00
}
// Batch delete server
// @Summary Batch delete server
// @Security BearerAuth
// @Schemes
// @Description Batch delete server
// @Tags auth required
// @Accept json
// @param request body []uint64 true "id list"
// @Produce json
// @Success 200 {object} model.CommonResponse[any]
// @Router /batch-delete/server [post]
2024-10-23 05:56:51 -04:00
func batchDeleteServer(c *gin.Context) (any, error) {
2024-10-20 11:23:04 -04:00
var servers []uint64
if err := c.ShouldBindJSON(&servers); err != nil {
2024-10-23 05:56:51 -04:00
return nil, err
2024-10-20 11:23:04 -04:00
}
singleton.ServerLock.RLock()
for _, sid := range servers {
if s, ok := singleton.ServerList[sid]; ok {
if !s.HasPermission(c) {
singleton.ServerLock.RUnlock()
return nil, singleton.Localizer.ErrorT("permission denied")
}
}
}
singleton.ServerLock.RUnlock()
err := singleton.DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Unscoped().Delete(&model.Server{}, "id in (?)", servers).Error; err != nil {
return err
}
if err := tx.Unscoped().Delete(&model.ServerGroupServer{}, "server_id in (?)", servers).Error; err != nil {
return err
}
return nil
})
if err != nil {
2024-10-23 05:56:51 -04:00
return nil, newGormError("%v", err)
2024-10-20 11:23:04 -04:00
}
singleton.AlertsLock.Lock()
for _, sid := range servers {
for _, alert := range singleton.Alerts {
if singleton.AlertsCycleTransferStatsStore[alert.ID] != nil {
delete(singleton.AlertsCycleTransferStatsStore[alert.ID].ServerName, sid)
delete(singleton.AlertsCycleTransferStatsStore[alert.ID].Transfer, sid)
delete(singleton.AlertsCycleTransferStatsStore[alert.ID].NextUpdate, sid)
2024-10-20 11:23:04 -04:00
}
}
}
singleton.DB.Unscoped().Delete(&model.Transfer{}, "server_id in (?)", servers)
singleton.AlertsLock.Unlock()
2024-10-20 11:23:04 -04:00
singleton.OnServerDelete(servers)
2024-10-20 11:23:04 -04:00
singleton.ReSortServer()
2024-10-23 05:56:51 -04:00
return nil, nil
2024-10-20 11:23:04 -04:00
}
2024-11-20 08:36:21 -05:00
// Force update Agent
// @Summary Force update Agent
// @Security BearerAuth
// @Schemes
// @Description Force update Agent
// @Tags auth required
// @Accept json
// @param request body []uint64 true "id list"
// @Produce json
// @Success 200 {object} model.CommonResponse[model.ServerTaskResponse]
2024-11-20 08:36:21 -05:00
// @Router /force-update/server [post]
func forceUpdateServer(c *gin.Context) (*model.ServerTaskResponse, error) {
2024-11-20 08:36:21 -05:00
var forceUpdateServers []uint64
if err := c.ShouldBindJSON(&forceUpdateServers); err != nil {
return nil, err
}
forceUpdateResp := new(model.ServerTaskResponse)
2024-11-20 08:36:21 -05:00
for _, sid := range forceUpdateServers {
singleton.ServerLock.RLock()
server := singleton.ServerList[sid]
singleton.ServerLock.RUnlock()
if server != nil && server.TaskStream != nil {
if !server.HasPermission(c) {
return nil, singleton.Localizer.ErrorT("permission denied")
}
2024-11-20 08:36:21 -05:00
if err := server.TaskStream.Send(&pb.Task{
Type: model.TaskTypeUpgrade,
}); err != nil {
forceUpdateResp.Failure = append(forceUpdateResp.Failure, sid)
} else {
forceUpdateResp.Success = append(forceUpdateResp.Success, sid)
}
} else {
forceUpdateResp.Offline = append(forceUpdateResp.Offline, sid)
}
}
return forceUpdateResp, nil
}
// Get server config
// @Summary Get server config
// @Security BearerAuth
// @Schemes
// @Description Get server config
// @Tags auth required
// @Produce json
// @Success 200 {object} model.CommonResponse[string]
// @Router /server/config/{id} [get]
func getServerConfig(c *gin.Context) (string, error) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
return "", err
}
singleton.ServerLock.RLock()
s, ok := singleton.ServerList[id]
if !ok || s.TaskStream == nil {
singleton.ServerLock.RUnlock()
return "", nil
}
singleton.ServerLock.RUnlock()
if !s.HasPermission(c) {
return "", singleton.Localizer.ErrorT("permission denied")
}
if err := s.TaskStream.Send(&pb.Task{
Type: model.TaskTypeReportConfig,
}); err != nil {
return "", err
}
timeout := time.NewTimer(time.Second * 10)
select {
case <-timeout.C:
return "", singleton.Localizer.ErrorT("operation timeout")
case data := <-s.ConfigCache:
timeout.Stop()
switch data := data.(type) {
case string:
return data, nil
case error:
return "", singleton.Localizer.ErrorT("get server config failed: %v", data)
}
}
return "", singleton.Localizer.ErrorT("get server config failed")
}
// Set server config
// @Summary Set server config
// @Security BearerAuth
// @Schemes
// @Description Set server config
// @Tags auth required
// @Accept json
// @Param body body model.ServerConfigForm true "ServerConfigForm"
// @Produce json
// @Success 200 {object} model.CommonResponse[model.ServerTaskResponse]
// @Router /server/config [post]
func setServerConfig(c *gin.Context) (*model.ServerTaskResponse, error) {
var configForm model.ServerConfigForm
if err := c.ShouldBindJSON(&configForm); err != nil {
return nil, err
}
var resp model.ServerTaskResponse
singleton.ServerLock.RLock()
servers := make([]*model.Server, 0, len(configForm.Servers))
for _, sid := range configForm.Servers {
if s, ok := singleton.ServerList[sid]; ok {
if !s.HasPermission(c) {
singleton.ServerLock.RUnlock()
return nil, singleton.Localizer.ErrorT("permission denied")
}
if s.TaskStream == nil {
resp.Offline = append(resp.Offline, s.ID)
continue
}
servers = append(servers, s)
}
}
singleton.ServerLock.RUnlock()
var wg sync.WaitGroup
var respMu sync.Mutex
for i := 0; i < len(servers); i += 10 {
end := i + 10
if end > len(servers) {
end = len(servers)
}
group := servers[i:end]
wg.Add(1)
go func(srvGroup []*model.Server) {
defer wg.Done()
for _, s := range srvGroup {
// Create and send the task.
task := &pb.Task{
Type: model.TaskTypeApplyConfig,
Data: configForm.Config,
}
if err := s.TaskStream.Send(task); err != nil {
respMu.Lock()
resp.Failure = append(resp.Failure, s.ID)
respMu.Unlock()
continue
}
respMu.Lock()
resp.Success = append(resp.Success, s.ID)
respMu.Unlock()
}
}(group)
}
wg.Wait()
return &resp, nil
}