diff --git a/cmd/dashboard/controller/controller.go b/cmd/dashboard/controller/controller.go index 5132d32..9a943c4 100644 --- a/cmd/dashboard/controller/controller.go +++ b/cmd/dashboard/controller/controller.go @@ -92,6 +92,7 @@ func routers(r *gin.Engine) { auth.GET("/server", commonHandler(listServer)) auth.PATCH("/server/:id", commonHandler(updateServer)) auth.POST("/batch-delete/server", commonHandler(batchDeleteServer)) + auth.POST("/force-update/server", commonHandler(forceUpdateServer)) auth.GET("/notification", commonHandler(listNotification)) auth.POST("/notification", commonHandler(createNotification)) diff --git a/cmd/dashboard/controller/server.go b/cmd/dashboard/controller/server.go index 03f1712..71a105c 100644 --- a/cmd/dashboard/controller/server.go +++ b/cmd/dashboard/controller/server.go @@ -8,6 +8,7 @@ import ( "github.com/naiba/nezha/model" "github.com/naiba/nezha/pkg/utils" + pb "github.com/naiba/nezha/proto" "github.com/naiba/nezha/service/singleton" ) @@ -129,3 +130,42 @@ func batchDeleteServer(c *gin.Context) (any, error) { return nil, nil } + +// 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.ForceUpdateResponse] +// @Router /force-update/server [post] +func forceUpdateServer(c *gin.Context) (*model.ForceUpdateResponse, error) { + var forceUpdateServers []uint64 + if err := c.ShouldBindJSON(&forceUpdateServers); err != nil { + return nil, err + } + + forceUpdateResp := new(model.ForceUpdateResponse) + + for _, sid := range forceUpdateServers { + singleton.ServerLock.RLock() + server := singleton.ServerList[sid] + singleton.ServerLock.RUnlock() + if server != nil && server.TaskStream != nil { + 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 +} diff --git a/model/server_api.go b/model/server_api.go index ddd5abe..867bd7c 100644 --- a/model/server_api.go +++ b/model/server_api.go @@ -27,3 +27,9 @@ type ServerForm struct { EnableDDNS bool `json:"enable_ddns,omitempty" validate:"optional"` // 启用DDNS DDNSProfiles []uint64 `gorm:"-" json:"ddns_profiles,omitempty"` // DDNS配置 } + +type ForceUpdateResponse struct { + Success []uint64 `json:"success,omitempty" validate:"optional"` + Failure []uint64 `json:"failure,omitempty" validate:"optional"` + Offline []uint64 `json:"offline,omitempty" validate:"optional"` +} diff --git a/service/singleton/crontask.go b/service/singleton/crontask.go index 5cda99d..7dc790e 100644 --- a/service/singleton/crontask.go +++ b/service/singleton/crontask.go @@ -1,9 +1,9 @@ package singleton import ( - "bytes" "fmt" "slices" + "strings" "sync" "github.com/jinzhu/copier" @@ -33,7 +33,7 @@ func loadCronTasks() { DB.Find(&CronList) var err error var notificationGroupList []uint64 - notificationMsgMap := make(map[uint64]*bytes.Buffer) + notificationMsgMap := make(map[uint64]*strings.Builder) for _, cron := range CronList { // 触发任务类型无需注册 if cron.TaskType == model.CronTypeTriggerTask { @@ -48,7 +48,7 @@ func loadCronTasks() { // 当前通知组首次出现 将其加入通知组列表并初始化通知组消息缓存 if _, ok := notificationMsgMap[cron.NotificationGroupID]; !ok { notificationGroupList = append(notificationGroupList, cron.NotificationGroupID) - notificationMsgMap[cron.NotificationGroupID] = bytes.NewBufferString("") + notificationMsgMap[cron.NotificationGroupID] = new(strings.Builder) notificationMsgMap[cron.NotificationGroupID].WriteString(Localizer.T("Tasks failed to register: [")) } notificationMsgMap[cron.NotificationGroupID].WriteString(fmt.Sprintf("%d,", cron.ID))