feat(monitor): F4 自定义监控仪表盘 — mock 桩改真实 CRUD + PromQL 查询渲染

后端:
- 新增 model/custom_dashboard.go (monitor_custom_dashboards 表)
- 新增 controller/custom_dashboard.go (CRUD + 设默认, 6 路由)
- routes.go 注册 /custom-dashboards 路由组
- database.go AutoMigrate 注册 CustomDashboard

前端:
- grafana.ts: dashboardApi 从 mock 返回空改为真实 HTTP 调用
- metrics.ts: prometheusProxyApi 新增 query/queryRange 方法
- GrafanaPanelContent.tsx: 从随机 mock 数据改为执行 PromQL 查询
  + Recharts LineChart 渲染 graph 类型
  + SVG gauge 渲染 gauge 类型
  + 数值卡片渲染 stat 类型
  + 表格渲染 table 类型
  + 30s 自动刷新 + loading/error 状态
  + 自动发现默认 Prometheus 数据源
- GrafanaDashboard.tsx: 传递 datasourceUrl 到 GrafanaPanelContent

验证: go build✅ npm build✅ 17/17 集成测试通过 E2E CRUD 全通过
This commit is contained in:
attacker committed 2026-06-27 23:28:04 +08:00
1 parent 7af10097c9
commit f725ca7cac
11 files changed
+670 -53

No files matched your search

+70
View File
@@ -450,6 +450,76 @@ grep -r 'kubernetes-clusters/.*\(pods\|deployments\|services\|ingresses\|nodes\|
---
## P1-F4: 自定义监控仪表盘
### 验证用例
```bash
TOKEN=$(python3 -c "
import urllib.request, json
login_data = json.dumps({'username': 'admin', 'password': '123456'}).encode()
req = urllib.request.Request('http://127.0.0.1:8080/api/v1/auth/login', data=login_data, method='POST')
req.add_header('Content-Type', 'application/json')
resp = urllib.request.urlopen(req)
print(json.loads(resp.read())['data']['token'])
")
# U1: 创建仪表盘
curl -s -X POST "http://127.0.0.1:8080/api/v1/custom-dashboards" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"测试","panels":"[]","is_default":true}' | python3 -c "import json,sys; d=json.load(sys.stdin); assert d['code']==200; print('✅ create')"
# 期望: code=200
# U2: 列表
curl -s "http://127.0.0.1:8080/api/v1/custom-dashboards" \
-H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; d=json.load(sys.stdin); assert d['data']['total']>=1; print('✅ list')"
# 期望: total >= 1
# U3: 更新(添加面板)
DASH_ID=$(curl -s "http://127.0.0.1:8080/api/v1/custom-dashboards" -H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; print(json.load(sys.stdin)['data']['list'][0]['id'])")
curl -s -X PUT "http://127.0.0.1:8080/api/v1/custom-dashboards/$DASH_ID" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"panels":"[{\"id\":\"1\",\"title\":\"CPU\",\"type\":\"graph\",\"query\":\"up\",\"width\":6,\"height\":4}]"}' | python3 -c "import json,sys; d=json.load(sys.stdin); assert d['code']==200; print('✅ update')"
# U4: 删除
curl -s -X DELETE "http://127.0.0.1:8080/api/v1/custom-dashboards/$DASH_ID" \
-H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; d=json.load(sys.stdin); assert d['code']==200; print('✅ delete')"
# U5: 前端 dashboardApi 不再是 mock
grep -c 'http.get\|http.post\|http.put\|http.delete' /srv/aiops/frontend/src/api/monitor/grafana.ts
# 期望: >= 5 (真实 HTTP 调用)
# U6: GrafanaPanelContent 使用 prometheusProxyApi(非 mock)
grep -c 'prometheusProxyApi' /srv/aiops/frontend/src/components/monitor/dashboard/GrafanaPanelContent.tsx
# 期望: >= 2
# U7: go build
cd /srv/aiops/backend && go build ./...
# 期望: 零错误
# U8: npm build
cd /srv/aiops/frontend && npm run build 2>&1 | tail -1
# 期望: built in Ns
# U9: 集成回归
python3 tests/integration_test.py --host 127.0.0.1 --port 8080 | tail -3
# 期望: 17/17 passed
```
### 通过标准
- [x] U1: 创建仪表盘返回 200
- [x] U2: 列表包含创建的仪表盘
- [x] U3: 更新仪表盘(添加面板)成功
- [x] U4: 删除仪表盘成功
- [x] U5: `dashboardApi` 全部方法为真实 HTTP 调用(非 mock 返回空)
- [x] U6: `GrafanaPanelContent` 使用 `prometheusProxyApi.query` / `queryRange` 执行 PromQL
- [x] U7: `go build` 零错误
- [x] U8: `npm run build` 零错误(✓ built in 18.01s)
- [x] U9: 17/17 集成回归测试通过
---
## 回归测试基线(每次改动后必跑)
```bash