Repository: ccfos/nightingale

Issue:
Pagination not working in metric view after adding new metrics in Template Center
### Question and Steps to reproduce

新建模板后新增指标，接口 /api/n9e/builtin-metrics   获取指标分页失效，返回的结果数只有limit大小，不是总大小，导致无法分页

### Relevant logs and configurations

```text
无日志
```

### Version

v8.3.1

Discussion:

Solution patch:
diff --git a/center/router/router_builtin_metrics.go b/center/router/router_builtin_metrics.go
index 85c7fab1..203dc4f9 100644
--- a/center/router/router_builtin_metrics.go
+++ b/center/router/router_builtin_metrics.go
@@ -50,7 +50,7 @@ func (rt *Router) builtinMetricsGets(c *gin.Context) {
 		lang = "zh_CN"
 	}
 
-	bmInDB, err := models.BuiltinMetricGets(rt.Ctx, "", collector, typ, query, unit, limit, ginx.Offset(c, limit))
+	bmInDB, err := models.BuiltinMetricGets(rt.Ctx, "", collector, typ, query, unit)
 	ginx.Dangerous(err)
 
 	bm, total, err := integration.BuiltinPayloadInFile.BuiltinMetricGets(bmInDB, lang, collector, typ, query, unit, limit, ginx.Offset(c, limit))
diff --git a/dskit/sqlbase/timeseries.go b/dskit/sqlbase/timeseries.go
index 55f2bd7e..16dbe493 100644
--- a/dskit/sqlbase/timeseries.go
+++ b/dskit/sqlbase/timeseries.go
@@ -79,10 +79,11 @@ func FormatMetricValues(keys types.Keys, rows []map[string]interface{}, ignoreDe
 	}
 
 	if keys.TimeKey == "" {
-		keys.TimeKey = "time"
-	}
-
-	if len(keys.TimeKey) > 0 {
+		// 默认支持 __time__ 和 time 作为时间字段
+		// 用户可以使用 as __time__ 来避免与表中已有的 time 字段冲突
+		keyMap["__time__"] = "time"
+		keyMap["time"] = "time"
+	} else {
 		keyMap[keys.TimeKey] = "time"
 	}
 
@@ -142,7 +143,20 @@ func FormatMetricValues(keys types.Keys, rows []map[string]interface{}, ignoreDe
 			labelsStrHash := fmt.Sprintf("%x", md5.Sum([]byte(strings.Join(labelsStr, ","))))
 
 			// Append new values to the existing metric, if present
-			ts, exists := metricTs[keys.TimeKey]
+			var ts float64
+			var exists bool
+
+			if keys.TimeKey == "" {
+				// 没有配置 timeKey，按优先级查找：__time__ > time
+				ts, exists = metricTs["__time__"]
+				if !exists {
+					ts, exists = metricTs["time"]
+				}
+			} else {
+				// 用户配置了 timeKey，使用用户配置的
+				ts, exists = metricTs[keys.TimeKey]
+			}
+
 			if !exists {
 				ts = float64(time.Now().Unix()) // Default to current time if not specified
 			}
diff --git a/models/builtin_metrics.go b/models/builtin_metrics.go
index ec685be3..55905180 100644
--- a/models/builtin_metrics.go
+++ b/models/builtin_metrics.go
@@ -110,12 +110,14 @@ func BuiltinMetricDels(ctx *ctx.Context, ids []int64) error {
 	return DB(ctx).Where("id in ?", ids).Delete(new(BuiltinMetric)).Error
 }
 
-func BuiltinMetricGets(ctx *ctx.Context, lang, collector, typ, query, unit string, limit, offset int) ([]*BuiltinMetric, error) {
+func BuiltinMetricGets(ctx *ctx.Context, lang, collector, typ, query, unit string) ([]*BuiltinMetric, error) {
 	session := DB(ctx)
 	session = builtinMetricQueryBuild(lang, collector, session, typ, query, unit)
 	var lst []*BuiltinMetric
-	err := session.Limit(limit).Offset(offset).Order("collector asc, typ asc, name asc").Find(&lst).Error
-	return lst, err
+	if err := session.Order("collector asc, typ asc, name asc").Find(&lst).Error; err != nil {
+		return nil, err
+	}
+	return lst, nil
 }
 
 func builtinMetricQueryBuild(lang, collector string, session *gorm.DB, typ string, query, unit string) *gorm.DB {
diff --git a/models/user.go b/models/user.go
index 8c45c429..5b8ce8b3 100644
--- a/models/user.go
+++ b/models/user.go
@@ -408,6 +408,18 @@ func InitRoot(ctx *ctx.Context) bool {
 		return false
 	}
 
+	// 查询用户个数
+	count, err := Count(DB(ctx).Model(&User{}))
+	if err != nil {
+		fmt.Println("failed to count user:", err)
+		os.Exit(1)
+	}
+
+	if count == 1 {
+		// 说明数据库只有一个 root 用户，并且 root 用户密码没有加密，需要初始化 salt
+		InitSalt(ctx)
+	}
+
 	newPass, err := CryptoPass(ctx, user.Password)
 	if err != nil {
 		fmt.Println("failed to crypto pass:", err)



Test patch:
diff --git a/dskit/sqlbase/timeseries_test.go b/dskit/sqlbase/timeseries_test.go
index 41962659..4fa24ef1 100644
--- a/dskit/sqlbase/timeseries_test.go
+++ b/dskit/sqlbase/timeseries_test.go
@@ -56,6 +56,49 @@ func TestFormatMetricValues(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "test __time__ priority over time",
+			keys: types.Keys{
+				ValueKey: "value",
+				LabelKey: "host",
+			},
+			rows: []map[string]interface{}{
+				{
+					"host":     "server1",
+					"value":    100,
+					"time":     int64(1715642100), // 这个应该被忽略
+					"__time__": int64(1715642135), // 这个应该被使用
+				},
+			},
+		},
+		{
+			name: "test fallback to time when __time__ not exists",
+			keys: types.Keys{
+				ValueKey: "value",
+				LabelKey: "host",
+			},
+			rows: []map[string]interface{}{
+				{
+					"host":  "server2",
+					"value": 200,
+					"time":  int64(1715642200), // 应该使用这个
+				},
+			},
+		},
+		{
+			name: "test __time__ alone without time field",
+			keys: types.Keys{
+				ValueKey: "value",
+				LabelKey: "host",
+			},
+			rows: []map[string]interface{}{
+				{
+					"host":     "server3",
+					"value":    300,
+					"__time__": int64(1715642300), // 应该使用这个
+				},
+			},
+		},
 	}
 
 	for _, tt := range tests {