@cloudcare/guance-front-tools 1.0.12 → 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.
- package/guance-all-charts.json +3415 -0
- package/lib/cjs/generated/dashboardCharts.d.ts +54 -13
- package/lib/cjs/scripts/grafana-covert-to-guance-core.d.ts +1 -1
- package/lib/cjs/scripts/grafana-covert-to-guance-core.js +5 -289
- package/lib/esm/generated/dashboardCharts.d.ts +54 -13
- package/lib/esm/scripts/grafana-covert-to-guance-core.d.ts +1 -1
- package/lib/esm/scripts/grafana-covert-to-guance-core.js +5 -289
- package/lib/example/grafana2.json +878 -0
- package/lib/example/guance-dahs-3.json +348 -0
- package/lib/scripts/grafana-covert-to-guance-core.js +5 -286
- package/lib/scripts/grafana-covert-to-guance-core.ts +12 -326
- package/lib/scripts/grafana-covert-to-guance.js +52 -29
- package/lib/scripts/grafana-covert-to-guance.ts +60 -32
- package/package.json +4 -3
- package/schemas/charts/chart-schema.json +8 -5
- package/schemas/charts/common/chart-link-item-schema.json +48 -0
- package/schemas/charts/common/chart-links-schema.json +9 -0
- package/schemas/charts/common/common-chart-types-schema.json +3 -1
- package/schemas/charts/dashboard-schema.json +11 -4
- package/schemas/charts/query/query-item-schema.json +19 -1
- package/schemas/charts/settings/settings-time-schema.json +1 -5
- package/schemas/charts/settings/settings-unit-items-schema.json +3 -1
- package/schemas/charts/settings/settings-units-schema.json +2 -3
- package/scripts/validate-file.mjs +57 -0
- package/skills/grafana-to-guance-dashboard/SKILL.md +102 -0
- package/skills/grafana-to-guance-dashboard/agents/openai.yaml +4 -0
- package/skills/grafana-to-guance-dashboard/references/converter-notes.md +134 -0
- package/skills/grafana-to-guance-dashboard/scripts/convert-grafana-dashboard.mjs +1899 -0
- package/test/cli.test.mjs +316 -0
- package/test-output/grafana2.cli.guance.json +1029 -0
- package/test-output/grafana2.guance.json +1029 -0
- package/test-output/grafana2.keep-meta.guance.json +1384 -0
- package/test-output/pod.guance.json +2153 -0
- package/test-output/skill-test2-enhanced.guance.json +21596 -0
- package/test-output/skill-test2-validated.guance.json +11610 -0
- package/test-output/skill-test2.guance.json +11610 -0
- package/test-output/test.guance.json +1086 -0
- package/test-output/test2.guance.guance-promql.json +23212 -0
- package/test-output/test2.guance.json +17554 -0
|
@@ -0,0 +1,1899 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import { pathToFileURL } from 'url'
|
|
6
|
+
import Ajv from 'ajv'
|
|
7
|
+
|
|
8
|
+
const PANEL_TYPE_MAP = {
|
|
9
|
+
stat: 'singlestat',
|
|
10
|
+
singlestat: 'singlestat',
|
|
11
|
+
timeseries: 'sequence',
|
|
12
|
+
graph: 'sequence',
|
|
13
|
+
trend: 'sequence',
|
|
14
|
+
bargauge: 'toplist',
|
|
15
|
+
gauge: 'gauge',
|
|
16
|
+
barchart: 'bar',
|
|
17
|
+
piechart: 'pie',
|
|
18
|
+
table: 'table',
|
|
19
|
+
text: 'text',
|
|
20
|
+
heatmap: 'heatmap',
|
|
21
|
+
histogram: 'histogram',
|
|
22
|
+
treemap: 'treemap',
|
|
23
|
+
geomap: 'worldmap',
|
|
24
|
+
logs: 'log',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const GRAFANA_BUILTIN_VARS = new Set([
|
|
28
|
+
'__interval',
|
|
29
|
+
'__interval_ms',
|
|
30
|
+
'__range',
|
|
31
|
+
'__range_s',
|
|
32
|
+
'__range_ms',
|
|
33
|
+
'__from',
|
|
34
|
+
'__to',
|
|
35
|
+
'__dashboard',
|
|
36
|
+
'__name',
|
|
37
|
+
'__org',
|
|
38
|
+
'__user',
|
|
39
|
+
])
|
|
40
|
+
|
|
41
|
+
const PROMQL_RESERVED_WORDS = new Set([
|
|
42
|
+
'and',
|
|
43
|
+
'or',
|
|
44
|
+
'unless',
|
|
45
|
+
'by',
|
|
46
|
+
'without',
|
|
47
|
+
'on',
|
|
48
|
+
'ignoring',
|
|
49
|
+
'group_left',
|
|
50
|
+
'group_right',
|
|
51
|
+
'bool',
|
|
52
|
+
'offset',
|
|
53
|
+
])
|
|
54
|
+
|
|
55
|
+
const UNIT_MAP = {
|
|
56
|
+
percent: ['percent', 'percent'],
|
|
57
|
+
bytes: ['digital', 'B'],
|
|
58
|
+
decbytes: ['digital', 'B'],
|
|
59
|
+
bits: ['digital', 'b'],
|
|
60
|
+
deckbytes: ['digital', 'KB'],
|
|
61
|
+
decgbytes: ['digital', 'GB'],
|
|
62
|
+
ms: ['time', 'ms'],
|
|
63
|
+
s: ['time', 's'],
|
|
64
|
+
m: ['time', 'min'],
|
|
65
|
+
h: ['time', 'h'],
|
|
66
|
+
d: ['time', 'd'],
|
|
67
|
+
short: ['custom', 'short'],
|
|
68
|
+
none: ['custom', 'none'],
|
|
69
|
+
reqps: ['custom', 'reqps'],
|
|
70
|
+
ops: ['custom', 'ops'],
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const COMPARE_OPTIONS = {
|
|
74
|
+
hourCompare: { label: '小时同比', value: 'hourCompare' },
|
|
75
|
+
dayCompare: { label: '日同比', value: 'dayCompare' },
|
|
76
|
+
weekCompare: { label: '周同比', value: 'weekCompare' },
|
|
77
|
+
monthCompare: { label: '月同比', value: 'monthCompare' },
|
|
78
|
+
circleCompare: { label: '环比', value: 'circleCompare' },
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (isDirectExecution()) {
|
|
82
|
+
main()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isDirectExecution() {
|
|
86
|
+
if (!process.argv[1]) return false
|
|
87
|
+
return import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function main() {
|
|
91
|
+
const { inputPath, outputPath, validateOutput, schemaId, guancePromqlCompatible, keepGrafanaMeta } = parseArgs(process.argv.slice(2))
|
|
92
|
+
const grafanaDashboard = readJson(inputPath)
|
|
93
|
+
const guanceDashboard = convertDashboard(grafanaDashboard, { guancePromqlCompatible, keepGrafanaMeta })
|
|
94
|
+
|
|
95
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true })
|
|
96
|
+
fs.writeFileSync(outputPath, `${JSON.stringify(guanceDashboard, null, 2)}\n`, 'utf8')
|
|
97
|
+
console.log(`Converted ${inputPath} -> ${outputPath}`)
|
|
98
|
+
|
|
99
|
+
if (validateOutput) {
|
|
100
|
+
validateDashboardFile(outputPath, schemaId)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function parseArgs(args) {
|
|
105
|
+
let inputPath = ''
|
|
106
|
+
let outputPath = ''
|
|
107
|
+
let validateOutput = false
|
|
108
|
+
let schemaId = 'dashboard-schema.json'
|
|
109
|
+
let guancePromqlCompatible = false
|
|
110
|
+
let keepGrafanaMeta = false
|
|
111
|
+
|
|
112
|
+
for (let index = 0; index < args.length; index++) {
|
|
113
|
+
const value = args[index]
|
|
114
|
+
if ((value === '-i' || value === '--input') && args[index + 1]) {
|
|
115
|
+
inputPath = path.resolve(args[++index])
|
|
116
|
+
continue
|
|
117
|
+
}
|
|
118
|
+
if ((value === '-o' || value === '--output') && args[index + 1]) {
|
|
119
|
+
outputPath = path.resolve(args[++index])
|
|
120
|
+
continue
|
|
121
|
+
}
|
|
122
|
+
if (value === '--validate') {
|
|
123
|
+
validateOutput = true
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
if (value === '--schema' && args[index + 1]) {
|
|
127
|
+
schemaId = args[++index]
|
|
128
|
+
continue
|
|
129
|
+
}
|
|
130
|
+
if (value === '--guance-promql-compatible') {
|
|
131
|
+
guancePromqlCompatible = true
|
|
132
|
+
continue
|
|
133
|
+
}
|
|
134
|
+
if (value === '--keep-grafana-meta') {
|
|
135
|
+
keepGrafanaMeta = true
|
|
136
|
+
continue
|
|
137
|
+
}
|
|
138
|
+
if (value === '-h' || value === '--help') {
|
|
139
|
+
printHelp()
|
|
140
|
+
process.exit(0)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!inputPath) {
|
|
145
|
+
printHelp()
|
|
146
|
+
process.exit(1)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!outputPath) {
|
|
150
|
+
const parsed = path.parse(inputPath)
|
|
151
|
+
outputPath = path.join(parsed.dir, `${parsed.name}.guance.json`)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return { inputPath, outputPath, validateOutput, schemaId, guancePromqlCompatible, keepGrafanaMeta }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function printHelp() {
|
|
158
|
+
console.error(
|
|
159
|
+
'Usage: node convert-grafana-dashboard.mjs --input <grafana.json> [--output <guance.json>] [--validate] [--schema <schema-id>] [--guance-promql-compatible] [--keep-grafana-meta]'
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function validateDashboardFile(filePath, schemaId) {
|
|
164
|
+
const projectRoot = findProjectRoot()
|
|
165
|
+
const schemasDirectory = path.join(projectRoot, 'schemas')
|
|
166
|
+
const ajv = new Ajv({ allErrors: true })
|
|
167
|
+
|
|
168
|
+
forEachFile(schemasDirectory, (schemaPath) => {
|
|
169
|
+
if (!schemaPath.endsWith('.json')) return
|
|
170
|
+
ajv.addSchema(readJson(schemaPath))
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
const valid = ajv.validate(schemaId, readJson(filePath))
|
|
174
|
+
if (valid) {
|
|
175
|
+
console.log(`Validated ${filePath} against ${schemaId}`)
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
console.error(`Validation failed for ${filePath} against ${schemaId}:`)
|
|
180
|
+
for (const error of ajv.errors || []) {
|
|
181
|
+
const instancePath = error.instancePath || '/'
|
|
182
|
+
console.error(`- ${instancePath} ${error.message}`)
|
|
183
|
+
}
|
|
184
|
+
process.exit(1)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function findProjectRoot() {
|
|
188
|
+
let currentDirectory = process.cwd()
|
|
189
|
+
|
|
190
|
+
while (true) {
|
|
191
|
+
if (fs.existsSync(path.join(currentDirectory, 'schemas', 'dashboard-schema.json'))) {
|
|
192
|
+
return currentDirectory
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const parentDirectory = path.dirname(currentDirectory)
|
|
196
|
+
if (parentDirectory === currentDirectory) {
|
|
197
|
+
throw new Error('Could not locate project root containing schemas/dashboard-schema.json')
|
|
198
|
+
}
|
|
199
|
+
currentDirectory = parentDirectory
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function convertDashboard(grafanaDashboard, options = {}) {
|
|
204
|
+
const variableNames = new Set((grafanaDashboard.templating?.list || []).map((item) => item?.name).filter(Boolean))
|
|
205
|
+
const state = {
|
|
206
|
+
groups: [],
|
|
207
|
+
groupUnfoldStatus: {},
|
|
208
|
+
charts: [],
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const sortedPanels = sortPanels(grafanaDashboard.panels || [])
|
|
212
|
+
collectPanels(sortedPanels, state, null, variableNames, options)
|
|
213
|
+
|
|
214
|
+
return pruneEmpty({
|
|
215
|
+
title: grafanaDashboard.title || '',
|
|
216
|
+
description: grafanaDashboard.description || undefined,
|
|
217
|
+
tags: grafanaDashboard.tags || undefined,
|
|
218
|
+
uid: grafanaDashboard.uid || undefined,
|
|
219
|
+
dashboardExtend: {
|
|
220
|
+
groupUnfoldStatus: state.groupUnfoldStatus,
|
|
221
|
+
},
|
|
222
|
+
main: {
|
|
223
|
+
vars: convertVariables(grafanaDashboard.templating?.list || [], variableNames),
|
|
224
|
+
charts: state.charts,
|
|
225
|
+
groups: state.groups,
|
|
226
|
+
type: 'template',
|
|
227
|
+
},
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function collectPanels(panels, state, inheritedGroup = null, variableNames = new Set(), options = {}) {
|
|
232
|
+
let activeRow = inheritedGroup
|
|
233
|
+
let openRowPanel = null
|
|
234
|
+
|
|
235
|
+
for (const panel of panels) {
|
|
236
|
+
if (panel.type === 'row') {
|
|
237
|
+
const rowName = panel.title || ''
|
|
238
|
+
if (rowName) {
|
|
239
|
+
state.groups.push({ name: rowName })
|
|
240
|
+
state.groupUnfoldStatus[rowName] = !panel.collapsed
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (panel.collapsed) {
|
|
244
|
+
collectPanels(sortPanels(panel.panels || []), state, rowName || null, variableNames, options)
|
|
245
|
+
activeRow = inheritedGroup
|
|
246
|
+
openRowPanel = null
|
|
247
|
+
} else {
|
|
248
|
+
activeRow = rowName || null
|
|
249
|
+
openRowPanel = panel
|
|
250
|
+
}
|
|
251
|
+
continue
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const chart = convertPanel(panel, activeRow, openRowPanel, variableNames, options)
|
|
255
|
+
if (chart) {
|
|
256
|
+
state.charts.push(chart)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function sortPanels(panels) {
|
|
262
|
+
return [...panels].sort((left, right) => {
|
|
263
|
+
const leftPos = left.gridPos || {}
|
|
264
|
+
const rightPos = right.gridPos || {}
|
|
265
|
+
const leftY = leftPos.y ?? Number.MAX_SAFE_INTEGER
|
|
266
|
+
const rightY = rightPos.y ?? Number.MAX_SAFE_INTEGER
|
|
267
|
+
if (leftY !== rightY) return leftY - rightY
|
|
268
|
+
const leftX = leftPos.x ?? Number.MAX_SAFE_INTEGER
|
|
269
|
+
const rightX = rightPos.x ?? Number.MAX_SAFE_INTEGER
|
|
270
|
+
if (leftX !== rightX) return leftX - rightX
|
|
271
|
+
return (left.id || 0) - (right.id || 0)
|
|
272
|
+
})
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function convertVariables(variables, variableNames) {
|
|
276
|
+
return variables
|
|
277
|
+
.map((variable, index) => convertVariable(variable, index, variableNames))
|
|
278
|
+
.filter(Boolean)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function convertVariable(variable, index, variableNames) {
|
|
282
|
+
const variableType = String(variable.type || '')
|
|
283
|
+
const current = variable.current || {}
|
|
284
|
+
const currentText = stringifyCurrent(current.text)
|
|
285
|
+
const currentValue = stringifyCurrent(current.value)
|
|
286
|
+
const includeAll = Boolean(variable.includeAll)
|
|
287
|
+
const defaultVal = {
|
|
288
|
+
label: normalizeAllValue(currentText, variable.allValue),
|
|
289
|
+
value: normalizeAllValue(currentValue, variable.allValue, true),
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const base = {
|
|
293
|
+
name: variable.label || variable.name || '',
|
|
294
|
+
seq: index,
|
|
295
|
+
code: variable.name || `var_${index}`,
|
|
296
|
+
hide: variable.hide && variable.hide !== 0 ? 1 : 0,
|
|
297
|
+
multiple: Boolean(variable.multi),
|
|
298
|
+
includeStar: includeAll,
|
|
299
|
+
valueSort: 'desc',
|
|
300
|
+
extend: pruneEmpty({
|
|
301
|
+
originalType: variableType,
|
|
302
|
+
description: variable.description || undefined,
|
|
303
|
+
starMeaning: includeAll ? '*' : undefined,
|
|
304
|
+
options: Array.isArray(variable.options) ? variable.options : undefined,
|
|
305
|
+
refresh: variable.refresh,
|
|
306
|
+
skipUrlSync: variable.skipUrlSync,
|
|
307
|
+
sort: variable.sort,
|
|
308
|
+
}),
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (variableType === 'textbox' || variableType === 'constant' || variableType === 'interval') {
|
|
312
|
+
return pruneEmpty({
|
|
313
|
+
...base,
|
|
314
|
+
datasource: 'custom',
|
|
315
|
+
type: 'CUSTOM_LIST',
|
|
316
|
+
multiple: false,
|
|
317
|
+
includeStar: false,
|
|
318
|
+
definition: {
|
|
319
|
+
value: variable.query || currentValue || '',
|
|
320
|
+
defaultVal,
|
|
321
|
+
},
|
|
322
|
+
})
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (variableType === 'custom' || variableType === 'datasource') {
|
|
326
|
+
return pruneEmpty({
|
|
327
|
+
...base,
|
|
328
|
+
datasource: 'custom',
|
|
329
|
+
type: 'CUSTOM_LIST',
|
|
330
|
+
definition: {
|
|
331
|
+
value: variable.query || extractCustomOptions(variable.options || []),
|
|
332
|
+
defaultVal,
|
|
333
|
+
},
|
|
334
|
+
})
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (variableType === 'query') {
|
|
338
|
+
const queryString = extractVariableQuery(variable)
|
|
339
|
+
const queryKind = inferVariableQueryType(variable, queryString)
|
|
340
|
+
return pruneEmpty({
|
|
341
|
+
...base,
|
|
342
|
+
datasource: queryKind === 'FIELD' ? 'object' : 'dataflux',
|
|
343
|
+
type: queryKind,
|
|
344
|
+
definition: {
|
|
345
|
+
tag: '',
|
|
346
|
+
field: queryKind === 'FIELD' ? extractFieldName(queryString) : '',
|
|
347
|
+
value: replaceVariables(queryString || '', variableNames),
|
|
348
|
+
metric: extractMetricName(queryString, variableNames),
|
|
349
|
+
object: queryKind === 'FIELD' ? 'HOST' : '',
|
|
350
|
+
defaultVal,
|
|
351
|
+
},
|
|
352
|
+
})
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return pruneEmpty({
|
|
356
|
+
...base,
|
|
357
|
+
datasource: 'custom',
|
|
358
|
+
type: 'CUSTOM_LIST',
|
|
359
|
+
multiple: false,
|
|
360
|
+
includeStar: false,
|
|
361
|
+
definition: {
|
|
362
|
+
value: variable.query || currentValue || '',
|
|
363
|
+
defaultVal,
|
|
364
|
+
},
|
|
365
|
+
})
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function convertPanel(panel, groupName, rowPanel, variableNames, options) {
|
|
369
|
+
const chartType = inferChartType(panel)
|
|
370
|
+
if (!chartType || !panel.gridPos) {
|
|
371
|
+
return null
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const queries = buildQueries(panel, chartType, variableNames, options)
|
|
375
|
+
const settings = buildSettings(panel, chartType, queries, variableNames)
|
|
376
|
+
const links = extractPanelLinks(panel, variableNames)
|
|
377
|
+
const group = groupName ?? null
|
|
378
|
+
const position = buildPosition(panel, rowPanel)
|
|
379
|
+
|
|
380
|
+
return pruneEmpty({
|
|
381
|
+
name: replaceVariables(panel.title || '', variableNames),
|
|
382
|
+
type: chartType,
|
|
383
|
+
group: { name: group },
|
|
384
|
+
pos: position,
|
|
385
|
+
extend: {
|
|
386
|
+
settings,
|
|
387
|
+
links: links.length ? links : undefined,
|
|
388
|
+
sourcePanelType: options.keepGrafanaMeta ? panel.type : undefined,
|
|
389
|
+
sourcePanelId: options.keepGrafanaMeta ? panel.id : undefined,
|
|
390
|
+
pluginVersion: options.keepGrafanaMeta ? panel.pluginVersion || undefined : undefined,
|
|
391
|
+
grafana: options.keepGrafanaMeta
|
|
392
|
+
? pruneEmpty({
|
|
393
|
+
fieldConfig: panel.fieldConfig,
|
|
394
|
+
options: panel.options,
|
|
395
|
+
transformations: panel.transformations,
|
|
396
|
+
transparent: panel.transparent,
|
|
397
|
+
repeat: panel.repeat,
|
|
398
|
+
datasource: panel.datasource,
|
|
399
|
+
})
|
|
400
|
+
: undefined,
|
|
401
|
+
},
|
|
402
|
+
queries,
|
|
403
|
+
})
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function buildPosition(panel, rowPanel) {
|
|
407
|
+
const gridPos = panel.gridPos || {}
|
|
408
|
+
const rowOffset = rowPanel?.gridPos?.y ?? 0
|
|
409
|
+
const rawY = typeof gridPos.y === 'number' ? gridPos.y - rowOffset : 0
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
x: numberOr(gridPos.x, 0),
|
|
413
|
+
y: round1(rawY * 1.9 + 0.5),
|
|
414
|
+
w: numberOr(gridPos.w, 12),
|
|
415
|
+
h: round1(numberOr(gridPos.h, 8) * 1.9 + 0.1),
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function buildQueries(panel, chartType, variableNames, options = {}) {
|
|
420
|
+
const queries = []
|
|
421
|
+
const targets = Array.isArray(panel.targets) ? panel.targets : []
|
|
422
|
+
|
|
423
|
+
for (let index = 0; index < targets.length; index++) {
|
|
424
|
+
const target = targets[index]
|
|
425
|
+
const queryText = extractTargetQuery(target)
|
|
426
|
+
if (!queryText) continue
|
|
427
|
+
|
|
428
|
+
const qtype = inferQueryLanguage(target, queryText)
|
|
429
|
+
const normalizedQueryText = normalizeTargetQuery(queryText, qtype, options)
|
|
430
|
+
queries.push(
|
|
431
|
+
pruneEmpty({
|
|
432
|
+
name: target.legendFormat || target.alias || '',
|
|
433
|
+
type: chartType,
|
|
434
|
+
qtype,
|
|
435
|
+
datasource: 'dataflux',
|
|
436
|
+
disabled: Boolean(target.hide),
|
|
437
|
+
query: {
|
|
438
|
+
q: replaceVariables(normalizedQueryText, variableNames),
|
|
439
|
+
code: normalizeQueryCode(target.refId, index),
|
|
440
|
+
type: qtype,
|
|
441
|
+
promqlCode: qtype === 'promql' ? index + 1 : undefined,
|
|
442
|
+
alias: target.legendFormat || target.alias || '',
|
|
443
|
+
field: target.field || undefined,
|
|
444
|
+
},
|
|
445
|
+
extend: pruneEmpty({
|
|
446
|
+
refId: target.refId || undefined,
|
|
447
|
+
datasource: target.datasource || undefined,
|
|
448
|
+
editorMode: target.editorMode || undefined,
|
|
449
|
+
queryMode: target.queryMode || undefined,
|
|
450
|
+
}),
|
|
451
|
+
})
|
|
452
|
+
)
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (chartType === 'text' && panel.options?.content) {
|
|
456
|
+
queries.push({
|
|
457
|
+
query: {
|
|
458
|
+
content: replaceVariables(panel.options.content, variableNames),
|
|
459
|
+
},
|
|
460
|
+
})
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return queries
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function buildSettings(panel, chartType, queries, variableNames) {
|
|
467
|
+
const defaults = panel.fieldConfig?.defaults || {}
|
|
468
|
+
const custom = defaults.custom || {}
|
|
469
|
+
const options = panel.options || {}
|
|
470
|
+
const legend = options.legend || panel.legend || {}
|
|
471
|
+
const transformationInfo = parseTransformations(panel.transformations || [])
|
|
472
|
+
const aliases = buildAliases(queries)
|
|
473
|
+
const tableColumns = buildTableColumns(panel.fieldConfig, transformationInfo.organize, variableNames)
|
|
474
|
+
const fieldOverrides = buildFieldOverrides(panel.fieldConfig, variableNames)
|
|
475
|
+
const legacyGauge = panel.gauge || {}
|
|
476
|
+
const valueMappings = buildLegacyValueMappings(panel.valueMaps)
|
|
477
|
+
const rangeMappings = buildLegacyRangeMappings(panel.rangeMaps)
|
|
478
|
+
const mappingItems = [...buildMappings(defaults.mappings), ...valueMappings, ...rangeMappings]
|
|
479
|
+
const explicitUnit = firstDefined(defaults.unit, panel.format, panel.yaxes?.[0]?.format)
|
|
480
|
+
const unit = explicitUnit || inferUnitFromQueries(queries, chartType)
|
|
481
|
+
const precision = firstDefinedNumber(defaults.decimals, panel.decimals)
|
|
482
|
+
const min = firstDefinedNumber(defaults.min, legacyGauge.minValue, panel.yaxes?.[0]?.min)
|
|
483
|
+
const max = firstDefinedNumber(defaults.max, legacyGauge.maxValue, panel.yaxes?.[0]?.max)
|
|
484
|
+
const lineWidth = firstDefinedNumber(custom.lineWidth, panel.linewidth)
|
|
485
|
+
const fillOpacity = firstDefinedNumber(custom.fillOpacity, normalizeLegacyFill(panel.fill))
|
|
486
|
+
const reduceOptions = options.reduceOptions || {}
|
|
487
|
+
const legendValues = mapLegendCalcs(legend.calcs?.length ? legend.calcs : extractLegacyLegendCalcs(legend))
|
|
488
|
+
const connectNulls = normalizeConnectNulls(firstDefined(custom.spanNulls, panel.nullPointMode))
|
|
489
|
+
const pointMode = firstDefined(custom.showPoints, panel.points === true ? 'always' : panel.points === false ? 'never' : undefined)
|
|
490
|
+
const graphMode = options.graphMode || undefined
|
|
491
|
+
const legacyTextMode = panel.valueName || undefined
|
|
492
|
+
const workspaceInfo = extractWorkspaceInfo(panel.targets || [])
|
|
493
|
+
const tooltip = options.tooltip || panel.tooltip || {}
|
|
494
|
+
const statText = options.text || {}
|
|
495
|
+
const textInfo = chartType === 'text' ? analyzeTextPanel(options.content, options.mode) : null
|
|
496
|
+
const tableFooter = options.footer || {}
|
|
497
|
+
const tableSortBy = Array.isArray(options.sortBy) ? options.sortBy : []
|
|
498
|
+
const tableCustom = chartType === 'table' ? custom : {}
|
|
499
|
+
const compareInfo = inferCompareSettings(queries, chartType)
|
|
500
|
+
const sortInfo = inferSortSettings(chartType, legend, tableSortBy)
|
|
501
|
+
const customUnits = buildCustomUnits(panel.fieldConfig)
|
|
502
|
+
const customColors = buildCustomColors(panel.fieldConfig)
|
|
503
|
+
const colorMappings = buildColorMappings(panel.fieldConfig, chartType)
|
|
504
|
+
const valColorMappings = buildValColorMappings(panel.fieldConfig, transformationInfo.organize)
|
|
505
|
+
const effectiveUnitType = customUnits.length ? 'custom' : unit ? 'global' : undefined
|
|
506
|
+
const slimit = inferSeriesLimit(queries, options, chartType)
|
|
507
|
+
const settings = {
|
|
508
|
+
showTitle: true,
|
|
509
|
+
titleDesc: panel.description || '',
|
|
510
|
+
isSampling: true,
|
|
511
|
+
changeWorkspace: workspaceInfo.changeWorkspace,
|
|
512
|
+
workspaceUUID: workspaceInfo.workspaceUUID,
|
|
513
|
+
workspaceName: workspaceInfo.workspaceName,
|
|
514
|
+
showFieldMapping: false,
|
|
515
|
+
openThousandsSeparator: true,
|
|
516
|
+
precision: typeof precision === 'number' ? String(precision) : '2',
|
|
517
|
+
timeInterval: normalizeTimeInterval(firstDefined(panel.interval, panel.targets?.find((item) => item.interval)?.interval, 'auto')),
|
|
518
|
+
fixedTime: panel.timeFrom || '',
|
|
519
|
+
maxPointCount: panel.maxDataPoints ?? null,
|
|
520
|
+
showLegend: legend.showLegend,
|
|
521
|
+
legendPostion: mapLegendPlacement(legend.placement),
|
|
522
|
+
legendValues,
|
|
523
|
+
showLegend: firstDefined(legend.showLegend, legend.show),
|
|
524
|
+
showLine: chartType === 'sequence' ? inferShowLine(panel, custom) : undefined,
|
|
525
|
+
lineType: mapLineInterpolation(custom.lineInterpolation),
|
|
526
|
+
connectNulls,
|
|
527
|
+
openStack: inferOpenStack(panel, custom),
|
|
528
|
+
stackType: mapStackType(firstDefined(custom.stacking?.mode, panel.stack ? 'normal' : 'none')),
|
|
529
|
+
chartType: inferDisplayChartType(panel, chartType),
|
|
530
|
+
isTimeInterval: chartType === 'sequence' || chartType === 'bar' || chartType === 'heatmap' || chartType === 'histogram',
|
|
531
|
+
xAxisShowType: chartType === 'sequence' || chartType === 'bar' ? 'time' : undefined,
|
|
532
|
+
unitType: effectiveUnitType,
|
|
533
|
+
globalUnit: customUnits.length ? undefined : mapUnit(unit),
|
|
534
|
+
units: customUnits.length ? customUnits : undefined,
|
|
535
|
+
colors: customColors.length ? customColors : undefined,
|
|
536
|
+
colorMappings: colorMappings.length ? colorMappings : undefined,
|
|
537
|
+
levels: buildLevels(defaults.thresholds),
|
|
538
|
+
slimit,
|
|
539
|
+
mappings: mappingItems,
|
|
540
|
+
alias: aliases,
|
|
541
|
+
min,
|
|
542
|
+
max,
|
|
543
|
+
showPercent: Array.isArray(options.displayLabels) ? options.displayLabels.includes('percent') : undefined,
|
|
544
|
+
showLabel: chartType === 'pie' ? Array.isArray(options.displayLabels) && options.displayLabels.length > 0 : undefined,
|
|
545
|
+
showLabelValue: Array.isArray(options.displayLabels)
|
|
546
|
+
? options.displayLabels.includes('value') || options.displayLabels.includes('name')
|
|
547
|
+
: undefined,
|
|
548
|
+
direction: options.orientation || undefined,
|
|
549
|
+
queryMode: chartType === 'table' ? 'toGroupColumn' : undefined,
|
|
550
|
+
showTableHead: chartType === 'table' ? options.showHeader !== false : undefined,
|
|
551
|
+
pageEnable: chartType === 'table' ? false : undefined,
|
|
552
|
+
pageSize: chartType === 'table' ? 20 : undefined,
|
|
553
|
+
showColumns: chartType === 'table' ? tableColumns.map((column) => column.title || column.field) : undefined,
|
|
554
|
+
valMappings: chartType === 'table' ? buildTableMappings(panel.fieldConfig, transformationInfo.organize) : undefined,
|
|
555
|
+
valColorMappings: chartType === 'table' && valColorMappings.length ? valColorMappings : undefined,
|
|
556
|
+
legendValueOpen: Array.isArray(legend.values) ? legend.values.includes('value') : undefined,
|
|
557
|
+
legendValuePercentOpen: Array.isArray(legend.values) ? legend.values.includes('percent') : undefined,
|
|
558
|
+
showTopSize: chartType === 'toplist' ? true : undefined,
|
|
559
|
+
topSize: chartType === 'toplist' ? extractReduceLimit(options) : undefined,
|
|
560
|
+
scientificNotation: unit === 'short' ? true : undefined,
|
|
561
|
+
mainMeasurementQueryCode: queries[0]?.query?.code || undefined,
|
|
562
|
+
mainMeasurementLimit: chartType === 'pie' ? extractReduceLimit(options) : undefined,
|
|
563
|
+
color: defaults.color?.fixedColor || undefined,
|
|
564
|
+
fontColor: options.colorMode === 'value' ? defaults.color?.fixedColor : undefined,
|
|
565
|
+
bgColor: options.colorMode === 'background' ? defaults.color?.fixedColor : undefined,
|
|
566
|
+
sequenceChartType: chartType === 'singlestat' && graphMode ? inferSequenceChartType(panel, graphMode) : undefined,
|
|
567
|
+
showLineAxis: chartType === 'singlestat' ? graphMode !== 'none' : undefined,
|
|
568
|
+
repeatChartVariable: typeof panel.repeat === 'string' && panel.repeat ? panel.repeat : undefined,
|
|
569
|
+
repeatChartRowLimit: typeof panel.maxPerRow === 'number' ? panel.maxPerRow : undefined,
|
|
570
|
+
compares: compareInfo.compares,
|
|
571
|
+
compareType: compareInfo.compareType,
|
|
572
|
+
openCompare: compareInfo.openCompare,
|
|
573
|
+
compareChartType: compareInfo.compareChartType,
|
|
574
|
+
mainMeasurementSort: sortInfo.mainMeasurementSort,
|
|
575
|
+
sorderByOrder: sortInfo.sorderByOrder,
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const links = extractPanelLinks(panel)
|
|
579
|
+
if (links.length) {
|
|
580
|
+
settings.queryCodes = queries.map((query) => query.query?.code).filter(Boolean)
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
settings.extend = pruneEmpty({
|
|
584
|
+
appearance: pruneEmpty({
|
|
585
|
+
lineWidth,
|
|
586
|
+
fillOpacity,
|
|
587
|
+
gradientMode: custom.gradientMode || undefined,
|
|
588
|
+
pointMode,
|
|
589
|
+
pointSize: firstDefinedNumber(custom.pointSize, panel.pointradius),
|
|
590
|
+
axisPlacement: custom.axisPlacement || undefined,
|
|
591
|
+
axisLabel: custom.axisLabel || undefined,
|
|
592
|
+
axisColorMode: custom.axisColorMode || undefined,
|
|
593
|
+
axisCenteredZero: typeof custom.axisCenteredZero === 'boolean' ? custom.axisCenteredZero : undefined,
|
|
594
|
+
axisSoftMin: numberOrUndefined(custom.axisSoftMin),
|
|
595
|
+
axisSoftMax: numberOrUndefined(custom.axisSoftMax),
|
|
596
|
+
barAlignment: numberOrUndefined(custom.barAlignment),
|
|
597
|
+
scaleDistribution: custom.scaleDistribution || undefined,
|
|
598
|
+
drawStyle: custom.drawStyle || undefined,
|
|
599
|
+
lineStyle: custom.lineStyle || undefined,
|
|
600
|
+
spanNulls: custom.spanNulls,
|
|
601
|
+
stackingGroup: custom.stacking?.group || undefined,
|
|
602
|
+
graphMode,
|
|
603
|
+
colorMode: options.colorMode || undefined,
|
|
604
|
+
fieldColorMode: defaults.color?.mode || undefined,
|
|
605
|
+
fixedColor: defaults.color?.fixedColor || undefined,
|
|
606
|
+
thresholdsMode: defaults.thresholds?.mode || undefined,
|
|
607
|
+
thresholdsStyleMode: custom.thresholdsStyle?.mode || undefined,
|
|
608
|
+
textMode: options.textMode || legacyTextMode,
|
|
609
|
+
reduceCalcs: Array.isArray(reduceOptions.calcs) ? reduceOptions.calcs : undefined,
|
|
610
|
+
reduceFields: reduceOptions.fields || undefined,
|
|
611
|
+
reduceValues: typeof reduceOptions.values === 'boolean' ? reduceOptions.values : undefined,
|
|
612
|
+
pieType: options.pieType || undefined,
|
|
613
|
+
gaugeMode: chartType === 'gauge' || panel.type === 'singlestat' ? inferGaugeMode(panel, options, legacyGauge) : undefined,
|
|
614
|
+
thresholdMarkers: typeof legacyGauge.thresholdMarkers === 'boolean' ? legacyGauge.thresholdMarkers : undefined,
|
|
615
|
+
thresholdLabels: typeof legacyGauge.thresholdLabels === 'boolean' ? legacyGauge.thresholdLabels : undefined,
|
|
616
|
+
hideFrom: custom.hideFrom || undefined,
|
|
617
|
+
justifyMode: options.justifyMode || undefined,
|
|
618
|
+
titleSize: typeof statText.titleSize === 'number' ? statText.titleSize : undefined,
|
|
619
|
+
valueSize: typeof statText.valueSize === 'number' ? statText.valueSize : undefined,
|
|
620
|
+
}),
|
|
621
|
+
legend: pruneEmpty({
|
|
622
|
+
displayMode: legend.displayMode || undefined,
|
|
623
|
+
sortBy: legend.sortBy || legend.sort || undefined,
|
|
624
|
+
sortDesc: typeof legend.sortDesc === 'boolean' ? legend.sortDesc : undefined,
|
|
625
|
+
width: firstDefinedNumber(legend.width, legend.sideWidth),
|
|
626
|
+
}),
|
|
627
|
+
tooltip: pruneEmpty({
|
|
628
|
+
mode: tooltip.mode || tooltip.sharedMode || undefined,
|
|
629
|
+
sort: tooltip.sort || tooltip.value_type || undefined,
|
|
630
|
+
}),
|
|
631
|
+
table: chartType === 'table'
|
|
632
|
+
? pruneEmpty({
|
|
633
|
+
align: tableCustom.align || undefined,
|
|
634
|
+
displayMode: tableCustom.displayMode || undefined,
|
|
635
|
+
sortBy: tableSortBy,
|
|
636
|
+
footer: pruneEmpty({
|
|
637
|
+
fields: tableFooter.fields || undefined,
|
|
638
|
+
reducer: Array.isArray(tableFooter.reducer) ? tableFooter.reducer : undefined,
|
|
639
|
+
show: typeof tableFooter.show === 'boolean' ? tableFooter.show : undefined,
|
|
640
|
+
}),
|
|
641
|
+
})
|
|
642
|
+
: undefined,
|
|
643
|
+
text: textInfo || undefined,
|
|
644
|
+
tableColumns: chartType === 'table' && tableColumns.length ? tableColumns : undefined,
|
|
645
|
+
fieldOverrides: fieldOverrides.length ? fieldOverrides : undefined,
|
|
646
|
+
transformations: transformationInfo.normalized.length ? transformationInfo.normalized : undefined,
|
|
647
|
+
fieldFilterPattern: transformationInfo.fieldFilterPattern || undefined,
|
|
648
|
+
valueFilters: transformationInfo.valueFilters.length ? transformationInfo.valueFilters : undefined,
|
|
649
|
+
layout: pruneEmpty({
|
|
650
|
+
repeatDirection: panel.repeatDirection || undefined,
|
|
651
|
+
}),
|
|
652
|
+
})
|
|
653
|
+
|
|
654
|
+
return pruneEmpty(settings)
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function buildLevels(thresholds) {
|
|
658
|
+
const steps = Array.isArray(thresholds?.steps) ? thresholds.steps : []
|
|
659
|
+
return steps
|
|
660
|
+
.filter((step) => typeof step.value === 'number' || typeof step.color === 'string')
|
|
661
|
+
.map((step, index) => ({
|
|
662
|
+
title: `Level ${index + 1}`,
|
|
663
|
+
value: typeof step.value === 'number' ? step.value : 0,
|
|
664
|
+
bgColor: normalizeColor(step.color),
|
|
665
|
+
}))
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function buildMappings(mappings) {
|
|
669
|
+
if (!Array.isArray(mappings)) return []
|
|
670
|
+
const result = []
|
|
671
|
+
|
|
672
|
+
for (const mapping of mappings) {
|
|
673
|
+
if (mapping.type === 'value' && mapping.options && typeof mapping.options === 'object') {
|
|
674
|
+
for (const [rawValue, item] of Object.entries(mapping.options)) {
|
|
675
|
+
result.push({
|
|
676
|
+
originalVal: [rawValue],
|
|
677
|
+
operation: '=',
|
|
678
|
+
mappingVal: item.text || rawValue,
|
|
679
|
+
})
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (mapping.type === 'range' && mapping.options) {
|
|
684
|
+
const from = mapping.options.from
|
|
685
|
+
const to = mapping.options.to
|
|
686
|
+
result.push({
|
|
687
|
+
originalVal: [String(from ?? ''), String(to ?? '')],
|
|
688
|
+
operation: 'between',
|
|
689
|
+
mappingVal: mapping.options.result?.text || '',
|
|
690
|
+
})
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
return result
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function buildTableMappings(fieldConfig, organize) {
|
|
698
|
+
const overrideMappings = []
|
|
699
|
+
const overrides = Array.isArray(fieldConfig?.overrides) ? fieldConfig.overrides : []
|
|
700
|
+
const organizeMaps = createOrganizeMaps(organize)
|
|
701
|
+
|
|
702
|
+
for (const override of overrides) {
|
|
703
|
+
const field = override.matcher?.options
|
|
704
|
+
if (!field) continue
|
|
705
|
+
const displayField = resolveDisplayFieldName(field, organizeMaps)
|
|
706
|
+
const properties = Array.isArray(override.properties) ? override.properties : []
|
|
707
|
+
for (const property of properties) {
|
|
708
|
+
if (property.id !== 'mappings') continue
|
|
709
|
+
const mappings = buildMappings(property.value)
|
|
710
|
+
for (const mapping of mappings) {
|
|
711
|
+
overrideMappings.push({
|
|
712
|
+
field: displayField,
|
|
713
|
+
...mapping,
|
|
714
|
+
})
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return overrideMappings
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function buildTableColumns(fieldConfig, organize, variableNames = new Set()) {
|
|
723
|
+
const columns = new Map()
|
|
724
|
+
const defaultCustom = fieldConfig?.defaults?.custom || {}
|
|
725
|
+
const overrides = Array.isArray(fieldConfig?.overrides) ? fieldConfig.overrides : []
|
|
726
|
+
const organizeMaps = createOrganizeMaps(organize)
|
|
727
|
+
const { renamedFields, excludedFields, indexedFields } = organizeMaps
|
|
728
|
+
|
|
729
|
+
for (const override of overrides) {
|
|
730
|
+
const rawField = override.matcher?.options
|
|
731
|
+
if (!rawField) continue
|
|
732
|
+
const field = resolveRawFieldName(rawField, organizeMaps)
|
|
733
|
+
if (excludedFields[field] === true || excludedFields[rawField] === true) continue
|
|
734
|
+
const columnKey = resolveDisplayFieldName(field, organizeMaps)
|
|
735
|
+
const currentColumn = columns.get(columnKey) || {
|
|
736
|
+
field,
|
|
737
|
+
title: columnKey,
|
|
738
|
+
order: typeof indexedFields[field] === 'number' ? indexedFields[field] : undefined,
|
|
739
|
+
align: defaultCustom.align || undefined,
|
|
740
|
+
displayMode: defaultCustom.displayMode || undefined,
|
|
741
|
+
}
|
|
742
|
+
for (const property of override.properties || []) {
|
|
743
|
+
if (property.id === 'custom.width') {
|
|
744
|
+
currentColumn.width = property.value
|
|
745
|
+
}
|
|
746
|
+
if (property.id === 'custom.align') {
|
|
747
|
+
currentColumn.align = property.value
|
|
748
|
+
}
|
|
749
|
+
if (property.id === 'custom.displayMode') {
|
|
750
|
+
currentColumn.displayMode = property.value
|
|
751
|
+
}
|
|
752
|
+
if (property.id === 'links') {
|
|
753
|
+
currentColumn.links = Array.isArray(property.value)
|
|
754
|
+
? property.value.map((link) => normalizeGuanceLinkItem(link, variableNames))
|
|
755
|
+
: undefined
|
|
756
|
+
}
|
|
757
|
+
if (property.id === 'mappings') {
|
|
758
|
+
currentColumn.mappings = buildMappings(property.value)
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
columns.set(columnKey, pruneEmpty(currentColumn))
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if (organize) {
|
|
765
|
+
for (const [field, order] of Object.entries(indexedFields)) {
|
|
766
|
+
if (excludedFields[field] === true) continue
|
|
767
|
+
const columnKey = resolveDisplayFieldName(field, organizeMaps)
|
|
768
|
+
if (columns.has(columnKey)) {
|
|
769
|
+
const currentColumn = columns.get(columnKey)
|
|
770
|
+
currentColumn.field = field
|
|
771
|
+
currentColumn.order = typeof order === 'number' ? order : currentColumn.order
|
|
772
|
+
currentColumn.title = columnKey
|
|
773
|
+
columns.set(columnKey, pruneEmpty(currentColumn))
|
|
774
|
+
continue
|
|
775
|
+
}
|
|
776
|
+
columns.set(
|
|
777
|
+
columnKey,
|
|
778
|
+
pruneEmpty({
|
|
779
|
+
field,
|
|
780
|
+
title: columnKey,
|
|
781
|
+
order: typeof order === 'number' ? order : undefined,
|
|
782
|
+
})
|
|
783
|
+
)
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
return finalizeTableColumns([...columns.values()])
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function buildAliases(queries) {
|
|
791
|
+
return queries
|
|
792
|
+
.filter((query) => query.query?.alias)
|
|
793
|
+
.map((query) => ({
|
|
794
|
+
alias: query.query.alias,
|
|
795
|
+
key: query.query.code || query.name || '',
|
|
796
|
+
name: query.query.code || query.name || '',
|
|
797
|
+
}))
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
function buildCustomUnits(fieldConfig) {
|
|
801
|
+
const overrides = Array.isArray(fieldConfig?.overrides) ? fieldConfig.overrides : []
|
|
802
|
+
const units = []
|
|
803
|
+
|
|
804
|
+
for (const override of overrides) {
|
|
805
|
+
const rawKey = String(override?.matcher?.options || '').trim()
|
|
806
|
+
if (!rawKey) continue
|
|
807
|
+
|
|
808
|
+
const unitProperty = Array.isArray(override?.properties)
|
|
809
|
+
? override.properties.find((property) => property?.id === 'unit')
|
|
810
|
+
: undefined
|
|
811
|
+
if (!unitProperty || typeof unitProperty.value !== 'string') continue
|
|
812
|
+
|
|
813
|
+
const unit = unitProperty.value
|
|
814
|
+
units.push(
|
|
815
|
+
pruneEmpty({
|
|
816
|
+
key: rawKey,
|
|
817
|
+
name: rawKey,
|
|
818
|
+
unit,
|
|
819
|
+
units: mapUnit(unit),
|
|
820
|
+
})
|
|
821
|
+
)
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
return units
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
function buildCustomColors(fieldConfig) {
|
|
828
|
+
const overrides = Array.isArray(fieldConfig?.overrides) ? fieldConfig.overrides : []
|
|
829
|
+
const colors = []
|
|
830
|
+
|
|
831
|
+
for (const override of overrides) {
|
|
832
|
+
const rawKey = String(override?.matcher?.options || '').trim()
|
|
833
|
+
if (!rawKey) continue
|
|
834
|
+
|
|
835
|
+
const colorProperty = Array.isArray(override?.properties)
|
|
836
|
+
? override.properties.find((property) => property?.id === 'color')
|
|
837
|
+
: undefined
|
|
838
|
+
const fixedColor = colorProperty?.value?.fixedColor
|
|
839
|
+
const colorMode = colorProperty?.value?.mode
|
|
840
|
+
if (typeof fixedColor !== 'string' || !fixedColor) continue
|
|
841
|
+
if (colorMode && colorMode !== 'fixed') continue
|
|
842
|
+
|
|
843
|
+
colors.push(
|
|
844
|
+
pruneEmpty({
|
|
845
|
+
key: rawKey,
|
|
846
|
+
name: rawKey,
|
|
847
|
+
color: normalizeColor(fixedColor),
|
|
848
|
+
})
|
|
849
|
+
)
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
return colors
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
function buildColorMappings(fieldConfig, chartType) {
|
|
856
|
+
if (chartType !== 'toplist') return []
|
|
857
|
+
|
|
858
|
+
const steps = Array.isArray(fieldConfig?.defaults?.thresholds?.steps) ? fieldConfig.defaults.thresholds.steps : []
|
|
859
|
+
if (steps.length === 0) return []
|
|
860
|
+
|
|
861
|
+
const mappings = []
|
|
862
|
+
for (let index = 0; index < steps.length; index++) {
|
|
863
|
+
const current = steps[index]
|
|
864
|
+
const next = steps[index + 1]
|
|
865
|
+
const bgColor = normalizeColor(current?.color)
|
|
866
|
+
const start = current?.value
|
|
867
|
+
const end = next?.value
|
|
868
|
+
|
|
869
|
+
if (typeof start === 'number' && typeof end === 'number') {
|
|
870
|
+
mappings.push({
|
|
871
|
+
value: [start, end],
|
|
872
|
+
bgColor,
|
|
873
|
+
operation: 'between',
|
|
874
|
+
})
|
|
875
|
+
continue
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
if (typeof start === 'number') {
|
|
879
|
+
mappings.push({
|
|
880
|
+
value: [start],
|
|
881
|
+
bgColor,
|
|
882
|
+
operation: '>=',
|
|
883
|
+
})
|
|
884
|
+
continue
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
if (typeof end === 'number') {
|
|
888
|
+
mappings.push({
|
|
889
|
+
value: [end],
|
|
890
|
+
bgColor,
|
|
891
|
+
operation: '<',
|
|
892
|
+
})
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
return mappings
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
function buildValColorMappings(fieldConfig, organize) {
|
|
900
|
+
const overrides = Array.isArray(fieldConfig?.overrides) ? fieldConfig.overrides : []
|
|
901
|
+
const organizeMaps = createOrganizeMaps(organize)
|
|
902
|
+
const mappings = []
|
|
903
|
+
|
|
904
|
+
for (const override of overrides) {
|
|
905
|
+
const rawField = override?.matcher?.options
|
|
906
|
+
if (!rawField) continue
|
|
907
|
+
|
|
908
|
+
const field = resolveDisplayFieldName(resolveRawFieldName(rawField, organizeMaps), organizeMaps)
|
|
909
|
+
const properties = Array.isArray(override?.properties) ? override.properties : []
|
|
910
|
+
const mappingProperty = properties.find((property) => property?.id === 'mappings')
|
|
911
|
+
if (!mappingProperty) continue
|
|
912
|
+
|
|
913
|
+
const colorProperty = properties.find((property) => property?.id === 'color')
|
|
914
|
+
const fixedColor = typeof colorProperty?.value?.fixedColor === 'string' ? normalizeColor(colorProperty.value.fixedColor) : ''
|
|
915
|
+
const tableMappings = buildMappings(mappingProperty.value)
|
|
916
|
+
|
|
917
|
+
for (const mapping of tableMappings) {
|
|
918
|
+
mappings.push(
|
|
919
|
+
pruneEmpty({
|
|
920
|
+
field,
|
|
921
|
+
value: mapping.originalVal,
|
|
922
|
+
bgColor: '',
|
|
923
|
+
fontColor: fixedColor,
|
|
924
|
+
lineColor: '',
|
|
925
|
+
operation: mapping.operation,
|
|
926
|
+
})
|
|
927
|
+
)
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
return mappings
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
function buildFieldOverrides(fieldConfig, variableNames = new Set()) {
|
|
935
|
+
const overrides = Array.isArray(fieldConfig?.overrides) ? fieldConfig.overrides : []
|
|
936
|
+
const normalized = []
|
|
937
|
+
|
|
938
|
+
for (const override of overrides) {
|
|
939
|
+
const properties = Array.isArray(override.properties) ? override.properties : []
|
|
940
|
+
const normalizedProperties = properties
|
|
941
|
+
.map((property) => normalizeOverrideProperty(property, variableNames))
|
|
942
|
+
.filter(Boolean)
|
|
943
|
+
|
|
944
|
+
if (normalizedProperties.length === 0) continue
|
|
945
|
+
|
|
946
|
+
normalized.push(
|
|
947
|
+
pruneEmpty({
|
|
948
|
+
matcher: pruneEmpty({
|
|
949
|
+
id: override.matcher?.id || undefined,
|
|
950
|
+
options: override.matcher?.options,
|
|
951
|
+
}),
|
|
952
|
+
properties: normalizedProperties,
|
|
953
|
+
})
|
|
954
|
+
)
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
return normalized
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
function normalizeOverrideProperty(property, variableNames = new Set()) {
|
|
961
|
+
if (!property?.id) return undefined
|
|
962
|
+
|
|
963
|
+
if (property.id === 'links') {
|
|
964
|
+
return pruneEmpty({
|
|
965
|
+
id: property.id,
|
|
966
|
+
value: Array.isArray(property.value)
|
|
967
|
+
? property.value.map((link) => normalizeGuanceLinkItem(link, variableNames))
|
|
968
|
+
: undefined,
|
|
969
|
+
})
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
return pruneEmpty({
|
|
973
|
+
id: property.id,
|
|
974
|
+
value: property.value,
|
|
975
|
+
})
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
function analyzeTextPanel(content, mode) {
|
|
979
|
+
if (typeof content !== 'string' || !content.trim()) return undefined
|
|
980
|
+
|
|
981
|
+
const normalizedMode = typeof mode === 'string' ? mode : undefined
|
|
982
|
+
const containsScript = /<script\b/i.test(content)
|
|
983
|
+
const containsHtml = /<[a-z][\s\S]*>/i.test(content)
|
|
984
|
+
const contentKind = containsScript ? 'interactive_html' : containsHtml ? 'html' : 'markdown'
|
|
985
|
+
const actions = extractTextActions(content)
|
|
986
|
+
|
|
987
|
+
return pruneEmpty({
|
|
988
|
+
mode: normalizedMode,
|
|
989
|
+
contentKind,
|
|
990
|
+
containsScript: containsScript || undefined,
|
|
991
|
+
actions: actions.length ? actions : undefined,
|
|
992
|
+
})
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
function extractTextActions(content) {
|
|
996
|
+
const actions = []
|
|
997
|
+
const seen = new Set()
|
|
998
|
+
|
|
999
|
+
const anchorRegex = /<a\b[^>]*href=(['"])(.*?)\1[^>]*>([\s\S]*?)<\/a>/gi
|
|
1000
|
+
for (const match of content.matchAll(anchorRegex)) {
|
|
1001
|
+
const url = (match[2] || '').trim()
|
|
1002
|
+
const label = stripHtmlTags(match[3] || '').trim()
|
|
1003
|
+
pushTextAction(actions, seen, {
|
|
1004
|
+
title: label || undefined,
|
|
1005
|
+
url: url || undefined,
|
|
1006
|
+
open: url === '#' ? 'curWin' : 'newWin',
|
|
1007
|
+
type: inferGuanceLinkType({ title: label, url }),
|
|
1008
|
+
show: true,
|
|
1009
|
+
showChanged: false,
|
|
1010
|
+
})
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
const constUrlRegex = /\bconst\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*=\s*([`'"])([\s\S]*?)\2\s*;/g
|
|
1014
|
+
const constantUrls = new Map()
|
|
1015
|
+
for (const match of content.matchAll(constUrlRegex)) {
|
|
1016
|
+
constantUrls.set(match[1], match[3])
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
const windowOpenRegex = /window\.open\(\s*([A-Za-z_$][A-Za-z0-9_$]*|[`'"][\s\S]*?[`'"])\s*(?:,\s*([`'"][\s\S]*?[`'"]))?\s*\)/g
|
|
1020
|
+
for (const match of content.matchAll(windowOpenRegex)) {
|
|
1021
|
+
const rawTarget = match[1]?.trim() || ''
|
|
1022
|
+
const rawBlank = match[2]?.trim() || ''
|
|
1023
|
+
const directUrl = unwrapQuoted(rawTarget)
|
|
1024
|
+
const variableUrl = constantUrls.get(rawTarget)
|
|
1025
|
+
const url = directUrl || variableUrl
|
|
1026
|
+
pushTextAction(actions, seen, {
|
|
1027
|
+
title: inferActionTitle(content, url),
|
|
1028
|
+
url: url || undefined,
|
|
1029
|
+
open: rawBlank.includes('_blank') ? 'newWin' : 'curWin',
|
|
1030
|
+
type: inferGuanceLinkType({ title: inferActionTitle(content, url), url }),
|
|
1031
|
+
show: true,
|
|
1032
|
+
showChanged: false,
|
|
1033
|
+
})
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
return actions
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
function pushTextAction(actions, seen, action) {
|
|
1040
|
+
const normalized = pruneEmpty(action)
|
|
1041
|
+
const key = JSON.stringify(normalized)
|
|
1042
|
+
if (!key || seen.has(key) || Object.keys(normalized).length === 0) return
|
|
1043
|
+
seen.add(key)
|
|
1044
|
+
actions.push(normalized)
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
function inferActionTitle(content, url) {
|
|
1048
|
+
if (typeof url !== 'string' || !url) return undefined
|
|
1049
|
+
if (content.includes('HotCall') || url.includes('hotcall')) return 'HotCall'
|
|
1050
|
+
if (content.includes('业务大盘')) return '业务大盘'
|
|
1051
|
+
if (content.includes('拓扑图') || content.includes('traceLink')) return '跳转观测云'
|
|
1052
|
+
return undefined
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
function unwrapQuoted(value) {
|
|
1056
|
+
if (typeof value !== 'string') return ''
|
|
1057
|
+
const trimmed = value.trim()
|
|
1058
|
+
if (trimmed.length < 2) return ''
|
|
1059
|
+
const quote = trimmed[0]
|
|
1060
|
+
if ((quote === '"' || quote === "'" || quote === '`') && trimmed.at(-1) === quote) {
|
|
1061
|
+
return trimmed.slice(1, -1)
|
|
1062
|
+
}
|
|
1063
|
+
return ''
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
function stripHtmlTags(value) {
|
|
1067
|
+
return String(value).replace(/<[^>]+>/g, ' ')
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
function normalizeColumnTitle(field, renamedField) {
|
|
1071
|
+
if (typeof renamedField === 'string' && renamedField.trim()) return renamedField.trim()
|
|
1072
|
+
return field
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
function finalizeTableColumns(columns) {
|
|
1076
|
+
const mergedColumns = new Map()
|
|
1077
|
+
|
|
1078
|
+
for (const column of columns) {
|
|
1079
|
+
const key = column.title || column.field
|
|
1080
|
+
const existing = mergedColumns.get(key)
|
|
1081
|
+
if (!existing) {
|
|
1082
|
+
mergedColumns.set(key, { ...column })
|
|
1083
|
+
continue
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
mergedColumns.set(
|
|
1087
|
+
key,
|
|
1088
|
+
pruneEmpty({
|
|
1089
|
+
...existing,
|
|
1090
|
+
...column,
|
|
1091
|
+
field: existing.field || column.field,
|
|
1092
|
+
title: key,
|
|
1093
|
+
order: Math.min(numberOr(existing.order, Number.MAX_SAFE_INTEGER), numberOr(column.order, Number.MAX_SAFE_INTEGER)),
|
|
1094
|
+
width: firstDefined(existing.width, column.width),
|
|
1095
|
+
align: firstDefined(existing.align, column.align),
|
|
1096
|
+
displayMode: firstDefined(existing.displayMode, column.displayMode),
|
|
1097
|
+
links: existing.links || column.links,
|
|
1098
|
+
mappings: existing.mappings || column.mappings,
|
|
1099
|
+
})
|
|
1100
|
+
)
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
return [...mergedColumns.values()].sort(
|
|
1104
|
+
(left, right) => numberOr(left.order, Number.MAX_SAFE_INTEGER) - numberOr(right.order, Number.MAX_SAFE_INTEGER)
|
|
1105
|
+
)
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
function createOrganizeMaps(organize) {
|
|
1109
|
+
const renamedFields = organize?.renameByName || {}
|
|
1110
|
+
const excludedFields = organize?.excludeByName || {}
|
|
1111
|
+
const indexedFields = organize?.indexByName || {}
|
|
1112
|
+
const displayToRaw = {}
|
|
1113
|
+
|
|
1114
|
+
for (const [rawField, renamedField] of Object.entries(renamedFields)) {
|
|
1115
|
+
const title = normalizeColumnTitle(rawField, renamedField)
|
|
1116
|
+
if (title && title !== rawField) {
|
|
1117
|
+
displayToRaw[title] = rawField
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
return {
|
|
1122
|
+
renamedFields,
|
|
1123
|
+
excludedFields,
|
|
1124
|
+
indexedFields,
|
|
1125
|
+
displayToRaw,
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
function resolveRawFieldName(field, organizeMaps) {
|
|
1130
|
+
return organizeMaps.displayToRaw[field] || field
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
function resolveDisplayFieldName(field, organizeMaps) {
|
|
1134
|
+
return normalizeColumnTitle(field, organizeMaps.renamedFields[field])
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
function parseTransformations(transformations) {
|
|
1138
|
+
const normalized = []
|
|
1139
|
+
let organize = null
|
|
1140
|
+
let fieldFilterPattern = ''
|
|
1141
|
+
const valueFilters = []
|
|
1142
|
+
|
|
1143
|
+
for (const transformation of Array.isArray(transformations) ? transformations : []) {
|
|
1144
|
+
if (!transformation?.id) continue
|
|
1145
|
+
|
|
1146
|
+
if (transformation.id === 'organize') {
|
|
1147
|
+
organize = transformation.options || {}
|
|
1148
|
+
normalized.push(
|
|
1149
|
+
pruneEmpty({
|
|
1150
|
+
type: 'organize',
|
|
1151
|
+
renameByName: organize.renameByName,
|
|
1152
|
+
excludeByName: organize.excludeByName,
|
|
1153
|
+
indexByName: organize.indexByName,
|
|
1154
|
+
})
|
|
1155
|
+
)
|
|
1156
|
+
continue
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
if (transformation.id === 'filterFieldsByName') {
|
|
1160
|
+
fieldFilterPattern = transformation.options?.include?.pattern || transformation.options?.exclude?.pattern || ''
|
|
1161
|
+
normalized.push(
|
|
1162
|
+
pruneEmpty({
|
|
1163
|
+
type: 'filterFieldsByName',
|
|
1164
|
+
include: transformation.options?.include,
|
|
1165
|
+
exclude: transformation.options?.exclude,
|
|
1166
|
+
})
|
|
1167
|
+
)
|
|
1168
|
+
continue
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
if (transformation.id === 'filterByValue') {
|
|
1172
|
+
const filters = Array.isArray(transformation.options?.filters) ? transformation.options.filters : []
|
|
1173
|
+
valueFilters.push(
|
|
1174
|
+
pruneEmpty({
|
|
1175
|
+
match: transformation.options?.match || undefined,
|
|
1176
|
+
type: transformation.options?.type || undefined,
|
|
1177
|
+
filters,
|
|
1178
|
+
})
|
|
1179
|
+
)
|
|
1180
|
+
normalized.push(
|
|
1181
|
+
pruneEmpty({
|
|
1182
|
+
type: 'filterByValue',
|
|
1183
|
+
match: transformation.options?.match || undefined,
|
|
1184
|
+
mode: transformation.options?.type || undefined,
|
|
1185
|
+
filters,
|
|
1186
|
+
})
|
|
1187
|
+
)
|
|
1188
|
+
continue
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
normalized.push(
|
|
1192
|
+
pruneEmpty({
|
|
1193
|
+
type: transformation.id,
|
|
1194
|
+
options: transformation.options,
|
|
1195
|
+
})
|
|
1196
|
+
)
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
return {
|
|
1200
|
+
organize,
|
|
1201
|
+
fieldFilterPattern,
|
|
1202
|
+
valueFilters,
|
|
1203
|
+
normalized,
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
function extractPanelLinks(panel, variableNames) {
|
|
1208
|
+
const links = []
|
|
1209
|
+
const defaults = panel.fieldConfig?.defaults || {}
|
|
1210
|
+
const overrideLinks = Array.isArray(panel.fieldConfig?.overrides)
|
|
1211
|
+
? panel.fieldConfig.overrides.flatMap((override) =>
|
|
1212
|
+
(override.properties || [])
|
|
1213
|
+
.filter((property) => property.id === 'links')
|
|
1214
|
+
.flatMap((property) => property.value || [])
|
|
1215
|
+
)
|
|
1216
|
+
: []
|
|
1217
|
+
|
|
1218
|
+
const allLinks = [
|
|
1219
|
+
...(Array.isArray(panel.links) ? panel.links : []),
|
|
1220
|
+
...(Array.isArray(defaults.links) ? defaults.links : []),
|
|
1221
|
+
...overrideLinks,
|
|
1222
|
+
]
|
|
1223
|
+
|
|
1224
|
+
for (const link of allLinks) {
|
|
1225
|
+
if (!link || !link.url) continue
|
|
1226
|
+
links.push(normalizeGuanceLinkItem(link, variableNames))
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
return links
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
function normalizeGuanceLinkItem(link, variableNames = new Set()) {
|
|
1233
|
+
return pruneEmpty({
|
|
1234
|
+
url: replaceVariables(link.url || '', variableNames),
|
|
1235
|
+
open: Boolean(link.targetBlank) ? 'newWin' : 'curWin',
|
|
1236
|
+
show: true,
|
|
1237
|
+
type: inferGuanceLinkType(link),
|
|
1238
|
+
showChanged: false,
|
|
1239
|
+
})
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
function inferGuanceLinkType(link) {
|
|
1243
|
+
const title = String(link?.title || '').toLowerCase()
|
|
1244
|
+
const url = String(link?.url || '').toLowerCase()
|
|
1245
|
+
const combined = `${title} ${url}`
|
|
1246
|
+
|
|
1247
|
+
if (url.includes('pipeline-log')) return 'custom'
|
|
1248
|
+
if (url.includes('/tracing/')) return 'tracing'
|
|
1249
|
+
if (url.includes('/logindi/') || url.includes('/log/') || url.includes('/logging/')) return 'logging'
|
|
1250
|
+
if (url.includes('/objectadmin/docker_containers') || url.includes('/container')) return 'container'
|
|
1251
|
+
if (url.includes('/objectadmin/host_processes') || url.includes('/process')) return 'processes'
|
|
1252
|
+
if (url.includes('/scene/builtinview/detail') || url.includes('/host')) return 'host'
|
|
1253
|
+
if (title.includes('trace') || title.includes('tracing')) return 'tracing'
|
|
1254
|
+
if (title.includes('日志')) return 'logging'
|
|
1255
|
+
return 'custom'
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
function inferChartType(panel) {
|
|
1259
|
+
return PANEL_TYPE_MAP[panel.type] || null
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
function inferDisplayChartType(panel, chartType) {
|
|
1263
|
+
if (chartType === 'pie') {
|
|
1264
|
+
return panel.options?.pieType === 'donut' ? 'donut' : 'pie'
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
if (chartType === 'bar') return 'bar'
|
|
1268
|
+
if (chartType === 'toplist') return 'bar'
|
|
1269
|
+
|
|
1270
|
+
const drawStyle = panel.fieldConfig?.defaults?.custom?.drawStyle
|
|
1271
|
+
const fillOpacity = panel.fieldConfig?.defaults?.custom?.fillOpacity
|
|
1272
|
+
if (drawStyle === 'bars') return 'bar'
|
|
1273
|
+
if (fillOpacity && fillOpacity > 0) return 'areaLine'
|
|
1274
|
+
return 'line'
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
function inferQueryLanguage(target, queryText) {
|
|
1278
|
+
const datasourceType = getDatasourceType(target.datasource)
|
|
1279
|
+
// Guance datasource defaults to DQL unless the Grafana target explicitly marks the query as PromQL.
|
|
1280
|
+
if (target.qtype === 'promql') return 'promql'
|
|
1281
|
+
if (target.qtype === 'dql') return 'dql'
|
|
1282
|
+
if (datasourceType.includes('guance-guance-datasource')) return 'dql'
|
|
1283
|
+
if (isPrometheusLikeDatasource(datasourceType)) return 'promql'
|
|
1284
|
+
if (isDqlLikeDatasource(datasourceType)) return 'dql'
|
|
1285
|
+
if (/^\s*(with|select)\b/i.test(queryText)) return 'dql'
|
|
1286
|
+
if (/^[A-Z]::/.test(queryText)) return 'dql'
|
|
1287
|
+
return 'promql'
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
function inferVariableQueryType(variable, queryString) {
|
|
1291
|
+
const datasourceType = getDatasourceType(variable.datasource)
|
|
1292
|
+
const explicitQtype = String(variable.query?.qtype || '').toLowerCase()
|
|
1293
|
+
if (datasourceType.includes('object')) return 'FIELD'
|
|
1294
|
+
if (explicitQtype === 'promql') return 'PROMQL_QUERY'
|
|
1295
|
+
if (explicitQtype === 'dql') return 'QUERY'
|
|
1296
|
+
if (isDqlLikeDatasource(datasourceType) && /^\s*(with|select)\b/i.test(queryString)) return 'QUERY'
|
|
1297
|
+
if (/field_values\(/i.test(queryString) || /label_values\(/i.test(queryString)) return 'QUERY'
|
|
1298
|
+
if (/^[A-Z]::/.test(queryString) || /L\('/.test(queryString)) return 'QUERY'
|
|
1299
|
+
if (/^\s*(with|select)\b/i.test(queryString)) return 'QUERY'
|
|
1300
|
+
return 'PROMQL_QUERY'
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
function extractVariableQuery(variable) {
|
|
1304
|
+
if (typeof variable.query === 'string') return variable.query
|
|
1305
|
+
if (variable.query && typeof variable.query === 'object') {
|
|
1306
|
+
return variable.query.rawQuery || variable.query.query || variable.query.expr || ''
|
|
1307
|
+
}
|
|
1308
|
+
if (typeof variable.definition === 'string') return variable.definition
|
|
1309
|
+
return ''
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
function extractTargetQuery(target) {
|
|
1313
|
+
const candidates = [target.expr, target.query, target.queryText, target.expression, target.rawSql]
|
|
1314
|
+
for (const candidate of candidates) {
|
|
1315
|
+
if (typeof candidate === 'string' && candidate.trim()) return candidate
|
|
1316
|
+
}
|
|
1317
|
+
return ''
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
function extractWorkspaceInfo(targets) {
|
|
1321
|
+
const workspaceUUIDs = []
|
|
1322
|
+
const workspaceNames = []
|
|
1323
|
+
|
|
1324
|
+
for (const target of Array.isArray(targets) ? targets : []) {
|
|
1325
|
+
for (const item of Array.isArray(target.workspaceUUIDs) ? target.workspaceUUIDs : []) {
|
|
1326
|
+
if (item?.value && !workspaceUUIDs.includes(item.value)) workspaceUUIDs.push(item.value)
|
|
1327
|
+
if (item?.label && !workspaceNames.includes(item.label)) workspaceNames.push(item.label)
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
return pruneEmpty({
|
|
1332
|
+
changeWorkspace: workspaceUUIDs.length > 0,
|
|
1333
|
+
workspaceUUID: workspaceUUIDs.length ? workspaceUUIDs.join(',') : undefined,
|
|
1334
|
+
workspaceName: workspaceNames.length ? workspaceNames : undefined,
|
|
1335
|
+
})
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
function normalizeTargetQuery(queryText, qtype, options = {}) {
|
|
1339
|
+
if (qtype !== 'promql') return queryText
|
|
1340
|
+
if (!options.guancePromqlCompatible) return queryText
|
|
1341
|
+
return normalizePromqlForGuance(queryText)
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
function extractMetricName(queryString, variableNames) {
|
|
1345
|
+
if (/^\s*(with|select)\b/i.test(queryString)) return ''
|
|
1346
|
+
const labelValuesMatch = queryString.match(/label_values\(([^,]+),\s*([^)]+)\)/i)
|
|
1347
|
+
if (labelValuesMatch) return replaceVariables(labelValuesMatch[1].trim(), variableNames)
|
|
1348
|
+
return ''
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
function extractFieldName(queryString) {
|
|
1352
|
+
const fieldValuesMatch = queryString.match(/field_values\(`?([^`)\s]+)`?\)/i)
|
|
1353
|
+
if (fieldValuesMatch) return fieldValuesMatch[1].trim()
|
|
1354
|
+
const labelValuesMatch = queryString.match(/label_values\([^,]+,\s*([^)]+)\)/i)
|
|
1355
|
+
if (labelValuesMatch) return labelValuesMatch[1].replace(/[`'"]/g, '').trim()
|
|
1356
|
+
return ''
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
function normalizeTimeInterval(value) {
|
|
1360
|
+
if (!value || typeof value !== 'string') return 'auto'
|
|
1361
|
+
const normalized = value.trim()
|
|
1362
|
+
if (!normalized) return 'auto'
|
|
1363
|
+
return normalized
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
function normalizePromqlForGuance(queryText) {
|
|
1367
|
+
if (typeof queryText !== 'string' || !queryText.trim()) return queryText
|
|
1368
|
+
let result = ''
|
|
1369
|
+
let index = 0
|
|
1370
|
+
let braceDepth = 0
|
|
1371
|
+
|
|
1372
|
+
while (index < queryText.length) {
|
|
1373
|
+
const current = queryText[index]
|
|
1374
|
+
|
|
1375
|
+
if (current === '{') {
|
|
1376
|
+
braceDepth++
|
|
1377
|
+
result += current
|
|
1378
|
+
index++
|
|
1379
|
+
continue
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
if (current === '}') {
|
|
1383
|
+
braceDepth = Math.max(0, braceDepth - 1)
|
|
1384
|
+
result += current
|
|
1385
|
+
index++
|
|
1386
|
+
continue
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
if (braceDepth === 0 && /[A-Za-z_:]/.test(current)) {
|
|
1390
|
+
let end = index + 1
|
|
1391
|
+
while (end < queryText.length && /[A-Za-z0-9_:]/.test(queryText[end])) end++
|
|
1392
|
+
const token = queryText.slice(index, end)
|
|
1393
|
+
let lookahead = end
|
|
1394
|
+
while (lookahead < queryText.length && /\s/.test(queryText[lookahead])) lookahead++
|
|
1395
|
+
const next = queryText[lookahead]
|
|
1396
|
+
if (next === '{' || next === '[') {
|
|
1397
|
+
result += toGuancePromqlMetricName(token)
|
|
1398
|
+
index = end
|
|
1399
|
+
continue
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
result += current
|
|
1404
|
+
index++
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
return result
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
function inferUnitFromQueries(queries, chartType) {
|
|
1411
|
+
if (!Array.isArray(queries) || queries.length === 0) return undefined
|
|
1412
|
+
if (['text', 'table', 'topology', 'iframe', 'picture', 'video'].includes(chartType)) return undefined
|
|
1413
|
+
|
|
1414
|
+
const inferredUnits = queries
|
|
1415
|
+
.map((query) => inferUnitFromQueryText(query?.query?.q || ''))
|
|
1416
|
+
.filter(Boolean)
|
|
1417
|
+
|
|
1418
|
+
if (inferredUnits.length === 0) return undefined
|
|
1419
|
+
|
|
1420
|
+
const counts = new Map()
|
|
1421
|
+
for (const unit of inferredUnits) {
|
|
1422
|
+
counts.set(unit, (counts.get(unit) || 0) + 1)
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
let bestUnit = inferredUnits[0]
|
|
1426
|
+
let bestCount = counts.get(bestUnit) || 0
|
|
1427
|
+
for (const unit of inferredUnits) {
|
|
1428
|
+
const currentCount = counts.get(unit) || 0
|
|
1429
|
+
if (currentCount > bestCount) {
|
|
1430
|
+
bestUnit = unit
|
|
1431
|
+
bestCount = currentCount
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
return bestUnit
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
function inferCompareSettings(queries, chartType) {
|
|
1439
|
+
if (!Array.isArray(queries) || queries.length === 0) {
|
|
1440
|
+
return { compares: undefined, compareType: undefined, openCompare: undefined, compareChartType: undefined }
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
if (!['sequence', 'singlestat'].includes(chartType)) {
|
|
1444
|
+
return { compares: undefined, compareType: undefined, openCompare: undefined, compareChartType: undefined }
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
const compareTypes = []
|
|
1448
|
+
for (const query of queries) {
|
|
1449
|
+
const compareType = inferCompareTypeFromQuery(query?.query?.q || '')
|
|
1450
|
+
if (compareType && !compareTypes.includes(compareType)) {
|
|
1451
|
+
compareTypes.push(compareType)
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
if (compareTypes.length === 0) {
|
|
1456
|
+
return { compares: undefined, compareType: undefined, openCompare: undefined, compareChartType: undefined }
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
return {
|
|
1460
|
+
compares: compareTypes.map((type) => COMPARE_OPTIONS[type]).filter(Boolean),
|
|
1461
|
+
compareType: compareTypes,
|
|
1462
|
+
openCompare: true,
|
|
1463
|
+
compareChartType: chartType,
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
function inferCompareTypeFromQuery(queryText) {
|
|
1468
|
+
if (typeof queryText !== 'string' || !queryText.trim()) return undefined
|
|
1469
|
+
const normalized = queryText.toLowerCase()
|
|
1470
|
+
|
|
1471
|
+
if (/\boffset\s+1h\b/.test(normalized)) return 'hourCompare'
|
|
1472
|
+
if (/\boffset\s+1d\b/.test(normalized)) return 'dayCompare'
|
|
1473
|
+
if (/\boffset\s+(7d|1w)\b/.test(normalized)) return 'weekCompare'
|
|
1474
|
+
if (/\boffset\s+(30d|4w)\b/.test(normalized)) return 'monthCompare'
|
|
1475
|
+
|
|
1476
|
+
return undefined
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
function inferSortSettings(chartType, legend, tableSortBy) {
|
|
1480
|
+
const sequenceSort = inferSequenceSortOrder(legend)
|
|
1481
|
+
const mainMeasurementSort = inferMainMeasurementSort(chartType, legend, tableSortBy)
|
|
1482
|
+
|
|
1483
|
+
return {
|
|
1484
|
+
sorderByOrder: sequenceSort,
|
|
1485
|
+
mainMeasurementSort,
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
function inferSeriesLimit(queries, options, chartType) {
|
|
1490
|
+
const explicitLimit = extractReduceLimit(options)
|
|
1491
|
+
if (['pie', 'toplist', 'treemap'].includes(chartType)) {
|
|
1492
|
+
return extractTopkLimit(queries) || explicitLimit
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
if (['sequence', 'table'].includes(chartType)) {
|
|
1496
|
+
return extractTopkLimit(queries) || undefined
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
return undefined
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
function extractTopkLimit(queries) {
|
|
1503
|
+
if (!Array.isArray(queries)) return undefined
|
|
1504
|
+
|
|
1505
|
+
for (const query of queries) {
|
|
1506
|
+
const queryText = String(query?.query?.q || '')
|
|
1507
|
+
const match = queryText.match(/\btopk\s*\(\s*(\d+)/i)
|
|
1508
|
+
if (!match) continue
|
|
1509
|
+
const limit = Number(match[1])
|
|
1510
|
+
if (Number.isFinite(limit)) return limit
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
return undefined
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
function inferSequenceSortOrder(legend) {
|
|
1517
|
+
if (typeof legend?.sortDesc === 'boolean') {
|
|
1518
|
+
return legend.sortDesc ? 'desc' : 'asc'
|
|
1519
|
+
}
|
|
1520
|
+
return undefined
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
function inferMainMeasurementSort(chartType, legend, tableSortBy) {
|
|
1524
|
+
if (!['pie', 'toplist', 'table', 'treemap'].includes(chartType)) return undefined
|
|
1525
|
+
|
|
1526
|
+
if (chartType === 'table') {
|
|
1527
|
+
const primarySort = Array.isArray(tableSortBy) ? tableSortBy[0] : undefined
|
|
1528
|
+
if (primarySort && typeof primarySort.desc === 'boolean') {
|
|
1529
|
+
return primarySort.desc ? 'top' : 'bottom'
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
if (typeof legend?.sortDesc === 'boolean') {
|
|
1534
|
+
return legend.sortDesc ? 'top' : 'bottom'
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
return undefined
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
function inferUnitFromQueryText(queryText) {
|
|
1541
|
+
if (typeof queryText !== 'string' || !queryText.trim()) return undefined
|
|
1542
|
+
const normalized = queryText.toLowerCase()
|
|
1543
|
+
const isRateLike = /rate\(|irate\(|increase\(|delta\(|deriv\(/.test(normalized)
|
|
1544
|
+
|
|
1545
|
+
if (
|
|
1546
|
+
/cpu.*(?:usage|utili[sz]ation|used)|cpu_usage|cpu_used|usage_seconds_total/.test(normalized) &&
|
|
1547
|
+
/limit|quota|max|capacity|total|cores?|100/.test(normalized)
|
|
1548
|
+
) {
|
|
1549
|
+
return 'percent'
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
if (
|
|
1553
|
+
/memory.*(?:usage|used|utili[sz]ation)|heap.*used|rss|working_set|used_bytes|usage_bytes/.test(normalized)
|
|
1554
|
+
) {
|
|
1555
|
+
return 'bytes'
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
if (
|
|
1559
|
+
/disk.*(?:usage|used|utili[sz]ation)|filesystem.*(?:avail|free|size|used)|storage.*(?:used|usage)/.test(normalized)
|
|
1560
|
+
) {
|
|
1561
|
+
return 'bytes'
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
if (
|
|
1565
|
+
/error_rate|success_rate|failure_rate|biz_error_rate|_ratio\b|_percent\b|percent/.test(normalized) ||
|
|
1566
|
+
/container_cpu_usage_seconds_total/.test(normalized) && /kube_pod_container_resource_limits/.test(normalized) ||
|
|
1567
|
+
/\*\s*100\b/.test(normalized)
|
|
1568
|
+
) {
|
|
1569
|
+
return 'percent'
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
if (
|
|
1573
|
+
/p99|p95|p90|latency|duration|response_time|cost\b|elapsed|load_time|_ms\b|milliseconds?/.test(normalized) ||
|
|
1574
|
+
/performance_host_interface_p\d+/.test(normalized)
|
|
1575
|
+
) {
|
|
1576
|
+
return 'ms'
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
if (
|
|
1580
|
+
/_bytes\b|_bytes_total\b|memory|heap|rss|bandwidth|byte\b/.test(normalized)
|
|
1581
|
+
) {
|
|
1582
|
+
return 'bytes'
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
if (
|
|
1586
|
+
/gc_pause_seconds|gc.*(?:pause|time)|duration_seconds|latency_seconds|response_seconds/.test(normalized)
|
|
1587
|
+
) {
|
|
1588
|
+
return 's'
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
if (
|
|
1592
|
+
/\bqps\b|\brps\b|reqps|requests?_per_second|interface_qps|host_qps|requests?_total/.test(normalized) && isRateLike
|
|
1593
|
+
) {
|
|
1594
|
+
return 'reqps'
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
if (
|
|
1598
|
+
/\btps\b|\biops\b|\bops\b|operations?_per_second|ops_total/.test(normalized) && isRateLike
|
|
1599
|
+
) {
|
|
1600
|
+
return 'ops'
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
if (
|
|
1604
|
+
/cpu:load\d+s|load5s|load1s|load15s|system_load/.test(normalized)
|
|
1605
|
+
) {
|
|
1606
|
+
return 'short'
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
if (
|
|
1610
|
+
!isRateLike &&
|
|
1611
|
+
/goroutines?|threads?|connections?|conn_count|fd|file_descriptors?|queue(_size|_depth)?|pool(_size)?|inflight|pending|blocked|active_requests|jvm_.*_count|_count\b|_total\b|count_over_time\(/.test(normalized)
|
|
1612
|
+
) {
|
|
1613
|
+
return 'none'
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
if (
|
|
1617
|
+
/_seconds\b/.test(normalized) && !/rate\(|increase\(|irate\(/.test(normalized)
|
|
1618
|
+
) {
|
|
1619
|
+
return 's'
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
return undefined
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
function toGuancePromqlMetricName(token) {
|
|
1626
|
+
if (!token) return token
|
|
1627
|
+
if (token.includes(':')) return token
|
|
1628
|
+
if (!token.includes('_')) return token
|
|
1629
|
+
if (token.startsWith('__')) return token
|
|
1630
|
+
if (PROMQL_RESERVED_WORDS.has(token)) return token
|
|
1631
|
+
const firstUnderscore = token.indexOf('_')
|
|
1632
|
+
if (firstUnderscore <= 0 || firstUnderscore === token.length - 1) return token
|
|
1633
|
+
return `${token.slice(0, firstUnderscore)}:${token.slice(firstUnderscore + 1)}`
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
function getDatasourceType(datasource) {
|
|
1637
|
+
return String(datasource?.type || datasource || '').toLowerCase()
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
function isPrometheusLikeDatasource(datasourceType) {
|
|
1641
|
+
return datasourceType.includes('prometheus') || datasourceType.includes('guance-guance-datasource')
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
function isDqlLikeDatasource(datasourceType) {
|
|
1645
|
+
return (
|
|
1646
|
+
datasourceType.includes('mysql') ||
|
|
1647
|
+
datasourceType.includes('postgres') ||
|
|
1648
|
+
datasourceType.includes('mssql') ||
|
|
1649
|
+
datasourceType.includes('sql') ||
|
|
1650
|
+
datasourceType.includes('loki') ||
|
|
1651
|
+
datasourceType.includes('elasticsearch') ||
|
|
1652
|
+
datasourceType.includes('opensearch') ||
|
|
1653
|
+
datasourceType.includes('cloudwatch') ||
|
|
1654
|
+
datasourceType.includes('influx') ||
|
|
1655
|
+
datasourceType.includes('tempo') ||
|
|
1656
|
+
datasourceType.includes('jaeger') ||
|
|
1657
|
+
datasourceType.includes('zipkin')
|
|
1658
|
+
)
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
function extractReduceLimit(options) {
|
|
1662
|
+
const limit = options.reduceOptions?.limit
|
|
1663
|
+
return typeof limit === 'number' ? limit : 10
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
function extractCustomOptions(options) {
|
|
1667
|
+
if (!Array.isArray(options)) return ''
|
|
1668
|
+
return options
|
|
1669
|
+
.filter((item) => item && item.value !== '$__all')
|
|
1670
|
+
.map((item) => item.text || item.value || '')
|
|
1671
|
+
.filter(Boolean)
|
|
1672
|
+
.join(',')
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
function mapLegendPlacement(value) {
|
|
1676
|
+
if (value === 'bottom') return 'bottom'
|
|
1677
|
+
if (value === 'right') return 'right'
|
|
1678
|
+
if (value === 'left') return 'left'
|
|
1679
|
+
if (value === 'top') return 'top'
|
|
1680
|
+
return 'none'
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
function mapLegendCalcs(values) {
|
|
1684
|
+
if (!Array.isArray(values)) return []
|
|
1685
|
+
const allowed = new Set(['first', 'last', 'avg', 'min', 'max', 'sum', 'count'])
|
|
1686
|
+
return values
|
|
1687
|
+
.map((value) => String(value).toLowerCase())
|
|
1688
|
+
.filter((value) => allowed.has(value))
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
function extractLegacyLegendCalcs(legend) {
|
|
1692
|
+
if (!legend || typeof legend !== 'object') return []
|
|
1693
|
+
const calcs = []
|
|
1694
|
+
if (legend.current) calcs.push('last')
|
|
1695
|
+
if (legend.avg) calcs.push('avg')
|
|
1696
|
+
if (legend.min) calcs.push('min')
|
|
1697
|
+
if (legend.max) calcs.push('max')
|
|
1698
|
+
if (legend.total) calcs.push('sum')
|
|
1699
|
+
return calcs
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
function mapLineInterpolation(value) {
|
|
1703
|
+
if (value === 'smooth') return 'smooth'
|
|
1704
|
+
if (value === 'stepAfter') return 'stepAfter'
|
|
1705
|
+
if (value === 'stepBefore') return 'stepBefore'
|
|
1706
|
+
return 'linear'
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
function mapStackType(value) {
|
|
1710
|
+
if (value === 'percent') return 'percent'
|
|
1711
|
+
if (value === 'normal') return 'time'
|
|
1712
|
+
return 'time'
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
function inferShowLine(panel, custom) {
|
|
1716
|
+
if (typeof panel.lines === 'boolean') return panel.lines
|
|
1717
|
+
if (custom.drawStyle === 'bars') return false
|
|
1718
|
+
return true
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
function inferOpenStack(panel, custom) {
|
|
1722
|
+
if (custom.stacking?.mode) return custom.stacking.mode !== 'none'
|
|
1723
|
+
if (typeof panel.stack === 'boolean') return panel.stack
|
|
1724
|
+
return undefined
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
function inferSequenceChartType(panel, graphMode) {
|
|
1728
|
+
if (graphMode === 'area') return 'line'
|
|
1729
|
+
if (graphMode === 'none') return undefined
|
|
1730
|
+
return inferDisplayChartType(panel, 'sequence') === 'bar' ? 'bar' : 'line'
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
function inferGaugeMode(panel, options, legacyGauge) {
|
|
1734
|
+
if (panel.type === 'gauge') return 'gauge'
|
|
1735
|
+
if (legacyGauge.show) return 'gauge'
|
|
1736
|
+
if (options.graphMode === 'none') return 'value'
|
|
1737
|
+
return 'value'
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
function normalizeConnectNulls(value) {
|
|
1741
|
+
if (typeof value === 'boolean') return value
|
|
1742
|
+
if (typeof value === 'number') return value > 0
|
|
1743
|
+
if (value === 'connected') return true
|
|
1744
|
+
if (value === 'null' || value === 'null as zero') return false
|
|
1745
|
+
return undefined
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
function mapUnit(unit) {
|
|
1749
|
+
if (!unit) return []
|
|
1750
|
+
const mapped = UNIT_MAP[String(unit).toLowerCase()]
|
|
1751
|
+
if (mapped) return mapped
|
|
1752
|
+
return ['custom', String(unit)]
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
function buildLegacyValueMappings(valueMaps) {
|
|
1756
|
+
if (!Array.isArray(valueMaps)) return []
|
|
1757
|
+
return valueMaps
|
|
1758
|
+
.filter((item) => item && Object.prototype.hasOwnProperty.call(item, 'value'))
|
|
1759
|
+
.map((item) => ({
|
|
1760
|
+
originalVal: [String(item.value)],
|
|
1761
|
+
operation: normalizeLegacyMappingOperation(item.op),
|
|
1762
|
+
mappingVal: item.text || String(item.value),
|
|
1763
|
+
}))
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
function buildLegacyRangeMappings(rangeMaps) {
|
|
1767
|
+
if (!Array.isArray(rangeMaps)) return []
|
|
1768
|
+
return rangeMaps.map((item) => ({
|
|
1769
|
+
originalVal: [String(item.from ?? ''), String(item.to ?? '')],
|
|
1770
|
+
operation: 'between',
|
|
1771
|
+
mappingVal: item.text || '',
|
|
1772
|
+
}))
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
function normalizeLegacyMappingOperation(value) {
|
|
1776
|
+
const allowed = new Set(['>', '>=', '<', '<=', '=', '!=', 'between', '=~', '!=~', 'nodata'])
|
|
1777
|
+
if (allowed.has(value)) return value
|
|
1778
|
+
return '='
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
function normalizeLegacyFill(value) {
|
|
1782
|
+
if (typeof value !== 'number') return undefined
|
|
1783
|
+
return Math.max(0, Math.min(100, value * 10))
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
function firstDefinedNumber(...values) {
|
|
1787
|
+
for (const value of values) {
|
|
1788
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value
|
|
1789
|
+
}
|
|
1790
|
+
return undefined
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
function replaceVariables(input, variableNames = new Set()) {
|
|
1794
|
+
if (typeof input !== 'string') return input
|
|
1795
|
+
return input
|
|
1796
|
+
.replace(/\$\{([^}]+)\}/g, (match, expression) => {
|
|
1797
|
+
const variable = normalizeTemplateVariable(expression)
|
|
1798
|
+
if (!variable) return match
|
|
1799
|
+
if (GRAFANA_BUILTIN_VARS.has(variable)) return match
|
|
1800
|
+
if (!variableNames.has(variable)) return match
|
|
1801
|
+
return `#{${variable}}`
|
|
1802
|
+
})
|
|
1803
|
+
.replace(/(^|[^{])\$([A-Za-z0-9_]+)/g, (match, prefix, variable) => {
|
|
1804
|
+
if (GRAFANA_BUILTIN_VARS.has(variable)) return match
|
|
1805
|
+
if (!variableNames.has(variable)) return match
|
|
1806
|
+
return `${prefix}#{${variable}}`
|
|
1807
|
+
})
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
function normalizeTemplateVariable(expression) {
|
|
1811
|
+
const trimmed = String(expression).trim()
|
|
1812
|
+
if (!trimmed) return ''
|
|
1813
|
+
const beforeFormat = trimmed.split(':')[0]
|
|
1814
|
+
if (!/^[A-Za-z0-9_.]+$/.test(beforeFormat)) return ''
|
|
1815
|
+
return beforeFormat
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
function normalizeQueryCode(refId, index) {
|
|
1819
|
+
if (typeof refId === 'string' && refId.trim()) return refId.trim()
|
|
1820
|
+
const codePoint = 65 + index
|
|
1821
|
+
return String.fromCharCode(codePoint)
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
function stringifyCurrent(value) {
|
|
1825
|
+
if (Array.isArray(value)) return value.join(',')
|
|
1826
|
+
if (typeof value === 'string') return value
|
|
1827
|
+
if (typeof value === 'number' || typeof value === 'boolean') return String(value)
|
|
1828
|
+
return ''
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
function normalizeAllValue(value, allValue, isValue = false) {
|
|
1832
|
+
if (value === 'All') return allValue === '.*' ? '*' : 'all values'
|
|
1833
|
+
if (value === '$__all') return allValue === '.*' ? '*' : '__all__'
|
|
1834
|
+
if (isValue && value === '.*') return '*'
|
|
1835
|
+
return value
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
function normalizeColor(color) {
|
|
1839
|
+
if (!color) return '#999999'
|
|
1840
|
+
return String(color)
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
function numberOr(value, fallback) {
|
|
1844
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : fallback
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
function numberOrUndefined(value) {
|
|
1848
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
function round1(value) {
|
|
1852
|
+
return Number(value.toFixed(1))
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
function firstDefined(...values) {
|
|
1856
|
+
for (const value of values) {
|
|
1857
|
+
if (value !== undefined && value !== null && value !== '') return value
|
|
1858
|
+
}
|
|
1859
|
+
return undefined
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
function pruneEmpty(value) {
|
|
1863
|
+
if (Array.isArray(value)) {
|
|
1864
|
+
return value
|
|
1865
|
+
.map((item) => pruneEmpty(item))
|
|
1866
|
+
.filter((item) => item !== undefined)
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
if (!value || typeof value !== 'object') {
|
|
1870
|
+
return value
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
const entries = Object.entries(value)
|
|
1874
|
+
.map(([key, current]) => [key, pruneEmpty(current)])
|
|
1875
|
+
.filter(([, current]) => {
|
|
1876
|
+
if (current === undefined) return false
|
|
1877
|
+
if (Array.isArray(current) && current.length === 0) return false
|
|
1878
|
+
if (current && typeof current === 'object' && !Array.isArray(current) && Object.keys(current).length === 0)
|
|
1879
|
+
return false
|
|
1880
|
+
return true
|
|
1881
|
+
})
|
|
1882
|
+
|
|
1883
|
+
return Object.fromEntries(entries)
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
function readJson(filePath) {
|
|
1887
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'))
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
function forEachFile(directoryPath, callback) {
|
|
1891
|
+
for (const entry of fs.readdirSync(directoryPath, { withFileTypes: true })) {
|
|
1892
|
+
const entryPath = path.join(directoryPath, entry.name)
|
|
1893
|
+
if (entry.isDirectory()) {
|
|
1894
|
+
forEachFile(entryPath, callback)
|
|
1895
|
+
} else {
|
|
1896
|
+
callback(entryPath)
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
}
|