diff --git a/nezha-fronted/src/components/chart/chart/chartTimeSeries.vue b/nezha-fronted/src/components/chart/chart/chartTimeSeries.vue index b4386f54c..20dd556c2 100644 --- a/nezha-fronted/src/components/chart/chart/chartTimeSeries.vue +++ b/nezha-fronted/src/components/chart/chart/chartTimeSeries.vue @@ -191,7 +191,6 @@ export default { this.legends = [] this.series = chartOption.series = this.handleTimeSeries(this.chartInfo, chartOption.series[0], this.chartData) // 生成series和legends - console.log(this.$lodash.cloneDeep( this.chartData), ' this.chartData') chartOption.series.forEach(item => { if (item.lineStyle) { item.lineStyle.width = this.lineOption.lineWidth @@ -238,7 +237,6 @@ export default { delete chartOption.tooltip.position } const option = this.renderYAxis(this.chartData, this.chartInfo) - console.log(option) chartOption.yAxis[0] = { ...chartOption.yAxis[0], ...option.yAxis[0] } chartOption.yAxis[1] = { ...chartOption.yAxis[1], ...option.yAxis[1] } if (chartOption.toolbox.feature) { @@ -612,7 +610,6 @@ export default { yAxisLabelFormatter (minValue, maxValue, copies, unit, dot) { const self = this return function (val, index) { - console.log(val) let value = formatScientificNotation(val, 6) if (!val) { value = 0 diff --git a/nezha-fronted/src/components/chart/chartDataFormat.js b/nezha-fronted/src/components/chart/chartDataFormat.js index 2fbd8dbdd..c90d9e99f 100644 --- a/nezha-fronted/src/components/chart/chartDataFormat.js +++ b/nezha-fronted/src/components/chart/chartDataFormat.js @@ -1153,8 +1153,7 @@ export default { } }, Interval: function (value, copies, unit, interValType) { - console.log(unit) - const type = unit.type + const type = unit.type || unit const ascii = unit.ascii || 1000 if (!copies) { copies = 1 diff --git a/nezha-fronted/src/components/chart/renderChart.js b/nezha-fronted/src/components/chart/renderChart.js index 17f80f1d8..25c08d044 100644 --- a/nezha-fronted/src/components/chart/renderChart.js +++ b/nezha-fronted/src/components/chart/renderChart.js @@ -5,7 +5,6 @@ import { formatScientificNotation } from '@/components/common/js/tools' export default { methods: { renderYAxis (chartDatas, chartInfo, type) { - console.log(chartDatas, '1') let chartData = lodash.cloneDeep(chartDatas) if (type === 'legend') { chartData.forEach(item => { @@ -13,7 +12,6 @@ export default { }) chartData = chartData.map(item => [item]) } - console.log(chartDatas, '2') const decimals = chartInfo.param.decimals || 2 const chartOption = { yAxis: [ @@ -32,7 +30,6 @@ export default { } // 左y轴 const leftData = chartData.filter(item => item[0] && !item[0].yAxisIndex) - console.log(chartDatas, '3') const leftInfo = this.getMinMaxFromData(leftData, chartInfo.unit, chartInfo.param) // chartOption.yAxis[0].minInterval = chartDataFormat.Interval(leftInfo.maxValue, leftInfo.copies, leftInfo.unit, 'min') chartOption.yAxis[0].maxInterval = chartDataFormat.Interval(leftInfo.maxValue, leftInfo.copies, leftInfo.unit, 'max') * Math.ceil(this.series.length / 5) @@ -64,14 +61,9 @@ export default { // 如果全都为右y轴数据 则右y轴显示网格 const unit = chartDataFormat.getUnit(lodash.get(chartInfo, 'param.rightYAxis.unit', 2)) const allRight = this.series.every(item => item.yAxisIndex == 1) - console.log(allRight) chartOption.yAxis[1].splitLine.show = allRight - console.log(chartDatas, '4') const rightData = chartData.filter(item => item[0] && item[0].yAxisIndex) - console.log(chartDatas, '5') - console.log(rightData) const rightInfo = this.getMinMaxFromData(rightData, lodash.get(chartInfo, 'param.rightYAxis.unit', 2), lodash.get(chartInfo, 'param.rightYAxis', {}))// - console.log(rightInfo) chartOption.yAxis[1].minInterval = chartDataFormat.Interval(rightInfo.maxValue, rightInfo.copies, rightInfo.unit, 'min') chartOption.yAxis[1].maxInterval = chartDataFormat.Interval(rightInfo.maxValue, rightInfo.copies, rightInfo.unit, 'max') * Math.ceil(this.series.length / 5) if (chartInfo.param.stack) { diff --git a/nezha-fronted/src/components/common/bottomBox/tabs/licenseManagementTab.vue b/nezha-fronted/src/components/common/bottomBox/tabs/licenseManagementTab.vue index d1cf079b6..0c0fc399b 100644 --- a/nezha-fronted/src/components/common/bottomBox/tabs/licenseManagementTab.vue +++ b/nezha-fronted/src/components/common/bottomBox/tabs/licenseManagementTab.vue @@ -154,7 +154,6 @@ export default { }) } } else { - console.log(response) this.$message.error(response.msg || response.error) } }) diff --git a/nezha-fronted/src/components/common/mixin/mainMixinFun.js b/nezha-fronted/src/components/common/mixin/mainMixinFun.js index f62e1cb57..24356f389 100644 --- a/nezha-fronted/src/components/common/mixin/mainMixinFun.js +++ b/nezha-fronted/src/components/common/mixin/mainMixinFun.js @@ -83,7 +83,6 @@ export default { momentTz (timestamp, fmt) { // moment 转化时间戳为str const offset = localStorage.getItem('nz-sys-timezone') const format = fmt || localStorage.getItem('nz-default-dateFormat') - console.log(timestamp, fmt, offset, localStorage.getItem('nz-default-dateFormat'), format) return moment.tz(timestamp, offset).format(format) }, momentStrToTimestamp (str, fmt) { diff --git a/nezha-fronted/src/components/page/dashboard/explore/exploreItem.vue b/nezha-fronted/src/components/page/dashboard/explore/exploreItem.vue index 6891471b5..e84cca5b7 100644 --- a/nezha-fronted/src/components/page/dashboard/explore/exploreItem.vue +++ b/nezha-fronted/src/components/page/dashboard/explore/exploreItem.vue @@ -3885,10 +3885,12 @@ export default { this.$refs.logDetail && this.$refs.logDetail.resetOperation() if (this.expressions.length > 0) { const requestArr = [] + const realArr = [] // 记录原始位置 // 过滤掉state为0的元素 this.expressions.forEach((item, index) => { if (item != '' && this.promqlKeys[index].state && item) { requestArr.push(this.$get('/logs/loki/api/v1/query_range?format=1&query=' + encodeURIComponent(item) + '&start=' + this.$stringTimeParseToUnix(bus.formateTimeToTime(this.filterTime[0])) + '&end=' + this.$stringTimeParseToUnix(bus.formateTimeToTime(this.filterTime[1])) + '&limit=' + limit)) + realArr.push(index) } }) if (requestArr.length > 0) { @@ -3896,18 +3898,22 @@ export default { this.saveDisabled = false } axios.all(requestArr).then(res => { - this.postHistory() + console.log(res) this.chartLoading = false const errorRowIndex = [] res.forEach((r, i) => { - if (typeof r === 'string') { + if (r.code !== 200) { errorRowIndex.push(i) + this.promqlKeys[realArr[i]].isResError = true + } else { + this.promqlKeys[realArr[i]].isResError = false } }) if (errorRowIndex.length > 0) { this.$message.error(this.$t('tip.errorInRow') + ': ' + errorRowIndex.map(e => e + 1).join(' ,')) res = res.filter((r, i) => errorRowIndex.indexOf(i) === -1) } + this.postHistory() if (res.length > 0) { const logData = res.map(r => r.data) if (logData[0].result.length > 0) { @@ -3941,6 +3947,8 @@ export default { this.logData = logData hasGraph && this.loadLogGraph() }) + } else { + this.showIntroduce = true } }).catch(e => { this.chartLoading = false @@ -4370,7 +4378,6 @@ export default { } else { const promiseArr = [] this.expressions.forEach((item, index) => { - console.log(item) if (item != '' && this.promqlKeys[index].state && item) { promiseArr.push(this.$get('logs/loki/api/v1/format_query', { query: item })) } else { @@ -4386,7 +4393,6 @@ export default { this.updatePath() }, initQueryFromPath () { - console.log('initQueryFromPath') const param = this.$route.query[this.position] if (param) { const data = JSON.parse(param) @@ -4412,7 +4418,6 @@ export default { this.promqlCount = data.queries.length data.queries.forEach((item, index) => { this.$set(this.expressions, index, item.expr) - console.log(item) this.promqlKeys[index] = { ...item, id: getUUID(), @@ -4571,7 +4576,7 @@ export default { }) this.lastHistory.forEach((item) => { const findItem = arr.find(history => history.id == item.id) - if (findItem && findItem.queryValue !== item.value) { + if (findItem && findItem.queryValue !== item.value && !findItem.isError && !findItem.isResError) { postArr.push({ queryKey, queryValue: findItem.queryValue @@ -4580,7 +4585,7 @@ export default { }) arr.forEach(item => { const findItem = this.lastHistory.find(history => history.id == item.id) - if (!findItem) { + if (!findItem && !item.isResError) { postArr.push({ queryKey, queryValue: item.queryValue @@ -4588,8 +4593,8 @@ export default { } }) postArr = postArr.filter(item => item.queryValue) + console.log(postArr) this.$post('/sys/user/queryHistory', postArr).then(res => { - console.log(res) this.lastHistory = this.$lodash.cloneDeep(postArr) }) }, diff --git a/nezha-fronted/src/components/page/dashboard/explore/logqlHint.js b/nezha-fronted/src/components/page/dashboard/explore/logqlHint.js new file mode 100644 index 000000000..4b1b2718d --- /dev/null +++ b/nezha-fronted/src/components/page/dashboard/explore/logqlHint.js @@ -0,0 +1,178 @@ +const logqlHint = { + functions: [{ + label: 'rate', + detail: 'functions', + info: 'Calculates the number of entries per second.', + type: 'functions' + }, { + label: 'count_over_time', + detail: 'functions', + info: 'Counts the entries for each log stream within the given range.', + type: 'functions' + }, { + label: 'bytes_rate', + detail: 'functions', + info: 'Calculates the number of bytes per second for each stream.', + type: 'functions' + }, { + label: 'bytes_over_time', + detail: 'functions', + info: 'counts the amount of bytes used by each log stream for a given range.', + type: 'functions' + }, { + label: 'absent_over_time', + detail: 'functions', + info: 'returns an empty vector if the range vector passed to it has any elements and a 1-element vector with the value 1 if the range vector passed to it has no elements. (absent_over_time is useful for alerting on when no time series and logs stream exist for label combination for a certain amount of time.)', + type: 'functions' + } + ], + aggregateOp: [{ + label: 'duration_seconds', + detail: 'aggregation', + info: 'which will convert the label value in seconds from the go duration format (e.g 5m, 24s30ms).', + type: 'keyword' + }, { + label: 'duration', + detail: 'aggregation', + info: 'which will convert the label value in seconds from the go duration format (e.g 5m, 24s30ms).', + type: 'keyword' + }, { + label: 'bytes', + detail: 'aggregation', + info: 'which will convert the label value to raw bytes applying the bytes unit (e.g. 5 MiB, 3k, 1G).', + type: 'keyword' + }, { + label: 'rate', + detail: 'aggregation', + info: 'calculates per second rate of the sum of all values in the specified interval.', + type: 'keyword' + }, { + label: 'rate_counter', + detail: 'aggregation', + info: 'calculates per second rate of the values in the specified interval and treating them as “counter metric”.', + type: 'keyword' + }, { + label: 'sum_over_time', + detail: 'aggregation', + info: 'the sum of all values in the specified interval.', + type: 'keyword' + }, { + label: 'avg_over_time', + detail: 'aggregation', + info: 'the average value of all points in the specified interval.', + type: 'keyword' + }, { + label: 'max_over_time', + detail: 'aggregation', + info: 'the maximum value of all points in the specified interval.', + type: 'keyword' + }, { + label: 'min_over_time', + detail: 'aggregation', + info: 'the minimum value of all points in the specified interval.', + type: 'keyword' + }, { + label: 'min_over_time', + detail: 'aggregation', + info: 'the minimum value of all points in the specified interval.', + type: 'keyword' + }, { + label: 'first_over_time', + detail: 'aggregation', + info: 'the first value of all points in the specified interval.', + type: 'keyword' + }, { + label: 'last_over_time', + detail: 'aggregation', + info: 'the last value of all points in the specified interval.', + type: 'keyword' + }, { + label: 'stdvar_over_time', + detail: 'aggregation', + info: 'the population standard variance of the values in the specified interval.', + type: 'keyword' + }, { + label: 'stddev_over_time', + detail: 'aggregation', + info: 'the population standard deviation of the values in the specified interval.', + type: 'keyword' + }, { + label: 'quantile_over_time', + detail: 'aggregation', + info: 'the φ-quantile (0 ≤ φ ≤ 1) of the values in the specified interval.', + type: 'keyword' + }, { + label: 'absent_over_time', + detail: 'aggregation', + info: 'returns an empty vector if the range vector passed to it has any elements and a 1-element vector with the value 1 if the range vector passed to it has no elements. (absent_over_time is useful for alerting on when no time series and logs stream exist for label combination for a certain amount of time.)', + type: 'keyword' + }, + { + label: 'sum', + detail: 'aggregation', + info: 'Calculate sum over labels', + type: 'keyword' + }, + { + label: 'avg', + detail: 'aggregation', + info: 'Calculate the average over labels', + type: 'keyword' + }, + { + label: 'min', + detail: 'aggregation', + info: 'Select minimum over labels', + type: 'keyword' + }, + { + label: 'max', + detail: 'aggregation', + info: 'Select maximum over labels', + type: 'keyword' + }, + { + label: 'stddev', + detail: 'aggregation', + info: 'Calculate the population standard deviation over labels', + type: 'keyword' + }, + { + label: 'stdvar', + detail: 'aggregation', + info: 'Calculate the population standard variance over labels', + type: 'keyword' + }, + { + label: 'count', + detail: 'aggregation', + info: 'Count number of elements in the vector', + type: 'keyword' + }, + { + label: 'topk', + detail: 'aggregation', + info: 'Select largest k elements by sample value', + type: 'keyword' + }, + { + label: 'bottomk', + detail: 'aggregation', + info: 'Select smallest k elements by sample value', + type: 'keyword' + }, + { + label: 'sort', + detail: 'aggregation', + info: 'returns vector elements sorted by their sample values, in ascending order.', + type: 'keyword' + }, + { + label: 'sort_desc', + detail: 'aggregation', + info: 'Same as sort, but sorts in descending order.', + type: 'keyword' + } + ] +} +export default logqlHint diff --git a/nezha-fronted/src/components/page/dashboard/explore/promqlInput.vue b/nezha-fronted/src/components/page/dashboard/explore/promqlInput.vue index c6652d0c2..454dd624d 100644 --- a/nezha-fronted/src/components/page/dashboard/explore/promqlInput.vue +++ b/nezha-fronted/src/components/page/dashboard/explore/promqlInput.vue @@ -10,30 +10,29 @@ @click="dropDownVisible = false" >
-
- -
+ + + + + + + + + + + + + + + + + +
{{ errorMsg }} @@ -68,30 +67,29 @@ v-if="plugins.indexOf('metric-input') > -1" >
-
- -
+ + + + + + + + + + + + + + + + + +
{{ errorMsg }} @@ -254,6 +252,8 @@ import { get } from '@/http' import { PromQLExtension } from '@prometheus-io/codemirror-promql' import { baseTheme, promqlHighlighter, lightTheme } from './CMTheme.tsx' import { newCompleteStrategy } from '@prometheus-io/codemirror-promql/dist/esm/complete' +import { analyzeCompletion, computeStartCompletePosition, ContextKind } from '@prometheus-io/codemirror-promql/dist/esm/complete/hybrid' +import { aggregateOpModifierTerms, aggregateOpTerms, atModifierTerms, binOpModifierTerms, binOpTerms, durationTerms, functionIdentifierTerms, matchOpTerms, numberTerms } from '@prometheus-io/codemirror-promql/dist/esm/complete/promql.terms' // import {basicSetup} from '@codemirror/basic-setup'; import { EditorView, highlightSpecialChars, keymap, ViewUpdate, placeholder } from '@codemirror/view' import { EditorState, Prec, Compartment } from '@codemirror/state' @@ -261,6 +261,7 @@ import { bracketMatching, indentOnInput, syntaxHighlighting, syntaxTree } from ' import { defaultKeymap, history, historyKeymap, insertNewlineAndIndent } from '@codemirror/commands' import { highlightSelectionMatches } from '@codemirror/search' import { lintKeymap } from '@codemirror/lint' +import logqlHint from './logqlHint' import { autocompletion, completionKeymap, @@ -418,28 +419,143 @@ export default { const defaultHeaders = { Authorization: localStorage.getItem('nz-token') } + + let api = '/prom/' + if (this.type === 'log') { + api = '/logs/loki/' + } const promqlExtension = new PromQLExtension() - .activateCompletion(true) - .activateLinter(true) - .setComplete({ - completeStrategy: new HistoryCompleteStrategy( - newCompleteStrategy({ - remote: { - url: baseUrl + '/prom/', - fetchFn: (resource, options = {}) => { - const params = options.body?.toString() - const search = params ? `?${params}` : '' - return fetch(resource + search, { - method: 'Get', - headers: new Headers( - defaultHeaders - ) - }) - } - }, - maxMetricsMetadata: 99999 + const completeStrategy = newCompleteStrategy({ + remote: { + url: baseUrl + api, + fetchFn: (resource, options = {}) => { + const params = options.body?.toString() + const search = params ? `?${params}` : '' + return fetch(resource + search, { + method: 'Get', + headers: new Headers( + defaultHeaders + ) }) - ) + } + }, + maxMetricsMetadata: 99999 + }) + if (this.type === 'log') { + const autocompleteNodes = { + matchOp: matchOpTerms, + binOp: binOpTerms, + duration: durationTerms, + binOpModifier: [], + atModifier: [], + functionIdentifier: logqlHint.functions, + aggregateOp: logqlHint.aggregateOp, + aggregateOpModifier: aggregateOpModifierTerms, + number: numberTerms + } + completeStrategy.promQL = function (context) { + const { state, pos } = context + const tree = syntaxTree(state).resolve(pos, -1) + const contexts = analyzeCompletion(state, tree) + let asyncResult = Promise.resolve([]) + let completeSnippet = false + const span = true + for (const context of contexts) { + switch (context.kind) { + case ContextKind.Aggregation: + completeSnippet = true + asyncResult = asyncResult.then((result) => { + return result.concat(autocompleteNodes.aggregateOp) + }) + break + case ContextKind.Function: + completeSnippet = true + asyncResult = asyncResult.then((result) => { + return result.concat(autocompleteNodes.functionIdentifier) + }) + break + case ContextKind.BinOpModifier: + asyncResult = asyncResult.then((result) => { + return result.concat(autocompleteNodes.binOpModifier) + }) + break + case ContextKind.BinOp: + asyncResult = asyncResult.then((result) => { + return result.concat(autocompleteNodes.binOp) + }) + break + case ContextKind.MatchOp: + asyncResult = asyncResult.then((result) => { + return result.concat(autocompleteNodes.matchOp) + }) + break + case ContextKind.AggregateOpModifier: + asyncResult = asyncResult.then((result) => { + return result.concat(autocompleteNodes.aggregateOpModifier) + }) + break + case ContextKind.Duration: + span = false + asyncResult = asyncResult.then((result) => { + return result.concat(autocompleteNodes.duration) + }) + break + case ContextKind.Offset: + asyncResult = asyncResult.then((result) => { + return result.concat([{ label: 'offset' }]) + }) + break + case ContextKind.Bool: + asyncResult = asyncResult.then((result) => { + return result.concat([{ label: 'bool' }]) + }) + break + case ContextKind.AtModifiers: + asyncResult = asyncResult.then((result) => { + return result.concat(autocompleteNodes.atModifier) + }) + break + case ContextKind.Number: + asyncResult = asyncResult.then((result) => { + return result.concat(autocompleteNodes.number) + }) + break + case ContextKind.MetricName: + asyncResult = asyncResult.then((result) => { + return this.autocompleteMetricName(result, context) + }) + break + case ContextKind.LabelName: + asyncResult = asyncResult.then((result) => { + return this.autocompleteLabelName(result, context) + }) + break + case ContextKind.LabelValue: + asyncResult = asyncResult.then((result) => { + return this.autocompleteLabelValue(result, context) + }) + } + } + return asyncResult.then((result) => { + return arrayToCompletionResult(result, computeStartCompletePosition(tree, pos), pos, completeSnippet, span) + }) + } + } + function arrayToCompletionResult (data, from, to, includeSnippet = false, span = true) { + const options = data + // options.push(...logqlHint); + return { + from: from, + to: to, + options: options, + validFor: span ? /^[a-zA-Z0-9_:]+$/ : undefined + } + } + // console.log(JSON.stringify(promqlExtension.promQL())) + promqlExtension.activateCompletion(true) + .activateLinter(!(this.type === 'log')) + .setComplete({ + completeStrategy: new HistoryCompleteStrategy(completeStrategy) }) function HistoryCompleteStrategy (CompleteStrategy) { @@ -467,6 +583,7 @@ export default { })), span: /^[a-zA-Z0-9_:]+$/ } + console.log(res.options) // 过滤 非logs的函数 if (res !== null) { historyItems.options = historyItems.options.concat(res.options) @@ -485,6 +602,10 @@ export default { lightTheme ] if (!this.newView) { + let placeholderStr = 'Metrics Expression (press Shift+Enter for newlines)' + if (this.type === 'log') { + placeholderStr = 'Logs Expression (press Shift+Enter for newlines)' + } const EditorViewstate = EditorState.create({ doc: self.codeMirrorValue[self.index], extensions: [ @@ -500,7 +621,7 @@ export default { highlightSelectionMatches(), EditorView.lineWrapping, keymap.of([...closeBracketsKeymap, ...defaultKeymap, ...historyKeymap, ...completionKeymap, ...lintKeymap]), - placeholder('Expression (press Shift+Enter for newlines)'), + placeholder(placeholderStr), dynamicConfigCompartment.of(dynamicConfig), keymap.of([ { @@ -539,7 +660,7 @@ export default { const view = new EditorView({ state: EditorViewstate, // parent: document.getElementById('editor') - parent: document.getElementById(this.pqid + 'editor' + self.index) + parent: document.getElementById(this.pqid + 'editor' + self.index + this.type) }) self.newView = view } else { @@ -1090,14 +1211,35 @@ export default { }) } }, - selectLog (str) { - if (!str || str.length === 0) return - this.expressionList[this.index] = str - this.codeMirrorValue[this.index] = str - this.dropDownVisible = false - this.$emit('change', str) - this.$forceUpdate() - this.cascaderValue = '' + selectLog (item) { + if (!item.id) { + this.metricChangeNew(item.name) + } else if (item.id) { + this.dropDownVisible = false + this.$get('/expression/tmpl/' + item.id).then(res => { + if (res.code === 200) { + if (!res.data.vars || !res.data.vars.length) { + this.metricChange(item.expression) + this.initCodeMirror() + return + } + res.data.vars.forEach(item => { + res.data[item] = '' + const arr = item.split('.') + const keyword = arr[0].toLowerCase() + this.getAllOptins(keyword, keyword + 'Option') + }) + this.tempBox = { + ...this.tempBox, + ...res.data + } + + setTimeout(() => { + this.tempBoxShow = true + }, 100) + } + }) + } } }, watch: { @@ -1150,11 +1292,9 @@ export default { deep: true, immediate: true, handler (n) { - if (n !== 'log') { - this.$nextTick(() => { - this.initCodeMirror() - }) - } + this.$nextTick(() => { + this.initCodeMirror() + }) } } }, diff --git a/nezha-fronted/src/components/page/dashboard/explore/queryPrompt/queryPrompt.vue b/nezha-fronted/src/components/page/dashboard/explore/queryPrompt/queryPrompt.vue index fcbdb79dd..720421f60 100644 --- a/nezha-fronted/src/components/page/dashboard/explore/queryPrompt/queryPrompt.vue +++ b/nezha-fronted/src/components/page/dashboard/explore/queryPrompt/queryPrompt.vue @@ -336,7 +336,7 @@ export default { this.hideMe() }, selectLog () { - this.$emit('selectLog', this.logFinalStr) + this.$emit('selectLog', { name: this.logFinalStr }) this.hideMe() }, copyItem (value) {