@cloudcare/guance-front-tools 1.0.11 → 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +8 -0
  2. package/guance-all-charts.json +3415 -0
  3. package/lib/cjs/generated/dashboardCharts.d.ts +54 -13
  4. package/lib/cjs/scripts/grafana-covert-to-guance-core.d.ts +4 -0
  5. package/lib/cjs/scripts/grafana-covert-to-guance-core.js +10 -0
  6. package/lib/cjs/scripts/grafana-dashbord.d.ts +2220 -0
  7. package/lib/cjs/scripts/grafana-dashbord.js +4 -0
  8. package/lib/cjs/src/grafana-covert-to-guance.d.ts +2 -0
  9. package/lib/cjs/src/grafana-covert-to-guance.js +5 -0
  10. package/lib/cjs/src/index.d.ts +1 -0
  11. package/lib/cjs/src/index.js +1 -0
  12. package/lib/esm/generated/dashboardCharts.d.ts +54 -13
  13. package/lib/esm/scripts/grafana-covert-to-guance-core.d.ts +4 -0
  14. package/lib/esm/scripts/grafana-covert-to-guance-core.js +7 -0
  15. package/lib/esm/scripts/grafana-dashbord.d.ts +2220 -0
  16. package/lib/esm/scripts/grafana-dashbord.js +1 -0
  17. package/lib/esm/src/grafana-covert-to-guance.d.ts +2 -0
  18. package/lib/esm/src/grafana-covert-to-guance.js +1 -0
  19. package/lib/esm/src/index.d.ts +1 -0
  20. package/lib/esm/src/index.js +1 -0
  21. package/lib/example/grafana2.json +878 -0
  22. package/lib/example/guance-dahs-3.json +348 -0
  23. package/lib/scripts/grafana-covert-to-guance-core.js +7 -0
  24. package/lib/scripts/grafana-covert-to-guance-core.ts +23 -0
  25. package/lib/scripts/grafana-covert-to-guance.js +52 -397
  26. package/lib/scripts/grafana-covert-to-guance.ts +58 -410
  27. package/lib/src/grafana-covert-to-guance.ts +7 -0
  28. package/lib/src/index.ts +1 -0
  29. package/package.json +6 -4
  30. package/schemas/charts/chart-schema.json +8 -5
  31. package/schemas/charts/common/chart-link-item-schema.json +48 -0
  32. package/schemas/charts/common/chart-links-schema.json +9 -0
  33. package/schemas/charts/common/common-chart-types-schema.json +3 -1
  34. package/schemas/charts/dashboard-schema.json +11 -4
  35. package/schemas/charts/query/query-item-schema.json +19 -1
  36. package/schemas/charts/settings/settings-time-schema.json +1 -5
  37. package/schemas/charts/settings/settings-unit-items-schema.json +3 -1
  38. package/schemas/charts/settings/settings-units-schema.json +2 -3
  39. package/scripts/validate-file.mjs +57 -0
  40. package/skills/grafana-to-guance-dashboard/SKILL.md +102 -0
  41. package/skills/grafana-to-guance-dashboard/agents/openai.yaml +4 -0
  42. package/skills/grafana-to-guance-dashboard/references/converter-notes.md +134 -0
  43. package/skills/grafana-to-guance-dashboard/scripts/convert-grafana-dashboard.mjs +1899 -0
  44. package/test/cli.test.mjs +373 -0
  45. package/test-output/grafana2.cli.guance.json +1029 -0
  46. package/test-output/grafana2.guance.json +1029 -0
  47. package/test-output/grafana2.keep-meta.guance.json +1384 -0
  48. package/test-output/pod.guance.json +2153 -0
  49. package/test-output/skill-test2-enhanced.guance.json +21596 -0
  50. package/test-output/skill-test2-validated.guance.json +11610 -0
  51. package/test-output/skill-test2.guance.json +11610 -0
  52. package/test-output/test.guance.json +1086 -0
  53. package/test-output/test2.guance.guance-promql.json +23212 -0
  54. package/test-output/test2.guance.json +17554 -0
@@ -7,411 +7,66 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import * as fs from 'fs';
11
- import * as path from 'path';
10
+ import fs from 'fs';
11
+ import path from 'path';
12
12
  import yargs from 'yargs';
13
- // 1. Graph (折线图) - 用于展示时间序列数据的线图或柱状图。
14
- // 2. Stat (统计) - 用于显示单个数值,如当前值、最小值或最大值。
15
- // 3. Gauge (仪表盘) - 用于显示单个数值,并通过仪表盘样式进行可视化。
16
- // 4. Bar Gauge (柱状仪表盘) - 类似于仪表盘,但以柱状方式显示。
17
- // 5. Table (表格) - 用于以表格形式展示数据,支持多列、多行显示。
18
- // 6. Singlestat (单数值) - 适合展示单个重要的数值(现已被 Stat 面板替代)。
19
- // 7. Heatmap (热力图) - 用于显示数据密度或强度的热力图。
20
- // 8. Alert List (告警列表) - 用于展示触发的告警和状态。
21
- // 9. Logs (日志) - 用于展示和查询日志数据,尤其是通过 Loki 数据源。
22
- // 10. Pie Chart (饼图) - 用于显示数据的饼图。
23
- // 11. Text (文本) - 用于展示静态文本,支持 Markdown、HTML 格式。
24
- // 12. Gauge (Gauge 仪表) - 显示环形图表用于单个值的展示。
25
- // 13. Bar Chart (柱状图) - 显示条形图或柱状图。
26
- // 14. Timeseries (时间序列) - 最新的时间序列图表,提供更多功能和配置选项。
27
- // 15. Geomap (地理图) - 基于地图的地理数据可视化。
28
- // 16. Histogram (直方图) - 展示数据分布的图表。
29
- // 17. Status History (状态历史) - 显示状态随时间变化的历史记录。
30
- // 18. Canvas (画布) - 允许自定义绘制的可视化。
31
- // 19. Node Graph (节点图) - 可视化连接点之间的网络关系。
32
- // 20. XY Chart (XY 图表) - 显示二维数据的 XY 图表。
33
- // | 'group'
34
- // | 'sequence'
35
- // | 'singlestat'
36
- // | 'pie'
37
- // | 'bar'
38
- // | 'histogram'
39
- // | 'slo'
40
- // | 'toplist'
41
- // | 'gauge'
42
- // | 'scatter'
43
- // | 'bubble'
44
- // | 'table'
45
- // | 'treemap'
46
- // | 'funnel'
47
- // | 'chinamap'
48
- // | 'worldmap'
49
- // | 'hexgon'
50
- // | 'heatmap'
51
- // | 'topology'
52
- // | 'sankey'
53
- // | 'log'
54
- // | 'object'
55
- // | 'alarm'
56
- // | 'text'
57
- // | 'video'
58
- // | 'picture'
59
- // | 'command'
60
- // | 'iframe'
61
- const grafanaPanelTypeToGuanceChartMap = {
62
- stat: 'singlestat',
63
- singlestat: 'singlestat',
64
- barchart: 'bar',
65
- timeseries: 'sequence',
66
- graph: 'sequence',
67
- piechart: 'pie',
68
- histogram: 'histogram',
69
- bargauge: 'toplist',
70
- gauge: 'gauge',
71
- table: 'table',
72
- text: 'text',
73
- heatmap: 'heatmap',
74
- treemap: 'treemap',
75
- };
76
- const GRAFANA_KEYWORKD = ['__interval'];
77
- function replaceVariableStr(grafanaExpr) {
78
- return grafanaExpr.replace(/\$\{?([\d_\w]+)\}?/g, function (match, variable) {
79
- if (GRAFANA_KEYWORKD.includes(variable))
80
- return match;
81
- return `#{${variable}}`;
82
- });
83
- }
84
- /**
85
- * Get layout items sorted from top left to right and down.
86
- *
87
- * @return {Array} Array of layout objects.
88
- * @return {Array} Layout, sorted static items first.
89
- */
90
- function sortPanelItemsByRowCol(panels) {
91
- return panels.slice(0).sort(function (panelA, panelB) {
92
- // 宽度长的在前面
93
- const { gridPos: posA } = panelA;
94
- const { gridPos: posB } = panelB;
95
- if (!posA || !posB)
96
- return -1;
97
- if (posA.y === posB.y && posA.x === posB.x && posA.w > posB.w) {
98
- return -1;
99
- }
100
- if (posA.y === posB.y && posA.x === posB.x) {
101
- return 0;
102
- }
103
- if (posA.y > posB.y || (posA.y === posB.y && posA.x > posB.x)) {
104
- return 1;
105
- }
106
- return -1;
107
- });
108
- }
109
- // 数字转成abc
110
- function tenToTweenty(source = 1) {
111
- let numArr = [];
112
- source--;
113
- do {
114
- numArr.push(source % 26);
115
- source = Math.floor(source / 26);
116
- } while (source > 0);
117
- return numArr
118
- .reverse()
119
- .map((item, index) => {
120
- return String.fromCharCode(item + 97 + (index === numArr.length - 1 ? 0 : -1));
121
- })
122
- .join('')
123
- .toLowerCase();
124
- }
125
- function getGridH(h, rowHeight, margin) {
126
- return Math.round(h * rowHeight + Math.max(0, 2 * (h - 1)) * margin);
127
- }
128
- function getGuanceHByGrafanaH(granfanH) {
129
- return (getGridH(granfanH, 30, 4) + 10) / 20;
130
- }
131
- function covertPanelToGuanceChart(grafanaPanel, rowPanel) {
132
- const { gridPos, title, type, targets, options } = grafanaPanel;
133
- // const { gridPos: rowGridPos } = rowPanel
134
- const chartType = grafanaPanelTypeToGuanceChartMap[type];
135
- let pos = {
136
- x: gridPos.x,
137
- w: gridPos.w,
138
- y: gridPos.y,
139
- h: gridPos.h,
140
- };
141
- if (rowPanel) {
142
- const { gridPos: rowGridPos } = rowPanel;
143
- if (rowGridPos && gridPos) {
144
- if (!rowPanel.collapsed) {
145
- pos = {
146
- x: gridPos.x,
147
- w: gridPos.w,
148
- y: gridPos.y - rowGridPos.y,
149
- h: gridPos.h,
150
- };
151
- }
152
- }
153
- }
154
- const queries = [];
155
- if (targets && targets.length) {
156
- let currentIndex = 0;
157
- targets.forEach((_target) => {
158
- const queryStr = _target.expr || _target.query || _target.queryText;
159
- if (!queryStr)
160
- return;
161
- currentIndex++;
162
- const queryItem = {
163
- datasource: 'dataflux',
164
- qtype: 'promql',
165
- type: chartType,
166
- query: {
167
- q: replaceVariableStr(queryStr),
168
- type: 'promql',
169
- code: tenToTweenty(currentIndex),
170
- promqlCode: currentIndex,
171
- },
172
- };
173
- queries.push(queryItem);
174
- });
175
- }
176
- let settings = {};
177
- if (options) {
178
- switch (chartType) {
179
- case 'text':
180
- const queryItem = {
181
- query: {
182
- content: options.content,
183
- },
184
- };
185
- queries.push(queryItem);
186
- break;
187
- case 'toplist':
188
- settings = {
189
- showTopSize: true,
190
- chartType: 'bar',
191
- };
192
- break;
193
- default:
194
- break;
195
- }
196
- }
197
- const guanceChart = {
198
- extend: {
199
- settings: settings,
200
- },
201
- group: {
202
- name: rowPanel ? rowPanel.title : null,
203
- },
204
- pos: {
205
- x: pos.x,
206
- y: getGuanceHByGrafanaH(pos.y),
207
- h: getGuanceHByGrafanaH(pos.h),
208
- w: pos.w,
209
- },
210
- name: replaceVariableStr(title || ''),
211
- queries: queries,
212
- type: chartType,
213
- };
214
- return guanceChart;
215
- }
216
- const covertPanelsToCharts = (grafanaPanelData, rowPanel) => {
217
- const guanceCharts = [];
218
- grafanaPanelData.forEach((grafanaPanel) => {
219
- if (!grafanaPanel.gridPos)
220
- return;
221
- guanceCharts.push(covertPanelToGuanceChart(grafanaPanel, rowPanel));
222
- });
223
- return guanceCharts;
224
- };
225
- const VARIABLE_MAP = {
226
- query: 'PROMQL_QUERY',
227
- custom: 'CUSTOM_LIST',
228
- };
229
- const VARIABLE_DATASOURCE_MAP = {
230
- query: 'dataflux',
231
- custom: 'custom',
232
- };
233
- const covert = (grafanaData) => {
234
- var _a, _b, _c;
235
- const covertGuanceResult = {};
236
- // 标题
237
- covertGuanceResult.title = grafanaData.title;
238
- // 视图变量
239
- const guanceVars = [];
240
- (_b = (_a = grafanaData.templating) === null || _a === void 0 ? void 0 : _a.list) === null || _b === void 0 ? void 0 : _b.forEach((_variable, index) => {
241
- const { current, type, allValue } = _variable;
242
- if (!VARIABLE_MAP[type])
243
- return;
244
- let defaultVal = {
245
- label: '',
246
- value: '',
247
- };
248
- if (current) {
249
- let labels = [];
250
- let values = [];
251
- if (Array.isArray(current.text)) {
252
- labels = current.text;
253
- }
254
- else if (typeof current.text === 'string') {
255
- labels = [current.text];
256
- }
257
- if (Array.isArray(current.value)) {
258
- values = current.value;
259
- }
260
- else if (typeof current.value === 'string') {
261
- values = [current.value];
262
- }
263
- labels = labels.map((label) => {
264
- // all 的情况需要适配观测云
265
- if (label === 'All') {
266
- if (allValue === '.*') {
267
- return '*';
268
- }
269
- else {
270
- return 'all values';
271
- }
272
- }
273
- else {
274
- return label;
275
- }
276
- });
277
- values = values.map((value) => {
278
- // all 的情况需要适配观测云
279
- if (value === '$__all') {
280
- if (allValue === '.*') {
281
- return '*';
282
- }
283
- else {
284
- return '__all__';
285
- }
286
- }
287
- else {
288
- return value;
289
- }
290
- });
291
- defaultVal = {
292
- label: labels.join(','),
293
- value: values.join(','),
294
- };
295
- }
296
- let value = _variable.query;
297
- if (value && typeof value === 'object' && value.query) {
298
- value = replaceVariableStr(value.query);
299
- }
300
- else if (value && typeof value === 'string') {
301
- value = replaceVariableStr(_variable.query);
302
- }
303
- else {
304
- return;
305
- }
306
- const guanceVariableItem = {
307
- type: VARIABLE_MAP[type],
308
- datasource: VARIABLE_DATASOURCE_MAP[type],
309
- name: _variable.label || _variable.name || '',
310
- seq: index,
311
- hide: _variable.hide ? 1 : 0,
312
- multiple: _variable.multi !== undefined ? _variable.multi : true,
313
- includeStar: _variable.includeAll !== undefined ? _variable.includeAll : true,
314
- valueSort: 'desc',
315
- code: _variable.name,
316
- definition: {
317
- value: value,
318
- defaultVal: defaultVal,
319
- },
320
- };
321
- guanceVars.push(guanceVariableItem);
322
- });
323
- // 分组
324
- const guanceGroups = [];
325
- // 分组展开信息
326
- const guanceExpand = {};
327
- // 图表
328
- const guanceCharts = [];
329
- // 前一次分组,也就是当前图表的分组
330
- let lastRowPanel;
331
- // 当前分组的 panel 列表
332
- let lastPanels = [];
333
- let grafanaCharts = ((_c = grafanaData.panels) === null || _c === void 0 ? void 0 : _c.filter((_panel) => _panel.type === 'row' || grafanaPanelTypeToGuanceChartMap[_panel.type])) || [];
334
- grafanaCharts = sortPanelItemsByRowCol(grafanaCharts);
335
- grafanaCharts.forEach((_panel) => {
336
- var _a;
337
- if (_panel.type === 'row') {
338
- const _rowPanel = _panel;
339
- if (_rowPanel.title) {
340
- guanceGroups.push({
341
- name: _rowPanel.title,
342
- });
343
- // 分组展开收起数据
344
- guanceExpand[_rowPanel.title] = !_rowPanel.collapsed;
345
- }
346
- if (lastPanels.length) {
347
- // 上一个分组的图表转换,
348
- guanceCharts.push(...covertPanelsToCharts(lastPanels, lastRowPanel));
349
- //清空上一个分组列表
350
- lastPanels = [];
351
- //清空上一个分组
352
- lastRowPanel = undefined;
353
- }
354
- if (_rowPanel.collapsed) {
355
- // 收起
356
- const subPanels = ((_a = _rowPanel.panels) === null || _a === void 0 ? void 0 : _a.filter((_panel) => _panel.type === 'row' || grafanaPanelTypeToGuanceChartMap[_panel.type])) || [];
357
- guanceCharts.push(...covertPanelsToCharts(subPanels, _rowPanel));
358
- }
359
- else {
360
- lastRowPanel = _rowPanel;
361
- // 展开
362
- }
363
- }
364
- else {
365
- lastPanels.push(_panel);
366
- }
367
- });
368
- // 最后一个分组
369
- if (lastPanels.length) {
370
- guanceCharts.push(...covertPanelsToCharts(lastPanels, lastRowPanel));
371
- }
372
- covertGuanceResult.dashboardExtend = {
373
- groupUnfoldStatus: guanceExpand,
374
- };
375
- covertGuanceResult.main = {
376
- vars: guanceVars,
377
- charts: guanceCharts,
378
- groups: guanceGroups,
379
- };
380
- return covertGuanceResult;
381
- };
13
+ // @ts-ignore
14
+ import { convertDashboard, validateDashboardFile, } from '../../skills/grafana-to-guance-dashboard/scripts/convert-grafana-dashboard.mjs';
382
15
  export function run(args) {
383
16
  return __awaiter(this, void 0, void 0, function* () {
384
17
  const { argv } = yargs(args)
385
18
  .usage('Convert grafana dashboard json template to guance dashboard json template.')
386
- .demand('d')
387
- .alias('d', 'input')
388
- .describe('d', 'path to grafana dashboard json file path')
389
- .coerce('d', (d) => {
390
- const resolved = d && path.resolve(d);
391
- if (fs.existsSync(resolved) && /\.json$/.test(resolved)) {
392
- return resolved;
393
- }
394
- throw new Error(`Input Grafana JSON File "${d}" is not a JSON!`);
19
+ .option('d', {
20
+ alias: 'input',
21
+ type: 'string',
22
+ demandOption: true,
23
+ describe: 'path to grafana dashboard json file path',
24
+ coerce: (value) => {
25
+ const resolved = value && path.resolve(value);
26
+ if (fs.existsSync(resolved) && /\.json$/.test(resolved)) {
27
+ return resolved;
28
+ }
29
+ throw new Error(`Input Grafana JSON File "${value}" is not a JSON!`);
30
+ },
31
+ })
32
+ .option('o', {
33
+ alias: 'out',
34
+ type: 'string',
35
+ default: path.resolve(path.join('.', 'guance-dashboard.json')),
36
+ describe: 'path to output json file path',
37
+ coerce: (value) => path.resolve(value),
38
+ })
39
+ .option('validate', {
40
+ type: 'boolean',
41
+ default: false,
42
+ describe: 'validate generated dashboard against schema',
395
43
  })
396
- .alias('o', 'out')
397
- .describe('o', 'path to output json file path')
398
- .default('o', path.resolve(path.join('.', 'guance-dashboard.json')))
399
- .coerce('o', (o) => path.resolve(o));
44
+ .option('schema', {
45
+ type: 'string',
46
+ default: 'dashboard-schema.json',
47
+ describe: 'schema file name under schemas/',
48
+ })
49
+ .option('guance-promql-compatible', {
50
+ type: 'boolean',
51
+ default: false,
52
+ describe: 'rewrite PromQL metric names to Guance compatible measurement:field form',
53
+ })
54
+ .option('keep-grafana-meta', {
55
+ type: 'boolean',
56
+ default: false,
57
+ describe: 'keep source grafana metadata under extend.grafana',
58
+ });
400
59
  const grafanaJsonPath = argv.d;
401
60
  const outGuanceJsonPath = argv.o;
402
- try {
403
- const grafanaJSONData = JSON.parse(fs.readFileSync(grafanaJsonPath, 'utf-8'));
404
- const covertResult = covert(grafanaJSONData);
405
- // 确保目录存在
406
- const dir = path.dirname(outGuanceJsonPath);
407
- if (!fs.existsSync(dir)) {
408
- fs.mkdirSync(dir, { recursive: true }); // recursive: true 确保递归创建目录
409
- }
410
- fs.writeFileSync(outGuanceJsonPath, JSON.stringify(covertResult), 'utf-8');
411
- }
412
- catch (err) {
413
- throw new Error(err);
61
+ const grafanaJSONData = JSON.parse(fs.readFileSync(grafanaJsonPath, 'utf-8'));
62
+ const convertResult = convertDashboard(grafanaJSONData, {
63
+ guancePromqlCompatible: Boolean(argv.guancePromqlCompatible),
64
+ keepGrafanaMeta: Boolean(argv.keepGrafanaMeta),
65
+ });
66
+ fs.mkdirSync(path.dirname(outGuanceJsonPath), { recursive: true });
67
+ fs.writeFileSync(outGuanceJsonPath, `${JSON.stringify(convertResult, null, 2)}\n`, 'utf-8');
68
+ if (argv.validate) {
69
+ validateDashboardFile(outGuanceJsonPath, String(argv.schema));
414
70
  }
415
71
  });
416
72
  }
417
- // console.log(JSON.stringify(result, null, 2))