feat: 云账号编辑功能 + 通知渠道种子数据 + 修复mock.go编译错误
- 新增 PUT /cloud-accounts/:id 后端端点(UpdateCloudAccount handler + 路由注册) - 前端 CloudAccountManager 添加编辑弹窗:复用创建表单,凭证留空保持不变 - 修复 scripts/mock.go 编译错误:补充缺失的 generateNotifyChannels 函数(5种通知渠道种子) - 包含前序未提交的告警通知渠道 CRUD 后端API + AlertManager 通知配置前端UI
This commit is contained in:
1 parent
01a9bebb0c
commit
af2e60acd9
9 files changed
+822
-51
No files matched your search
@@ -340,6 +340,200 @@ func (h *AlertHandler) AcknowledgeAlert(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// ListNotifyChannels 获取通知渠道列表
|
||||
// @Summary 获取通知渠道列表
|
||||
// @Description 获取所有通知渠道列表
|
||||
// @Tags 告警
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} models.Response{data=models.ListResponse}
|
||||
// @Router /alerts/notify-channels [get]
|
||||
func (h *AlertHandler) ListNotifyChannels(c *gin.Context) {
|
||||
var channels []models.NotifyChannel
|
||||
h.db.Order("created_at DESC").Find(&channels)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: models.ListResponse{
|
||||
List: channels,
|
||||
Total: int64(len(channels)),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// CreateNotifyChannel 创建通知渠道
|
||||
// @Summary 创建通知渠道
|
||||
// @Description 创建新通知渠道
|
||||
// @Tags 告警
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body models.CreateNotifyChannelRequest true "创建请求"
|
||||
// @Success 200 {object} models.Response{data=models.NotifyChannel}
|
||||
// @Router /alerts/notify-channels [post]
|
||||
func (h *AlertHandler) CreateNotifyChannel(c *gin.Context) {
|
||||
var req models.CreateNotifyChannelRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
channel := models.NotifyChannel{
|
||||
Name: req.Name,
|
||||
Type: req.Type,
|
||||
Config: req.Config,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
if err := h.db.Create(&channel).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "创建失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "创建成功",
|
||||
Data: channel,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateNotifyChannel 更新通知渠道
|
||||
// @Summary 更新通知渠道
|
||||
// @Description 更新指定通知渠道
|
||||
// @Tags 告警
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "渠道ID"
|
||||
// @Param request body models.UpdateNotifyChannelRequest true "更新请求"
|
||||
// @Success 200 {object} models.Response{data=models.NotifyChannel}
|
||||
// @Router /alerts/notify-channels/{id} [put]
|
||||
func (h *AlertHandler) UpdateNotifyChannel(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var req models.UpdateNotifyChannelRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var channel models.NotifyChannel
|
||||
if err := h.db.First(&channel, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "通知渠道不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
updates := map[string]interface{}{}
|
||||
if req.Name != "" {
|
||||
updates["name"] = req.Name
|
||||
}
|
||||
if req.Type != "" {
|
||||
updates["type"] = req.Type
|
||||
}
|
||||
if req.Config != "" {
|
||||
updates["config"] = req.Config
|
||||
}
|
||||
if req.Enabled != nil {
|
||||
updates["enabled"] = *req.Enabled
|
||||
}
|
||||
|
||||
if err := h.db.Model(&channel).Updates(updates).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "更新失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
h.db.First(&channel, id)
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "更新成功",
|
||||
Data: channel,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteNotifyChannel 删除通知渠道
|
||||
// @Summary 删除通知渠道
|
||||
// @Description 删除指定通知渠道
|
||||
// @Tags 告警
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "渠道ID"
|
||||
// @Success 200 {object} models.Response
|
||||
// @Router /alerts/notify-channels/{id} [delete]
|
||||
func (h *AlertHandler) DeleteNotifyChannel(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
if err := h.db.Delete(&models.NotifyChannel{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "删除失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "删除成功",
|
||||
})
|
||||
}
|
||||
|
||||
// TestNotifyChannel 测试通知渠道
|
||||
// @Summary 测试通知渠道连通性
|
||||
// @Description 模拟测试通知渠道(返回测试状态)
|
||||
// @Tags 告警
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "渠道ID"
|
||||
// @Success 200 {object} models.Response
|
||||
// @Router /alerts/notify-channels/{id}/test [post]
|
||||
func (h *AlertHandler) TestNotifyChannel(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var channel models.NotifyChannel
|
||||
if err := h.db.First(&channel, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "通知渠道不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 模拟测试:校验配置非空即视为成功
|
||||
status := "success"
|
||||
message := "测试通知已发送"
|
||||
if channel.Config == "" {
|
||||
status = "warning"
|
||||
message = "渠道未配置参数,请先完善配置"
|
||||
}
|
||||
|
||||
h.db.Model(&channel).Update("test_status", status)
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: message,
|
||||
Data: gin.H{
|
||||
"status": status,
|
||||
"channel": channel.Name,
|
||||
"type": channel.Type,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// toJSON serializes a value to JSON string. Returns empty string on error.
|
||||
func toJSON(v interface{}) string {
|
||||
b, err := json.Marshal(v)
|
||||
|
||||
@@ -469,6 +469,70 @@ func (h *CloudAccountHandler) CreateCloudAccount(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateCloudAccount 更新云账号
|
||||
// @Summary 更新云账号
|
||||
// @Description 更新指定云账号信息
|
||||
// @Tags 云账号
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path int true "云账号ID"
|
||||
// @Param request body createCloudAccountRequest true "更新请求"
|
||||
// @Success 200 {object} models.Response{data=models.CloudAccount}
|
||||
// @Router /cloud-accounts/{id} [put]
|
||||
func (h *CloudAccountHandler) UpdateCloudAccount(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
var req createCloudAccountRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
Message: "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var account models.CloudAccount
|
||||
if err := h.db.First(&account, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, models.Response{
|
||||
Code: 404,
|
||||
Message: "云账号不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 只在提供非空值时更新凭证字段
|
||||
if strings.TrimSpace(req.Name) != "" {
|
||||
account.Name = strings.TrimSpace(req.Name)
|
||||
}
|
||||
if strings.TrimSpace(req.Provider) != "" {
|
||||
account.Provider = strings.TrimSpace(req.Provider)
|
||||
}
|
||||
if strings.TrimSpace(req.AccessKey) != "" {
|
||||
account.AccessKey = req.AccessKey
|
||||
}
|
||||
if strings.TrimSpace(req.SecretKey) != "" {
|
||||
account.SecretKey = req.SecretKey
|
||||
}
|
||||
if strings.TrimSpace(req.Status) != "" {
|
||||
account.Status = strings.TrimSpace(req.Status)
|
||||
}
|
||||
account.MonthlyCost = req.MonthlyCost
|
||||
|
||||
if err := h.db.Save(&account).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{
|
||||
Code: 500,
|
||||
Message: "更新失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "更新成功",
|
||||
Data: account,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteCloudAccount 删除云账号
|
||||
// @Summary 删除云账号
|
||||
// @Description 删除指定云账号
|
||||
|
||||
@@ -93,6 +93,7 @@ func Register(r *gin.Engine, db *gorm.DB, log *zap.Logger, cfg *config.Config) {
|
||||
{
|
||||
cloudAccounts.GET("", cloudAccountHandler.ListCloudAccounts)
|
||||
cloudAccounts.POST("", cloudAccountHandler.CreateCloudAccount)
|
||||
cloudAccounts.PUT("/:id", cloudAccountHandler.UpdateCloudAccount)
|
||||
cloudAccounts.DELETE("/:id", cloudAccountHandler.DeleteCloudAccount)
|
||||
}
|
||||
|
||||
@@ -132,6 +133,11 @@ func Register(r *gin.Engine, db *gorm.DB, log *zap.Logger, cfg *config.Config) {
|
||||
alerts.DELETE("/rules/:id", alertHandler.DeleteRule)
|
||||
alerts.POST("/rules/:id/toggle", alertHandler.ToggleRule)
|
||||
alerts.POST("/:id/ack", alertHandler.AcknowledgeAlert)
|
||||
alerts.GET("/notify-channels", alertHandler.ListNotifyChannels)
|
||||
alerts.POST("/notify-channels", alertHandler.CreateNotifyChannel)
|
||||
alerts.PUT("/notify-channels/:id", alertHandler.UpdateNotifyChannel)
|
||||
alerts.DELETE("/notify-channels/:id", alertHandler.DeleteNotifyChannel)
|
||||
alerts.POST("/notify-channels/:id/test", alertHandler.TestNotifyChannel)
|
||||
}
|
||||
|
||||
// 流水线路由
|
||||
|
||||
Reference in new issue
Block a user