Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(builtin-metrics): add CRUD operations for entity classes #1900

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions center/cconf/ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,5 +184,13 @@ ops:
- "/help/notification-settings"
- "/help/migrate"
- "/site-settings"

- name: builtin-metrics
cname: 内置指标
ops:
- "/builtin-metrics"
- "/builtin-metrics/add"
- "/builtin-metrics/put"
- "/builtin-metrics/del"
`
)
7 changes: 7 additions & 0 deletions center/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,13 @@ func (rt *Router) Config(r *gin.Engine) {
pages.POST("/metric-views", rt.auth(), rt.user(), rt.metricViewAdd)
pages.PUT("/metric-views", rt.auth(), rt.user(), rt.metricViewPut)

pages.POST("/builtin-metrics", rt.auth(), rt.user(), rt.perm("/builtin-metrics/add"), rt.builtinMetricsAdd)
pages.GET("/builtin-metrics", rt.auth(), rt.user(), rt.perm("/builtin-metrics"), rt.builtinMetricsGets)
pages.PUT("/builtin-metrics", rt.auth(), rt.user(), rt.perm("/builtin-metrics/put"), rt.builtinMetricsPut)
pages.DELETE("/builtin-metrics", rt.auth(), rt.user(), rt.perm("/builtin-metrics/del"), rt.builtinMetricsDel)
pages.GET("/builtin-metrics/types", rt.auth(), rt.user(), rt.perm("/builtin-metrics"), rt.builtinMetricsTypes)
pages.GET("/builtin-metrics/collectors", rt.auth(), rt.user(), rt.perm("/builtin-metrics"), rt.builtinMetricsCollectors)

pages.GET("/user-groups", rt.auth(), rt.user(), rt.userGroupGets)
pages.POST("/user-groups", rt.auth(), rt.user(), rt.perm("/user-groups/add"), rt.userGroupAdd)
pages.GET("/user-group/:id", rt.auth(), rt.user(), rt.userGroupGet)
Expand Down
81 changes: 81 additions & 0 deletions center/router/router_buildin_metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package router

import (
"net/http"

"github.com/ccfos/nightingale/v6/models"

"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)

// single or import
func (rt *Router) builtinMetricsAdd(c *gin.Context) {
var lst []models.BuiltinMetric
ginx.BindJSON(c, &lst)
username := Username(c)
count := len(lst)
if count == 0 {
ginx.Bomb(http.StatusBadRequest, "input json is empty")
}
reterr := make(map[string]string)
for i := 0; i < count; i++ {
if err := lst[i].Add(rt.Ctx, username); err != nil {
reterr[lst[i].Name] = err.Error()
}
}
ginx.NewRender(c).Data(reterr, nil)
}

func (rt *Router) builtinMetricsGets(c *gin.Context) {
collector := ginx.QueryStr(c, "collector", "")
typ := ginx.QueryStr(c, "typ", "")
query := ginx.QueryStr(c, "query", "")
limit := ginx.QueryInt(c, "limit", 20)

bm, err := models.BuiltinMetricGets(rt.Ctx, collector, typ, query, limit, ginx.Offset(c, limit))
ginx.Dangerous(err)

total, err := models.BuiltinMetricCount(rt.Ctx, collector, typ, query)
ginx.Dangerous(err)
ginx.NewRender(c).Data(gin.H{
"list": bm,
"total": total,
}, nil)
}

func (rt *Router) builtinMetricsPut(c *gin.Context) {
var req models.BuiltinMetric
ginx.BindJSON(c, &req)

bm, err := models.BuiltinMetricGet(rt.Ctx, "id = ?", req.ID)
ginx.Dangerous(err)
if bm == nil {
ginx.NewRender(c, http.StatusNotFound).Message("No such builtin metric")
return
}
username := Username(c)

req.UpdatedBy = username
ginx.NewRender(c).Message(bm.Update(rt.Ctx, req))
}

func (rt *Router) builtinMetricsDel(c *gin.Context) {
var req idsForm
ginx.BindJSON(c, &req)
req.Verify()

ginx.NewRender(c).Message(models.BuiltinMetricDels(rt.Ctx, req.Ids))
}

func (rt *Router) builtinMetricsTypes(c *gin.Context) {
query := ginx.QueryStr(c, "query", "")
motongxue marked this conversation as resolved.
Show resolved Hide resolved

ginx.NewRender(c).Data(models.BuiltinMetricTypes(rt.Ctx, query))
}

func (rt *Router) builtinMetricsCollectors(c *gin.Context) {
query := ginx.QueryStr(c, "query", "")
motongxue marked this conversation as resolved.
Show resolved Hide resolved

ginx.NewRender(c).Data(models.BuiltinMetricCollectors(rt.Ctx, query))
}
20 changes: 20 additions & 0 deletions docker/initsql/a-n9e.sql
Original file line number Diff line number Diff line change
Expand Up @@ -634,3 +634,23 @@ CREATE TABLE `es_index_pattern` (
PRIMARY KEY (`id`),
UNIQUE KEY (`datasource_id`, `name`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

CREATE TABLE `builtin_metrics` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'unique identifier',
`collector` varchar(191) NOT NULL COMMENT 'type of collector',
`typ` varchar(191) NOT NULL COMMENT 'type of metric',
`name` varchar(191) NOT NULL COMMENT 'name of metric',
`unit` varchar(191) NOT NULL COMMENT 'unit of metric',
`desc_cn` varchar(4096) NOT NULL COMMENT 'description of metric in Chinese',
`desc_en` varchar(4096) NOT NULL COMMENT 'description of metric in English',
`expression` varchar(4096) NOT NULL COMMENT 'expression of metric',
`created_at` bigint NOT NULL DEFAULT 0 COMMENT 'create time',
`created_by` varchar(191) NOT NULL DEFAULT '' COMMENT 'creator',
`updated_at` bigint NOT NULL DEFAULT 0 COMMENT 'update time',
`updated_by` varchar(191) NOT NULL DEFAULT '' COMMENT 'updater',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_collector_typ_name` (`collector`, `typ`, `name`),
INDEX `idx_collector` (`collector`),
INDEX `idx_typ` (`typ`),
INDEX `idx_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
167 changes: 167 additions & 0 deletions models/builtin_metric.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package models

import (
"errors"
"strings"
"time"

"github.com/ccfos/nightingale/v6/pkg/ctx"
)

// BuiltinMetric represents a metric along with its metadata.
type BuiltinMetric struct {
motongxue marked this conversation as resolved.
Show resolved Hide resolved
ID uint64 `json:"id" gorm:"primaryKey;type:bigint;autoIncrement;comment:'unique identifier'"` // Unique identifier
Collector string `json:"collector" gorm:"type:varchar(191);not null;index:idx_collector,sort:asc;comment:'type of collector'"` // Type of collector (e.g., 'categraf', 'telegraf')
Typ string `json:"typ" gorm:"type:varchar(191);not null;index:idx_typ,sort:asc;comment:'type of metric'"` // Type of metric (e.g., 'host', 'mysql', 'redis')
Name string `json:"name" gorm:"type:varchar(191);not null;index:idx_name,sort:asc;comment:'name of metric'"` // Name of the metric
Unit string `json:"unit" gorm:"type:varchar(191);not null;comment:'unit of metric'"` // Unit of the metric
DescCN string `json:"desc_cn" gorm:"type:varchar(4096);not null;comment:'description of metric in Chinese'"` // Description in Chinese
DescEN string `json:"desc_en" gorm:"type:varchar(4096);not null;comment:'description of metric in English'"` // Description in English
Expression string `json:"expression" gorm:"type:varchar(4096);not null;comment:'expression of metric'"` // Expression for calculation
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:'create time'"` // Creation timestamp (unix time)
CreatedBy string `json:"created_by" gorm:"type:varchar(191);not null;default:'';comment:'creator'"` // Creator
UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;not null;default:0;comment:'update time'"` // Update timestamp (unix time)
UpdatedBy string `json:"updated_by" gorm:"type:varchar(191);not null;default:'';comment:'updater'"` // Updater
}

func (bm *BuiltinMetric) TableName() string {
return "builtin_metrics"
}

func (bm *BuiltinMetric) Verify() error {
bm.Collector = strings.TrimSpace(bm.Collector)
if bm.Collector == "" {
return errors.New("collector is blank")
}

bm.Typ = strings.TrimSpace(bm.Typ)
if bm.Typ == "" {
return errors.New("type is blank")
}

bm.Name = strings.TrimSpace(bm.Name)
if bm.Name == "" {
return errors.New("name is blank")
}

return nil
}

func BuiltinMetricExists(ctx *ctx.Context, bm *BuiltinMetric) (bool, error) {
var count int64
err := DB(ctx).Model(bm).Where("collector = ? and typ = ? and name = ?", bm.Collector, bm.Typ, bm.Name).Count(&count).Error
if err != nil {
return false, err
}
return count > 0, nil
}

func (bm *BuiltinMetric) Add(ctx *ctx.Context, username string) error {
if err := bm.Verify(); err != nil {
return err
}
// check if the builtin metric already exists
exists, err := BuiltinMetricExists(ctx, bm)
if err != nil {
return err
}
if exists {
return errors.New("builtin metric already exists")
}
now := time.Now().Unix()
bm.CreatedAt = now
bm.UpdatedAt = now
bm.CreatedBy = username
return Insert(ctx, bm)
}

func (bm *BuiltinMetric) Update(ctx *ctx.Context, req BuiltinMetric) error {
if err := req.Verify(); err != nil {
return err
}

if bm.Collector != req.Collector && bm.Typ != req.Typ && bm.Name != req.Name {
exists, err := BuiltinMetricExists(ctx, &req)
if err != nil {
return err
}
if exists {
return errors.New("builtin metric already exists")
}
}
req.UpdatedAt = time.Now().Unix()

return DB(ctx).Model(bm).Select("*").Updates(req).Error
}

func BuiltinMetricDels(ctx *ctx.Context, ids []int64) error {
if len(ids) == 0 {
return nil
}
return DB(ctx).Where("id in ?", ids).Delete(new(BuiltinMetric)).Error
}

func BuiltinMetricGets(ctx *ctx.Context, collector, typ, query string, limit, offset int) ([]*BuiltinMetric, error) {
session := DB(ctx)
if collector != "" {
session = session.Where("collector = ?", collector)
}
if typ != "" {
session = session.Where("typ = ?", typ)
}
if query != "" {
queryPattern := "%" + query + "%"
session = session.Where("name LIKE ? OR desc_cn LIKE ? OR desc_en LIKE ?", queryPattern, queryPattern, queryPattern)
}

var lst []*BuiltinMetric

err := session.Limit(limit).Offset(offset).Find(&lst).Error

return lst, err
}

func BuiltinMetricCount(ctx *ctx.Context, collector, typ, query string) (int64, error) {
session := DB(ctx).Model(&BuiltinMetric{})
if collector != "" {
session = session.Where("collector = ?", collector)
}
if typ != "" {
session = session.Where("typ = ?", typ)
}
if query != "" {
queryPattern := "%" + query + "%"
session = session.Where("name LIKE ? OR desc_cn LIKE ? OR desc_en LIKE ?", queryPattern, queryPattern, queryPattern)
}

var cnt int64
err := session.Count(&cnt).Error

return cnt, err
}

func BuiltinMetricGet(ctx *ctx.Context, where string, args ...interface{}) (*BuiltinMetric, error) {
var lst []*BuiltinMetric
err := DB(ctx).Where(where, args...).Find(&lst).Error
if err != nil {
return nil, err
}

if len(lst) == 0 {
return nil, nil
}

return lst[0], nil
}

func BuiltinMetricTypes(ctx *ctx.Context, query string) ([]string, error) {
var typs []string
err := DB(ctx).Model(&BuiltinMetric{}).Where("typ like ?","%"+query+"%").Select("distinct(typ)").Pluck("typ", &typs).Error
motongxue marked this conversation as resolved.
Show resolved Hide resolved
return typs, err
}

func BuiltinMetricCollectors(ctx *ctx.Context, query string) ([]string, error) {
var collectors []string
err := DB(ctx).Model(&BuiltinMetric{}).Where("collector like ?","%"+query+"%").Select("distinct(collector)").Pluck("collector", &collectors).Error
return collectors, err
}
2 changes: 1 addition & 1 deletion models/migrate/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func Migrate(db *gorm.DB) {
func MigrateTables(db *gorm.DB) error {
dts := []interface{}{&RecordingRule{}, &AlertRule{}, &AlertSubscribe{}, &AlertMute{},
&TaskRecord{}, &ChartShare{}, &Target{}, &Configs{}, &Datasource{}, &NotifyTpl{},
&Board{}, &BoardBusigroup{}, &Users{}, &SsoConfig{}}
&Board{}, &BoardBusigroup{}, &Users{}, &SsoConfig{}, &models.BuiltinMetric{}}

if !columnHasIndex(db, &AlertHisEvent{}, "last_eval_time") {
dts = append(dts, &AlertHisEvent{})
Expand Down