fix(logs): 快捷 LogQL null 崩溃 + app 标签错位 + 查询授权「所有人」与默认策略
=== 一、快捷 LogQL 报 Cannot read properties of null (reading 'map') ===
根因:Loki 对无值的 label 返回 {"data":null},Go nil slice 序列化成 JSON null,
前端 setState(null) 后再 .map() / [...null] / .filter() 直接抛错。
表现为「不稳定」——切到某个没有该 label 值的范围就崩。
修复(前后端双保险):
后端 LokiLabels / LokiLabelValues 归一化 nil → []string{}
(LokiHistogram 原本就返回空切片,无需改)
前端 6 处调用点加 Array.isArray 防御:
QuickQueryTemplates ×3、QueryInput ×2、LokiLabelBrowser ×2
=== 二、选 astralx 只显示 2 个 app(不是权限问题)===
实测:
astralx 117 个 Deployment / 497 个 Pod
带 app 标签的 Pod 只有 2 个(cex-promotion-web / cex-web-h5)
其余 115 个 Deployment 的 Pod 模板没打 app,Kuboard 打的是 k8s.kuboard.cn/name
Loki 里 service_name / container 各有 79 个值(Promtail 从容器名生成)
修复:QuickQueryTemplates 的「app」候选改为三级回退 app → service_name → container,
并用 appLabel 状态记住实际生效的标签名,pod 收窄与 generate() 都用它,
保证生成的 LogQL selector 真能查到数据。
=== 三、查询授权:支持「所有人」+ 内置默认策略 ===
1. 后端 policyGroupAccess:userIds 里的 0 作为哨兵值表示「所有人」(anyone / *),
匹配任意用户(原来只能逐个指定用户 ID)
2. 种子 seedDefaultPolicy():首次启动且尚无任何策略组时,创建
「默认策略(所有人 · default 命名空间)」(labels: namespace=default,userIds: [0])。
管理员可在「查询授权」里把命名空间改成实际业务命名空间(如 astralx),
对该组所有用户立即生效。
3. 前端 PolicyGroupsPage:新增「所有人(anyone)」一键按钮(与具体用户互斥),
列表与编辑区显示「所有人」,校验文案同步更新。
验证:
GET /logs/label/nonexistent_xyz/values → {"data":[]}
GET /logs/label/service_name/values?query={namespace="astralx"} → 79 个
GET /logs/policy-groups → 1 条:默认策略(所有人 · default 命名空间)所有人
镜像:lghost/lokistack:latest + v0.1 = sha256:88a4ba626f62
This commit is contained in:
1 parent
46e73d3d17
commit
4dda6e5ba8
7 files changed
+119
-16
No files matched your search
@@ -5,6 +5,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"opsxloki/models"
|
||||
"opsxloki/pkg/crypto"
|
||||
@@ -82,10 +83,55 @@ func Seed() {
|
||||
}
|
||||
}
|
||||
|
||||
// 内置默认查询策略:所有人可查询 default 命名空间。
|
||||
// 目的:新环境/新账号开箱即有可查范围,快捷 LogQL 的 namespace 下拉不会空。
|
||||
seedDefaultPolicy()
|
||||
|
||||
// 内置告警规则:有 managed 数据源(部署过 Loki)时补齐通用模板(默认禁用)
|
||||
SeedAlertRules()
|
||||
}
|
||||
|
||||
// seedDefaultPolicy 创建「默认策略」:所有用户可查询 default 命名空间。
|
||||
//
|
||||
// 设计要点:
|
||||
// - UserIDs 里的 0 是哨兵值,表示「所有人」(anyone / *),见 policyGroupAccess
|
||||
// - 只在当前还没有任何策略组时创建,避免覆盖管理员的自定义配置
|
||||
// - 命名空间取 default 作为占位;管理员可在「查询授权」里改成实际业务命名空间
|
||||
// (如 astralx / prod),改动对该策略组所有用户立即生效
|
||||
func seedDefaultPolicy() {
|
||||
db := repository.GetDB()
|
||||
if db == nil {
|
||||
return
|
||||
}
|
||||
db.RLock()
|
||||
hasGroup := len(db.LogPolicyGroups) > 0
|
||||
var sourceID uint
|
||||
if len(db.LogSources) > 0 {
|
||||
sourceID = db.LogSources[0].ID
|
||||
}
|
||||
db.RUnlock()
|
||||
if hasGroup || sourceID == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
id := db.NextID("logPolicyGroups")
|
||||
now := time.Now()
|
||||
group := models.LogPolicyGroup{
|
||||
ID: id,
|
||||
Name: "默认策略(所有人 · default 命名空间)",
|
||||
SourceID: sourceID,
|
||||
Labels: map[string]string{"namespace": "default"},
|
||||
UserIDs: []uint{0}, // 0 = 所有人
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
db.Lock()
|
||||
db.LogPolicyGroups = append(db.LogPolicyGroups, group)
|
||||
db.Unlock()
|
||||
db.RequestSave()
|
||||
log.Printf("[SEED] 已创建默认查询策略:所有人可查 default 命名空间(可在「查询授权」改为实际业务命名空间)")
|
||||
}
|
||||
|
||||
// CleanupInitPassword 在 admin 首次修改密码后调用,删除明文密码文件。
|
||||
// 幂等:文件不存在时静默返回。
|
||||
func CleanupInitPassword() {
|
||||
|
||||
@@ -166,7 +166,9 @@ func policyGroupAccess(groups []models.LogPolicyGroup, sourceID, userID uint) *m
|
||||
continue
|
||||
}
|
||||
for _, id := range group.UserIDs {
|
||||
if id == userID {
|
||||
// 0 是哨兵值:表示「所有人」(anyone / *),匹配任意用户。
|
||||
// 用于默认策略——让所有账号开箱即可查询业务命名空间。
|
||||
if id == 0 || id == userID {
|
||||
return &models.LogSourceAccess{UserID: userID, Labels: group.Labels}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,6 +351,12 @@ func LokiLabels(ctx context.Context, src *models.LogSource, start, end int64) ([
|
||||
if err := json.Unmarshal(body, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Loki 在某 label 无值时会返回 {"data":null},Go nil slice 序列化成 JSON null,
|
||||
// 前端 setState(null) 后 .map() 直接抛 "Cannot read properties of null"。
|
||||
// 统一归一化为空切片,前端拿到的永远是数组。
|
||||
if resp.Data == nil {
|
||||
resp.Data = []string{}
|
||||
}
|
||||
return resp.Data, nil
|
||||
}
|
||||
|
||||
@@ -376,6 +382,11 @@ func LokiLabelValues(ctx context.Context, src *models.LogSource, name, selector
|
||||
if err := json.Unmarshal(body, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 同上:label 无值时 Loki 返回 {"data":null},归一化为空切片,
|
||||
// 避免前端 .map() 遇到 null 崩溃(快捷 LogQL 报错的直接原因)。
|
||||
if resp.Data == nil {
|
||||
resp.Data = []string{}
|
||||
}
|
||||
return resp.Data, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user