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:
1 parent
7af10097c9
commit
f725ca7cac
11 files changed
+670
-53
No files matched your search
@@ -1,13 +1,56 @@
|
||||
// Grafana 仪表盘 API(暂无后端,返回空结果优雅降级) // Logan.Li
|
||||
// Q1 类型安全: 对接 types/monitor.ts(GrafanaDashboard)
|
||||
// 自定义监控仪表盘 API // Logan.Li
|
||||
|
||||
import { http } from '@/utils/request';
|
||||
import type { GrafanaDashboard } from '@/types/monitor';
|
||||
|
||||
export const dashboardApi = {
|
||||
getDashboards: async (): Promise<{ data: GrafanaDashboard[]; total: number }> => ({ data: [], total: 0 }),
|
||||
getDatasources: async (): Promise<{ data: Record<string, unknown>[]; total: number }> => ({ data: [], total: 0 }),
|
||||
createDashboard: async (_data: Partial<GrafanaDashboard>): Promise<{ success: boolean }> => ({ success: false }),
|
||||
updateDashboard: async (_id: number | string, _data: Partial<GrafanaDashboard>): Promise<{ success: boolean }> => ({ success: false }),
|
||||
deleteDashboard: async (_id: number | string): Promise<{ success: boolean }> => ({ success: false }),
|
||||
createDatasource: async (_data: Record<string, unknown>): Promise<{ success: boolean }> => ({ success: false }),
|
||||
getDashboards: async (): Promise<{ data: GrafanaDashboard[]; total: number }> => {
|
||||
const res = await http.get('/custom-dashboards');
|
||||
// http 自动解包 .data,后端返回 {list, total}
|
||||
const body = (res as Record<string, unknown>) ?? {};
|
||||
const list = (body.list as GrafanaDashboard[]) ?? [];
|
||||
const total = (body.total as number) ?? list.length;
|
||||
return { data: list, total };
|
||||
},
|
||||
getDatasources: async (): Promise<{ data: Record<string, unknown>[]; total: number }> => {
|
||||
// 复用 Prometheus 数据源列表
|
||||
const res = await http.get('/prometheus-datasources');
|
||||
const arr = (res as Record<string, unknown>[]) ?? [];
|
||||
return { data: arr, total: arr.length };
|
||||
},
|
||||
createDashboard: async (data: Partial<GrafanaDashboard>): Promise<{ success: boolean }> => {
|
||||
// 将 panels 数组序列化为 JSON 字符串
|
||||
const payload: Record<string, unknown> = {
|
||||
name: data.name ?? '',
|
||||
description: data.description ?? '',
|
||||
datasource: data.datasource ?? 'prometheus',
|
||||
datasource_url: data.datasourceUrl ?? '',
|
||||
panels: JSON.stringify(data.panels ?? []),
|
||||
is_default: data.isDefault ?? false,
|
||||
};
|
||||
await http.post('/custom-dashboards', payload);
|
||||
return { success: true };
|
||||
},
|
||||
updateDashboard: async (id: number | string, data: Partial<GrafanaDashboard>): Promise<{ success: boolean }> => {
|
||||
const payload: Record<string, unknown> = {};
|
||||
if (data.name !== undefined) payload.name = data.name;
|
||||
if (data.description !== undefined) payload.description = data.description;
|
||||
if (data.datasource !== undefined) payload.datasource = data.datasource;
|
||||
if (data.datasourceUrl !== undefined) payload.datasource_url = data.datasourceUrl;
|
||||
if (data.panels !== undefined) payload.panels = JSON.stringify(data.panels);
|
||||
if (data.isDefault !== undefined) payload.is_default = data.isDefault;
|
||||
await http.put(`/custom-dashboards/${id}`, payload);
|
||||
return { success: true };
|
||||
},
|
||||
deleteDashboard: async (id: number | string): Promise<{ success: boolean }> => {
|
||||
await http.delete(`/custom-dashboards/${id}`);
|
||||
return { success: true };
|
||||
},
|
||||
setDefault: async (id: number | string): Promise<{ success: boolean }> => {
|
||||
await http.post(`/custom-dashboards/${id}/default`);
|
||||
return { success: true };
|
||||
},
|
||||
createDatasource: async (_data: Record<string, unknown>): Promise<{ success: boolean }> => {
|
||||
return { success: false };
|
||||
},
|
||||
};
|
||||
@@ -25,6 +25,11 @@ export const snmpApi = {
|
||||
export const prometheusProxyApi = {
|
||||
connect: (url: string) => http.post('/prometheus/connect', { url }),
|
||||
dashboard: (url: string, type: string) => http.post('/prometheus/dashboard', { url, type }),
|
||||
// F4: PromQL 即时查询
|
||||
query: (url: string, query: string) => http.post('/prometheus/query', { url, query }),
|
||||
// F4: PromQL 范围查询
|
||||
queryRange: (params: { url: string; query: string; start?: string; end?: string; step?: string }) =>
|
||||
http.post('/prometheus/query_range', params),
|
||||
};
|
||||
|
||||
export const zabbixProxyApi = {
|
||||
|
||||
Reference in new issue
Block a user