| @@ -296,6 +296,22 @@ CREATE TABLE `target` ( | |||
| -- KEY (`group_id`, `type`, `name`) | |||
| -- ) ENGINE=InnoDB DEFAULT CHARSET = utf8mb4; | |||
| CREATE TABLE `alert_aggr_view` ( | |||
| `id` bigint unsigned not null auto_increment, | |||
| `name` varchar(191) not null default '', | |||
| `rule` varchar(2048) not null default '', | |||
| `cate` tinyint(1) not null comment '0: preset 1: custom', | |||
| `user_id` bigint not null default 0, | |||
| `create_at` bigint not null default 0, | |||
| `create_by` varchar(64) not null default '', | |||
| `update_at` bigint not null default 0, | |||
| PRIMARY KEY (`id`), | |||
| KEY (`user_id`) | |||
| ) ENGINE=InnoDB DEFAULT CHARSET = utf8mb4; | |||
| insert into alert_aggr_view(name, rule, cate) values('GroupBy BusiGroup, Severity', 'field:group_name field:severity', 0); | |||
| insert into alert_aggr_view(name, rule, cate) values('GroupBy Metric', 'tagkey:__name__', 0); | |||
| CREATE TABLE `alert_cur_event` ( | |||
| `id` bigint unsigned not null comment 'use alert_his_event.id', | |||
| `cluster` varchar(128) not null, | |||
| @@ -0,0 +1,96 @@ | |||
| package models | |||
| import ( | |||
| "errors" | |||
| "sort" | |||
| "strings" | |||
| "time" | |||
| ) | |||
| // AlertAggrView 在告警聚合视图查看的时候,要存储一些聚合规则 | |||
| type AlertAggrView struct { | |||
| Id int64 `json:"id" gorm:"primaryKey"` | |||
| Name string `json:"name"` | |||
| Rule string `json:"rule"` | |||
| Cate int `json:"cate"` | |||
| UserId int64 `json:"user_id"` | |||
| CreateAt int64 `json:"create_at"` | |||
| CreateBy string `json:"create_by"` | |||
| UpdateAt int64 `json:"update_at"` | |||
| } | |||
| func (v *AlertAggrView) TableName() string { | |||
| return "alert_aggr_view" | |||
| } | |||
| func (v *AlertAggrView) Verify() error { | |||
| v.Name = strings.TrimSpace(v.Name) | |||
| if v.Name == "" { | |||
| return errors.New("name is blank") | |||
| } | |||
| v.Rule = strings.TrimSpace(v.Rule) | |||
| if v.Rule == "" { | |||
| return errors.New("rule is blank") | |||
| } | |||
| return nil | |||
| } | |||
| func (v *AlertAggrView) Add() error { | |||
| if err := v.Verify(); err != nil { | |||
| return err | |||
| } | |||
| now := time.Now().Unix() | |||
| v.CreateAt = now | |||
| v.UpdateAt = now | |||
| v.Cate = 1 | |||
| return Insert(v) | |||
| } | |||
| func (v *AlertAggrView) Update(name, rule string) error { | |||
| if err := v.Verify(); err != nil { | |||
| return err | |||
| } | |||
| v.UpdateAt = time.Now().Unix() | |||
| v.Name = name | |||
| v.Rule = rule | |||
| return DB().Model(v).Select("name", "rule", "update_at").Updates(v).Error | |||
| } | |||
| // AlertAggrViewDel: userid for safe delete | |||
| func AlertAggrViewDel(ids []int64, userId interface{}) error { | |||
| if len(ids) == 0 { | |||
| return nil | |||
| } | |||
| return DB().Where("id in ? and user_id = ?", ids, userId).Delete(new(AlertAggrView)).Error | |||
| } | |||
| func AlertAggrViewGets(userId interface{}) ([]AlertAggrView, error) { | |||
| var lst []AlertAggrView | |||
| err := DB().Where("user_id = ? or cate = 0", userId).Find(&lst).Error | |||
| if err == nil && len(lst) > 0 { | |||
| sort.Slice(lst, func(i, j int) bool { | |||
| return lst[i].Name < lst[j].Name | |||
| }) | |||
| } | |||
| return lst, err | |||
| } | |||
| func AlertAggrViewGet(where string, args ...interface{}) (*AlertAggrView, error) { | |||
| var lst []*AlertAggrView | |||
| err := DB().Where(where, args...).Find(&lst).Error | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if len(lst) == 0 { | |||
| return nil, nil | |||
| } | |||
| return lst[0], nil | |||
| } | |||
| @@ -217,6 +217,11 @@ func configRoute(r *gin.Engine, version string) { | |||
| pages.GET("/alert-his-event/:eid", jwtAuth(), alertHisEventGet) | |||
| } | |||
| pages.GET("/alert-aggr-views", jwtAuth(), alertAggrViewGets) | |||
| pages.DELETE("/alert-aggr-views", jwtAuth(), alertAggrViewDel) | |||
| pages.POST("/alert-aggr-views", jwtAuth(), alertAggrViewAdd) | |||
| pages.PUT("/alert-aggr-views", jwtAuth(), alertAggrViewPut) | |||
| pages.GET("/busi-group/:id/task-tpls", jwtAuth(), user(), perm("/job-tpls"), bgro(), taskTplGets) | |||
| pages.POST("/busi-group/:id/task-tpls", jwtAuth(), user(), perm("/job-tpls/add"), bgrw(), taskTplAdd) | |||
| pages.DELETE("/busi-group/:id/task-tpl/:tid", jwtAuth(), user(), perm("/job-tpls/del"), bgrw(), taskTplDel) | |||
| @@ -0,0 +1,55 @@ | |||
| package router | |||
| import ( | |||
| "net/http" | |||
| "github.com/didi/nightingale/v5/src/models" | |||
| "github.com/gin-gonic/gin" | |||
| "github.com/toolkits/pkg/ginx" | |||
| ) | |||
| func alertAggrViewGets(c *gin.Context) { | |||
| lst, err := models.AlertAggrViewGets(c.MustGet("userid")) | |||
| ginx.NewRender(c).Data(lst, err) | |||
| } | |||
| // name and rule is necessary | |||
| func alertAggrViewAdd(c *gin.Context) { | |||
| var f models.AlertAggrView | |||
| ginx.BindJSON(c, &f) | |||
| f.Id = 0 | |||
| f.CreateBy = c.MustGet("username").(string) | |||
| f.UserId = c.MustGet("userid").(int64) | |||
| ginx.NewRender(c).Message(f.Add()) | |||
| } | |||
| func alertAggrViewDel(c *gin.Context) { | |||
| var f idsForm | |||
| ginx.BindJSON(c, &f) | |||
| ginx.NewRender(c).Message(models.AlertAggrViewDel(f.Ids, c.MustGet("userid"))) | |||
| } | |||
| // id / name / rule is necessary | |||
| func alertAggrViewPut(c *gin.Context) { | |||
| var f models.AlertAggrView | |||
| ginx.BindJSON(c, &f) | |||
| view, err := models.AlertAggrViewGet("id = ?", f.Id) | |||
| ginx.Dangerous(err) | |||
| if view == nil { | |||
| ginx.NewRender(c).Message("no such item(id: %d)", f.Id) | |||
| return | |||
| } | |||
| userid := c.MustGet("userid").(int64) | |||
| if view.UserId != userid { | |||
| ginx.NewRender(c, http.StatusForbidden).Message("forbidden") | |||
| return | |||
| } | |||
| ginx.NewRender(c).Message(view.Update(f.Name, f.Rule)) | |||
| } | |||
| @@ -1,79 +1,79 @@ | |||
| package router | |||
| import ( | |||
| "net/http" | |||
| "github.com/gin-gonic/gin" | |||
| "github.com/toolkits/pkg/ginx" | |||
| "github.com/didi/nightingale/v5/src/models" | |||
| ) | |||
| func collectRuleGets(c *gin.Context) { | |||
| busiGroupId := ginx.UrlParamInt64(c, "id") | |||
| crs, err := models.CollectRuleGets(busiGroupId, ginx.QueryStr(c, "type", "")) | |||
| ginx.NewRender(c).Data(crs, err) | |||
| } | |||
| func collectRuleAdd(c *gin.Context) { | |||
| var lst []models.CollectRule | |||
| ginx.BindJSON(c, &lst) | |||
| count := len(lst) | |||
| if count == 0 { | |||
| ginx.Bomb(http.StatusBadRequest, "input json is empty") | |||
| } | |||
| username := c.MustGet("username").(string) | |||
| bgid := ginx.UrlParamInt64(c, "id") | |||
| // collect rule name -> error string | |||
| reterr := make(map[string]string) | |||
| for i := 0; i < count; i++ { | |||
| lst[i].Id = 0 | |||
| lst[i].GroupId = bgid | |||
| lst[i].CreateBy = username | |||
| lst[i].UpdateBy = username | |||
| lst[i].FE2DB() | |||
| if err := lst[i].Add(); err != nil { | |||
| reterr[lst[i].Name] = err.Error() | |||
| } else { | |||
| reterr[lst[i].Name] = "" | |||
| } | |||
| } | |||
| ginx.NewRender(c).Data(reterr, nil) | |||
| } | |||
| func collectRuleDel(c *gin.Context) { | |||
| var f idsForm | |||
| ginx.BindJSON(c, &f) | |||
| f.Verify() | |||
| // param(busiGroupId) for protect | |||
| ginx.NewRender(c).Message(models.CollectRuleDels(f.Ids, ginx.UrlParamInt64(c, "id"))) | |||
| } | |||
| func collectRuleGet(c *gin.Context) { | |||
| crid := ginx.UrlParamInt64(c, "crid") | |||
| cr, err := models.CollectRuleGetById(crid) | |||
| ginx.NewRender(c).Data(cr, err) | |||
| } | |||
| func collectRulePut(c *gin.Context) { | |||
| var f models.CollectRule | |||
| ginx.BindJSON(c, &f) | |||
| crid := ginx.UrlParamInt64(c, "crid") | |||
| cr, err := models.CollectRuleGetById(crid) | |||
| ginx.Dangerous(err) | |||
| if cr == nil { | |||
| ginx.NewRender(c, http.StatusNotFound).Message("No such CollectRule") | |||
| return | |||
| } | |||
| f.UpdateBy = c.MustGet("username").(string) | |||
| ginx.NewRender(c).Message(cr.Update(f)) | |||
| } | |||
| // import ( | |||
| // "net/http" | |||
| // "github.com/gin-gonic/gin" | |||
| // "github.com/toolkits/pkg/ginx" | |||
| // "github.com/didi/nightingale/v5/src/models" | |||
| // ) | |||
| // func collectRuleGets(c *gin.Context) { | |||
| // busiGroupId := ginx.UrlParamInt64(c, "id") | |||
| // crs, err := models.CollectRuleGets(busiGroupId, ginx.QueryStr(c, "type", "")) | |||
| // ginx.NewRender(c).Data(crs, err) | |||
| // } | |||
| // func collectRuleAdd(c *gin.Context) { | |||
| // var lst []models.CollectRule | |||
| // ginx.BindJSON(c, &lst) | |||
| // count := len(lst) | |||
| // if count == 0 { | |||
| // ginx.Bomb(http.StatusBadRequest, "input json is empty") | |||
| // } | |||
| // username := c.MustGet("username").(string) | |||
| // bgid := ginx.UrlParamInt64(c, "id") | |||
| // // collect rule name -> error string | |||
| // reterr := make(map[string]string) | |||
| // for i := 0; i < count; i++ { | |||
| // lst[i].Id = 0 | |||
| // lst[i].GroupId = bgid | |||
| // lst[i].CreateBy = username | |||
| // lst[i].UpdateBy = username | |||
| // lst[i].FE2DB() | |||
| // if err := lst[i].Add(); err != nil { | |||
| // reterr[lst[i].Name] = err.Error() | |||
| // } else { | |||
| // reterr[lst[i].Name] = "" | |||
| // } | |||
| // } | |||
| // ginx.NewRender(c).Data(reterr, nil) | |||
| // } | |||
| // func collectRuleDel(c *gin.Context) { | |||
| // var f idsForm | |||
| // ginx.BindJSON(c, &f) | |||
| // f.Verify() | |||
| // // param(busiGroupId) for protect | |||
| // ginx.NewRender(c).Message(models.CollectRuleDels(f.Ids, ginx.UrlParamInt64(c, "id"))) | |||
| // } | |||
| // func collectRuleGet(c *gin.Context) { | |||
| // crid := ginx.UrlParamInt64(c, "crid") | |||
| // cr, err := models.CollectRuleGetById(crid) | |||
| // ginx.NewRender(c).Data(cr, err) | |||
| // } | |||
| // func collectRulePut(c *gin.Context) { | |||
| // var f models.CollectRule | |||
| // ginx.BindJSON(c, &f) | |||
| // crid := ginx.UrlParamInt64(c, "crid") | |||
| // cr, err := models.CollectRuleGetById(crid) | |||
| // ginx.Dangerous(err) | |||
| // if cr == nil { | |||
| // ginx.NewRender(c, http.StatusNotFound).Message("No such CollectRule") | |||
| // return | |||
| // } | |||
| // f.UpdateBy = c.MustGet("username").(string) | |||
| // ginx.NewRender(c).Message(cr.Update(f)) | |||
| // } | |||
| @@ -223,10 +223,6 @@ func dashboardImport(c *gin.Context) { | |||
| ginx.NewRender(c).Data(ret, nil) | |||
| } | |||
| type idForm struct { | |||
| Id int64 `json:"id" binding:"required"` | |||
| } | |||
| func dashboardClone(c *gin.Context) { | |||
| dash := Dashboard(ginx.UrlParamInt64(c, "did")) | |||
| user := c.MustGet("user").(*models.User) | |||
| @@ -45,7 +45,7 @@ func taskTplGet(c *gin.Context) { | |||
| ginx.NewRender(c).Data(gin.H{ | |||
| "tpl": tpl, | |||
| "hosts": hosts, | |||
| }, nil) | |||
| }, err) | |||
| } | |||
| type taskTplForm struct { | |||
| @@ -97,7 +97,8 @@ func taskTplPut(c *gin.Context) { | |||
| ginx.Dangerous(err) | |||
| if tpl == nil { | |||
| ginx.Dangerous("no such task template") | |||
| ginx.NewRender(c).Message("no such task template") | |||
| return | |||
| } | |||
| user := c.MustGet("user").(*models.User) | |||