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 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions center/router/router.go
Expand Up @@ -229,6 +229,11 @@ 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.builtinMetricsAdd)
pages.GET("/builtin-metrics", rt.auth(), rt.user(), rt.builtinMetricsGets)
pages.PUT("/builtin-metrics", rt.auth(), rt.user(), rt.builtinMetricsPut)
pages.DELETE("/builtin-metrics", rt.auth(), rt.user(), rt.builtinMetricsDel)

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
69 changes: 69 additions & 0 deletions center/router/router_buildin_metrics.go
@@ -0,0 +1,69 @@
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", "")
search := ginx.QueryStr(c, "search", "")
limit := ginx.QueryInt(c, "limit", 20)

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

total, err := models.BuiltinMetricCount(rt.Ctx, collector, typ, search)
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.BuiltinMetricGetByID(rt.Ctx, 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))
}
20 changes: 20 additions & 0 deletions docker/initsql/a-n9e.sql
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(191) NOT NULL COMMENT 'expression of metric',
motongxue marked this conversation as resolved.
Show resolved Hide resolved
`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;
163 changes: 163 additions & 0 deletions models/builtin_metric.go
@@ -0,0 +1,163 @@
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 int64 `json:"id" gorm:"primaryKey"` // Unique identifier
Collector string `json:"collector"` // Typ of collector (e.g., 'categraf', 'telegraf')
Typ string `json:"typ"` // Typ of metric (e.g., 'host', 'mysql', 'redis')
Name string `json:"name"` // Name of the metric
Unit string `json:"unit"` // Unit of the metric
DescCN string `json:"desc_cn"` // Description in Chinese
DescEN string `json:"desc_en"` // Description in English
Expression string `json:"expression"` // Expression for calculation
CreatedAt int64 `json:"created_at"` // Creation timestamp (unix time)
CreatedBy string `json:"created_by"` // Creator
UpdatedAt int64 `json:"updated_at"` // Update timestamp (unix time)
motongxue marked this conversation as resolved.
Show resolved Hide resolved
UpdatedBy string `json:"updated_by"` // 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 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()
req.CreatedAt = bm.CreatedAt
motongxue marked this conversation as resolved.
Show resolved Hide resolved
req.CreatedBy = bm.CreatedBy

if err := req.Verify(); err != nil {
motongxue marked this conversation as resolved.
Show resolved Hide resolved
return err
}

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

func BuiltinMetricDels(ctx *ctx.Context, ids []int64) error {
for i := 0; i < len(ids); i++ {
ret := DB(ctx).Where("id = ?", ids[i]).Delete(&BuiltinMetric{})
motongxue marked this conversation as resolved.
Show resolved Hide resolved
if ret.Error != nil {
return ret.Error
}
}
return nil
}

func BuiltinMetricGetByID(ctx *ctx.Context, id int64) (*BuiltinMetric, error) {
motongxue marked this conversation as resolved.
Show resolved Hide resolved
return BuiltinMetricGet(ctx, "id = ?", id)
}

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

var lst []*BuiltinMetric

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

return lst, err
}

func BuiltinMetricCount(ctx *ctx.Context, collector, typ, search string) (int64, error) {
session := DB(ctx).Model(&BuiltinMetric{})
if collector != "" {
session = session.Where("collector = ?", collector)
}
if typ != "" {
session = session.Where("typ = ?", typ)
}
if search != "" {
session = session.Where("name like ?", "%"+search+"%").Where("desc_cn like ?", "%"+search+"%").Where("desc_en like ?", "%"+search+"%")
motongxue marked this conversation as resolved.
Show resolved Hide resolved
}

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
}