NEZ-858 feat: 新版explore-log

This commit is contained in:
chenjinsong
2021-08-02 19:51:53 +08:00
parent 8dd6fa475d
commit 98e9d60fe0
15 changed files with 2010 additions and 976 deletions

View File

@@ -24,7 +24,7 @@
"@topology/sequence-diagram": "^0.3.0", "@topology/sequence-diagram": "^0.3.0",
"axios": "^0.19.0", "axios": "^0.19.0",
"cytoscape": "^3.15.2", "cytoscape": "^3.15.2",
"echarts": "^5.0.1", "echarts": "^5.1.2",
"element-ui": "^2.15.3", "element-ui": "^2.15.3",
"file-saver": "^2.0.2", "file-saver": "^2.0.2",
"leaflet": "^1.7.1", "leaflet": "^1.7.1",

View File

@@ -54,6 +54,7 @@
.top-tool-left { .top-tool-left {
display: flex; display: flex;
} }
}
.top-tool-btn-group { .top-tool-btn-group {
display: flex; display: flex;
.top-tool-btn:not(:last-of-type):not(:first-of-type) { .top-tool-btn:not(:last-of-type):not(:first-of-type) {
@@ -110,7 +111,6 @@
width: auto; width: auto;
min-width: 36px; min-width: 36px;
} }
}
.top-tools--sub { .top-tools--sub {
.top-tool-left { .top-tool-left {
height: 100%; height: 100%;

View File

@@ -706,7 +706,6 @@
} }
} }
.top-tool-btn-group { .top-tool-btn-group {
margin-left: 10px;
display: flex; display: flex;
.top-tool-btn:not(:last-of-type):not(:first-of-type) { .top-tool-btn:not(:last-of-type):not(:first-of-type) {
border-left: none; border-left: none;

View File

@@ -59,7 +59,6 @@ export default {
.chart-unit{ .chart-unit{
width: 100px; width: 100px;
margin: 0 20px 0 0;
} }
.chart-unit.el-cascader .el-input.is-focus .el-input__inner { .chart-unit.el-cascader .el-input.is-focus .el-input__inner {
border-color: #FBCEA4; border-color: #FBCEA4;

View File

@@ -13,6 +13,7 @@ const cn = {
oneDay: '1 天', oneDay: '1 天',
twoDay: '2 天', twoDay: '2 天',
week: '1 周', week: '1 周',
time: '时间',
folder: '文件夹', folder: '文件夹',
key: '键名', key: '键名',
state: '状态', state: '状态',
@@ -64,6 +65,7 @@ const cn = {
other: '其他', other: '其他',
about: '关于', about: '关于',
query: '查询', query: '查询',
logLabels: '日志标签',
account: '账号', account: '账号',
back: '返回', back: '返回',
unavailable: '不可用', unavailable: '不可用',
@@ -114,6 +116,7 @@ const cn = {
}, },
reset: '重置', reset: '重置',
submit: '保存', submit: '保存',
limit: '限制',
noData: '没有数据', noData: '没有数据',
tag: '标签', tag: '标签',
syncChart: '同步图表', syncChart: '同步图表',
@@ -519,6 +522,10 @@ const cn = {
down: 'down', down: 'down',
prometheus: 'prometheus' prometheus: 'prometheus'
} }
},
explore: {
descending: '降序',
wrapLines: '换行'
} }
}, },
validate: { validate: {

View File

@@ -19,8 +19,10 @@ const en = {
oneDay: '1 day', oneDay: '1 day',
twoDay: '2 days', twoDay: '2 days',
week: '1 week', week: '1 week',
time: 'Time',
folder: 'Folder', folder: 'Folder',
key: 'Key', key: 'Key',
logs: 'Logs',
state: 'State', state: 'State',
projectName: 'Project name', projectName: 'Project name',
startTime: 'Start time', startTime: 'Start time',
@@ -69,6 +71,7 @@ const en = {
about: 'About', // 关于 about: 'About', // 关于
detail: 'Detail', // 详情 detail: 'Detail', // 详情
query: 'Query', // 查询 query: 'Query', // 查询
logLabels: 'Log labels',
account: 'Account', account: 'Account',
back: 'Back', // 返回 back: 'Back', // 返回
unavailable: 'Unavailable', unavailable: 'Unavailable',
@@ -118,6 +121,7 @@ const en = {
}, },
reset: 'Reset', reset: 'Reset',
submit: 'Save', submit: 'Save',
limit: 'Limit',
noData: 'No data', noData: 'No data',
tag: 'Tag', tag: 'Tag',
placeHolder: 'Please enter', placeHolder: 'Please enter',
@@ -528,6 +532,10 @@ const en = {
down: 'down', down: 'down',
prometheus: 'prometheus' prometheus: 'prometheus'
} }
},
explore: {
descending: 'Descending',
wrapLines: 'Wrap lines'
} }
}, },
validate: { // 校验规则 validate: { // 校验规则

View File

@@ -1,9 +1,9 @@
<template> <template>
<div class="interval-refresh"> <div class="interval-refresh">
<time-picker v-if="showTimePicker" ref="timePicker" v-model="searchTime" :default-pick="defaultPick" :show-empty="showEmpty" class="time-picker margin-r-10" size="small" @change="dateChange"></time-picker> <time-picker v-if="showTimePicker" ref="timePicker" v-model="searchTime" :default-pick="defaultPick" :show-empty="showEmpty" class="time-picker margin-r-10" size="small" @change="dateChange"></time-picker>
<multipleTime ref="multipleTime" v-if="showMultiple" :stepSearchTime="searchTime" @change="dateChange(searchTime)" class="multiple-time"/> <multipleTime v-if="showMultiple" ref="multipleTime" :stepSearchTime="searchTime" class="multiple-time margin-r-10" @change="dateChange(searchTime)"/>
<chart-unit v-model="unit" v-if="useChartUnit" style="margin-left: 10px"></chart-unit> <chart-unit v-if="useChartUnit" v-model="unit" class="margin-r-10"></chart-unit>
<div v-show="useRefresh" class="top-tool-btn-group margin-r-10"> <div v-show="useRefresh" class="top-tool-btn-group">
<button :id="id+'-refresh'" class="top-tool-btn top-tool-btn--text" @click="refreshDataFunc"> <button :id="id+'-refresh'" class="top-tool-btn top-tool-btn--text" @click="refreshDataFunc">
<i class="global-active-color nz-icon nz-icon-refresh" style="font-size: 14px"></i>&nbsp; <i class="global-active-color nz-icon nz-icon-refresh" style="font-size: 14px"></i>&nbsp;
<span><slot name="added-text"></slot></span> <span><slot name="added-text"></slot></span>

View File

@@ -1,677 +1,41 @@
<template> <template>
<div class="explore list-page"> <div class="explores">
<div class="main-list"> <explore-item
<div class="main-container" style="overflow: auto;"> v-for="item in exploreItems"
<!-- 顶部工具栏 --> :key="item"
<div class="top-tools" style="z-index: 1"> :closable="closable"
<div class="top-tool-left"></div> :tab-index="item"
<div class="top-tool-right"> @split="split"
<pick-time :refresh-data-func="expressionChange" v-model="filterTime" @unitChange="chartUnitChange" ref="pickTime" id="explore"> >
<template slot="added-text">{{$t('dashboard.metricPreview.runQuery')}}</template> </explore-item>
</pick-time>
<button id="explore-save-chart" v-has="'panel_chart_add'" :disabled="saveDisabled" class="top-tool-btn top-tool-btn--text" type="button"
@click="saveChart"
:class="{'nz-btn-disabled btn-disabled-cursor-not-allowed' : saveDisabled}">
{{$t('dashboard.metric.saveChart')}}
</button>
</div>
</div>
<div id="explore-promql-box" class="top-tools" style="padding-top: 0; flex-wrap: wrap">
<!--这个index居然是从1开始-->
<promql-input
v-for="index of promqlKeys.length"
:id="promqlKeys[index-1]"
:key="promqlKeys[index-1]"
:ref="'promql-'+(index-1)"
:expression-list="expressions"
:index="index-1"
:plugins="['metric-selector', 'metric-input', 'add', 'remove', 'copy']"
:styleType="1"
@addExpression="addExpression"
@copyExpression="copyExpression"
@removeExpression="removeExpression"
></promql-input>
</div>
<div ref="scrollWrap" style="height: auto; padding: 0 20px 4px;">
<div v-show="!showIntroduce" class="chart-view"
:class="{'shrink-view':!chartVisible || !defaultChartVisible}" style="position:relative;">
<div class="view-title" @click="changeChartVisible" style="position:absolute;z-index: 1000;top:10px;"><i class="nz-icon nz-icon-caret-top" ></i>&nbsp;graph</div>
<div class="chart-room">
<chart ref="exploreChart" :unit="chartUnit"></chart>
</div>
</div>
<div v-show="!showIntroduce" class="table-view"
:class="{'shrink-view':!tableVisible || !defaultTableVisible}" style="position: relative">
<div class="view-title" style="position:absolute;z-index: 1000;top:4px;" @click="changeTableVisible"><i class="nz-icon nz-icon-caret-top"></i>&nbsp;table</div>
<div class="table-room">
<!-- 自定义table列 -->
<transition name="el-zoom-in-top">
<element-set
class="pop-custom-explore"
v-if="tools.showCustomTableTitle"
@close="tools.showCustomTableTitle = false"
:custom-table-title.sync="tools.customTableTitle"
:original-table-title="tableTitle"
ref="customTableTitle"
></element-set>
</transition>
<el-table class="nz-table explore-table"
:data="tableData"
border
ref="exploreTable"
tooltip-effect="light"
v-loading="tools.loading">
<el-table-column
:resizable="false"
v-for="(item, index) in tools.customTableTitle"
v-if="item.show"
:key="`col-${index}`"
:label="item.label"
:prop="item.prop"
show-overflow-tooltip
min-width="110px"
></el-table-column>
<el-table-column width="28" v-if="tools.customTableTitle.length>0">
<template slot="header" :resizable="false">
<span @mousedown.stop="!tools.showCustomTableTitle && (tools.showCustomTableTitle = true)" class="nz-table-gear">
<i class="nz-icon nz-icon-gear"></i>
</span>
</template>
</el-table-column>
</el-table>
<pagination :page-obj="pageObj" @pageNo='pageNo' @pageSize='pageSize' ref="Pagination"
:append-to-body="false"></pagination>
</div>
</div>
<div v-show="showIntroduce" class="introduce-view">
<div class="info-room">
<div class="col-md-9 doc-content">
<h1 class="page-header">Query examples<a class="header-anchor" href="https://prometheus.io/docs/prometheus/latest/querying/examples/" target="_blank" rel="noopener noreferrer"><i style="font-size: 16px;" class="nz-icon nz-icon-link1"></i></a></h1>
<div class="content-divider"></div>
<h2 >
Simple time series selection
</h2>
<p>Return all time series with the metric <code>http_requests_total</code>:</p>
<pre><code>http_requests_total</code></pre>
<p>Return all time series with the metric <code>http_requests_total</code> and the given<code>job</code> and <code>handler</code> labels:</p>
<pre><code>http_requests_total{job="apiserver", handler="/api/comments"}</code></pre>
<p>Return a whole range of time (in this case 5 minutes) for the same vector,
making it a range vector:</p>
<pre><code>http_requests_total{job="apiserver", handler="/api/comments"}[5m]</code></pre>
<p>Note that an expression resulting in a range vector cannot be graphed directly,
but viewed in the tabular ("Console") view of the expression browser.</p>
<p>Using regular expressions, you could select time series only for jobs whose
name match a certain pattern, in this case, all jobs that end with <code>server</code>:</p>
<pre><code>http_requests_total{job=~".*server"}</code></pre>
<p>All regular expressions in Prometheus use RE2 syntax.</p>
<p>To select all HTTP status codes except 4xx ones, you could run:</p>
<pre><code>http_requests_total{status!~"4.."}</code></pre>
<h2 >
Subquery
</h2>
<p>Return the 5-minute rate of the <code>http_requests_total</code> metric for the past 30 minutes, with a resolution of 1 minute.</p>
<pre><code>rate(http_requests_total[5m])[30m:1m]</code></pre>
<p>This is an example of a nested subquery. The subquery for the <code>deriv</code> function uses the default resolution. Note that using subqueries unnecessarily is unwise.</p>
<pre><code>max_over_time(deriv(rate(distance_covered_total[5s])[30s:5s])[10m:])</code></pre>
<h2 >
Using functions, operators, etc.
</h2>
<p>Return the per-second rate for all time series with the <code>http_requests_total</code>
metric name, as measured over the last 5 minutes:</p>
<pre><code>rate(http_requests_total[5m])</code></pre>
<p>Assuming that the <code>http_requests_total</code> time series all have the labels <code>job</code>
(fanout by job name) and <code>instance</code> (fanout by instance of the job), we might
want to sum over the rate of all instances, so we get fewer output time series,
but still preserve the <code>job</code> dimension:</p>
<pre><code>sum by (job) (rate(http_requests_total[5m]))</code></pre>
<p>If we have two different metrics with the same dimensional labels, we can apply
binary operators to them and elements on both sides with the same label set
will get matched and propagated to the output. For example, this expression
returns the unused memory in MiB for every instance (on a fictional cluster
scheduler exposing these metrics about the instances it runs):</p>
<pre><code>(instance_memory_limit_bytes - instance_memory_usage_bytes) / 1024 / 1024</code></pre>
<p>The same expression, but summed by application, could be written like this:</p>
<pre><code>sum by (app, proc) (instance_memory_limit_bytes - instance_memory_usage_bytes) / 1024 / 1024</code></pre>
<p>If the same fictional cluster scheduler exposed CPU usage metrics like the following for every instance:</p>
<pre><code>instance_cpu_time_ns{app="lion", proc="web", rev="34d0f99", env="prod", job="cluster-manager"}
instance_cpu_time_ns{app="elephant", proc="worker", rev="34d0f99", env="prod", job="cluster-manager"}
instance_cpu_time_ns{app="turtle", proc="api", rev="4d3a513", env="prod", job="cluster-manager"}
instance_cpu_time_ns{app="fox", proc="widget", rev="4d3a513", env="prod", job="cluster-manager"}
...
</code></pre>
<p>...we could get the top 3 CPU users grouped by application (<code>app</code>) and process type (<code>proc</code>) like this:</p>
<pre><code>topk(3, sum by (app, proc) (rate(instance_cpu_time_ns[5m])))</code></pre>
<p>Assuming this metric contains one time series per running instance, you could count the number of running instances per application like this:</p>
<pre><code>count by (app) (instance_cpu_time_ns)</code></pre>
</div>
</div>
</div>
</div>
</div>
</div>
<transition name="right-box">
<chart-box :chart="chart" :from="$CONSTANTS.fromRoute.explore" :panel-data="panelData" :show-panel="{id: -1, name: '', type: 'explore'}" @close="handleBox(false)" @on-create-success="createSuccess" ref="addChartModal" v-if="rightBox.show"></chart-box>
</transition>
</div> </div>
</template> </template>
<script> <script>
import bus from '../../../../libs/bus' import exploreItem from '@/components/page/dashboard/explore/exploreItem'
import promqlInput from './promqlInput'
// import promqlInputPlus from "./promqlInputPlus";
import chart from '../overview/chart'
import axios from 'axios'
import chartBox from '../../../page/dashboard/chartBox'
import { getUUID } from '../../../common/js/common'
import chartDataFormat from '../../../charts/chartDataFormat'
export default { export default {
name: 'explore', name: 'explore',
components: { components: {
'promql-input': promqlInput, exploreItem
chart: chart,
'chart-box': chartBox
}, },
data () { data () {
return { return {
rightBox: { // 面板弹出框相关 exploreItems: [0]
show: false
},
promqlCount: 1,
promqlKeys: [],
expressions: [''],
filterTime: [
bus.timeFormate(bus.getOffsetTimezoneData(-1), 'yyyy-MM-dd hh:mm:ss'),
bus.timeFormate(bus.getOffsetTimezoneData(), 'yyyy-MM-dd hh:mm:ss')
],
/* 工具参数 */
tools: {
loading: false, // 是否显示table加载动画
showCustomTableTitle: false, // 自定义列弹框是否显示
customTableTitle: [] // 自定义列工具的数据
},
tableTitle: [],
showIntroduce: true,
defaultChartVisible: true,
defaultTableVisible: true,
chartVisible: true,
tableVisible: true,
pageObj: {
pageNo: 1,
pageSize: this.$CONSTANTS.defaultPageSize,
total: 0
},
tableData: [],
saveDisabled: true,
panelData: [],
chartUnit: 0,
historyParam: { useHistory: true, key: 'expore-history' },
chart: {},
metricOptions: []
} }
}, },
created () { computed: {
this.getPanelData() closable () {
this.queryMetrics() return this.exploreItems.length === 2
this.promqlKeys.push(getUUID()) }
}, },
methods: { methods: {
pageNo (val) { split (index) {
this.pageObj.pageNo = val const tabIndex = this.exploreItems.indexOf(index)
this.tableData = this.filterShowData(this.storedTableData, this.pageObj) if (tabIndex > -1) {
}, if (this.exploreItems.length === 2) {
pageSize (val) { this.exploreItems.splice(tabIndex, 1)
this.pageObj.pageSize = val
this.tableData = this.filterShowData(this.storedTableData, this.pageObj)
},
filterShowData (source, pageObj) {
return source.slice((pageObj.pageNo - 1) * pageObj.pageSize, pageObj.pageNo * pageObj.pageSize)
},
chartUnitChange: function (unit) {
this.chartUnit = unit
this.$nextTick(() => {
this.expressionChange()
})
},
queryChartData: function () {
this.$refs.exploreChart.startLoading()
setTimeout(() => {
if (this.expressions.length > 0) {
const requestArr = []
const promqlInputIndexs = []
const queryExpression = []
this.expressions.forEach((item, index) => {
if (item != '') {
const step = bus.getStep(this.filterTime[0], this.filterTime[1])
promqlInputIndexs.push(index)
queryExpression.push(item)
requestArr.push(this.$get('/prom/api/v1/query_range?query=' + item + '&start=' + this.$stringTimeParseToUnix(this.filterTime[0]) + '&end=' + this.$stringTimeParseToUnix(this.filterTime[1]) + '&step=' + step))
}
})
if (requestArr.length > 0) {
this.showIntroduce = false
this.saveDisabled = false
}
axios.all(requestArr).then(res => {
const series = []
const legend = []
if (res.length > 0) {
res.forEach((response, index) => {
const promqlIndex = promqlInputIndexs[index]
if (response.data && response.status == 'success') {
const data = response.data.result
if ((!data || data.length < 1) && response.message) {
this.$refs['promql-' + promqlIndex][0].setError(response.message)
return
}
data.forEach((result, i) => {
const seriesItem = {
name: '',
symbol: 'emptyCircle', // 去掉点
symbolSize: [2, 2],
showSymbol: false,
smooth: 0.2, // 曲线变平滑
data: [],
lineStyle: {
width: 1,
opacity: 0.9
},
type: 'line'
}
let legendName = ''
seriesItem.data = result.values.map((item) => {
return [item[0] * 1000, item[1]]
})
if (result.metric && Object.keys(result.metric).length > 0) {
const metric = Object.assign({}, result.metric)
seriesItem.name += metric.__name__ ? metric.__name__ : ''
seriesItem.name += '{'
delete metric.__name__
for (const key in metric) {
seriesItem.name += key + '=' + '"' + metric[key] + '",'
}
legendName = seriesItem.name.substr(0, seriesItem.name.length - 1)
legendName += '}'
} else { } else {
legendName = queryExpression[index] this.exploreItems.push(index === 0 ? 1 : 0)
}
seriesItem.name = legendName + '-' + index
series.push(seriesItem)
legend.push({ name: seriesItem.name, alias: legendName, isGray: false })
})
this.$refs['promql-' + promqlIndex][0].setError('')
} else {
// console.log(response)
this.$refs['promql-' + promqlIndex][0].setError(response.error)
}
})
this.$refs.exploreChart.setLegend(legend)
this.$refs.exploreChart.setRandomColors(series.length)
this.$refs.exploreChart.setSeries(series)
this.defaultChartVisible = true
}
this.$refs.exploreChart.endLoading()
})
}
}, 200)
},
queryTableData () {
this.tools.loading = true
setTimeout(() => {
if (this.expressions.length > 0) {
const requestArr = []
this.expressions.forEach((item, index) => {
if (item !== '') {
requestArr.push(this.$get('/prom/api/v1/query?query=' + item))
}
})
if (requestArr.length > 0) {
this.showIntroduce = false
}
axios.all(requestArr).then(res => {
const tData = []
const tLabels = []
if (res.length > 0) {
this.tableData = []
this.tableTitle = []
res.forEach((response, index) => {
if (response.data && response.status === 'success') {
const data = response.data.result
if (data) {
data.forEach((result, i) => {
const metrics = Object.assign({}, result.metric)
if (!Array.isArray(result.value)) {
const val = result
result = {
value: ['', val]
}
}
this.$set(metrics, 'value#' + index, chartDataFormat.getUnit(this.chartUnit).compute(result.value[1], null, 2))
for (const key in metrics) {
const label = {
label: key === '__name__' ? 'metric' : key,
prop: key,
show: true
}
const temp = tLabels.find((item, index) => {
return item.prop == label.prop
})
if (!temp) {
tLabels.push(label)
}
}
tData.push(metrics)
})
}
}
tLabels.sort((a, b) => {
return a.prop.charCodeAt(0) - b.prop.charCodeAt(0)
})
})
if (tData.length > 0) {
this.storedTableData = Object.assign([], tData)
this.pageObj.total = this.storedTableData.length
this.tableData = this.filterShowData(this.storedTableData, this.pageObj)
this.tableTitle = Object.assign([], tLabels)
this.tools.customTableTitle = Object.assign([], tLabels)
this.defaultTableVisible = true
} else {
// this.defaultTableVisible = false;
}
}
this.tools.loading = false
})
}
}, 200)
},
expressionChange: function () {
const nowTimeType = this.$refs.pickTime.$refs.timePicker.nowTimeType
// console.log(this.filterTime,nowTimeType);
this.setSearchTime(nowTimeType.type, nowTimeType.value)
if (this.expressions && this.expressions.length >= 1) {
this.queryTableData()
this.queryChartData()
this.storeHistory()
}
},
setSearchTime (type, val) { // 设置searchTime
if (type === 'minute') {
const startTime = bus.timeFormate(new Date(bus.computeTimezone(new Date().getTime())).setMinutes(new Date(bus.computeTimezone(new Date().getTime())).getMinutes() - val), 'yyyy-MM-dd hh:mm:ss')
const endTime = bus.timeFormate(new Date(bus.computeTimezone(new Date().getTime())), 'yyyy-MM-dd hh:mm:ss')
this.$set(this.filterTime, 0, startTime)
this.$set(this.filterTime, 1, endTime)
this.$set(this.filterTime, 2, val + 'm')
} else if (type === 'hour') {
const startTime = bus.timeFormate(new Date(bus.computeTimezone(new Date().getTime())).setHours(new Date(bus.computeTimezone(new Date().getTime())).getHours() - val), 'yyyy-MM-dd hh:mm:ss')
const endTime = bus.timeFormate(new Date(bus.computeTimezone(new Date().getTime())), 'yyyy-MM-dd hh:mm:ss')
this.$set(this.filterTime, 0, startTime)
this.$set(this.filterTime, 1, endTime)
this.$set(this.filterTime, 2, val + 'h')
} else if (type === 'date') {
const startTime = bus.timeFormate(new Date(bus.computeTimezone(new Date().getTime())).setDate(new Date(bus.computeTimezone(new Date().getTime())).getDate() - val), 'yyyy-MM-dd hh:mm:ss')
const endTime = bus.timeFormate(new Date(bus.computeTimezone(new Date().getTime())), 'yyyy-MM-dd hh:mm:ss')
this.$set(this.filterTime, 0, startTime)
this.$set(this.filterTime, 1, endTime)
this.$set(this.filterTime, 2, val + 'd')
}
this.$refs.pickTime.$refs.timePicker.searchTime = this.filterTime
},
storeHistory: function () {
const expire = 24
const historyJson = localStorage.getItem(this.historyParam.key)
const expressions = this.expressions.filter(item => {
return item && item != ''
})
const username = sessionStorage.getItem('nz-username')
if (historyJson && historyJson != 'undefined' && historyJson != '') {
const historyObj = JSON.parse(historyJson)
let history = historyObj[username]
if (history) {
// 过滤过期表达式
history = history.filter(item => {
return item.time + item.expire >= new Date().getTime()
})
let repeat = history.filter(item => {
return expressions.includes(item.insertText)
})
const old = history.filter(item => {
return !expressions.includes(item.insertText)
})
const freshExpression = expressions.filter(item => {
const find = history.find(t => { return t.insertText == item })
return !find
})
repeat = repeat.map(item => {
item.time = new Date().getTime()
item.num += 1
item.documentation = this.$t('dashboard.metricPreview.historyTip', { time: item.num, hour: 24 })
return item
})
const fresh = freshExpression.map(item => {
return {
label: item,
insertText: item,
documentation: this.$t('dashboard.metricPreview.historyTip', { time: 1, hour: 24 }),
num: 1,
time: new Date().getTime(),
expire: expire * 60 * 60 * 1000
}
})
historyObj[username] = fresh.concat(repeat).concat(old)
} else {
const history = expressions.map(item => {
return {
label: item,
insertText: item,
documentation: this.$t('dashboard.metricPreview.historyTip', { time: 1, hour: 24 }),
num: 1,
time: new Date().getTime(),
expire: expire * 60 * 60 * 1000
}
})
historyObj[username] = history
}
localStorage.setItem(this.historyParam.key, JSON.stringify(historyObj))
} else {
const history = expressions.map(item => {
return {
label: item,
insertText: item,
documentation: this.$t('dashboard.metricPreview.historyTip', { time: 1, hour: 24 }),
num: 1,
time: new Date().getTime(),
expire: expire * 60 * 60 * 1000
}
})
if (history && history.length > 0) {
const stored = {}
stored[username] = history
localStorage.setItem(this.historyParam.key, JSON.stringify(stored))
}
}
},
addExpression (index) {
this.expressions.splice(index + 1, 0, '')
this.promqlKeys.splice(index + 1, 0, getUUID())
this.promqlCount++
},
copyExpression (index) {
this.expressions.push(this.expressions[index])
this.promqlKeys.push(getUUID())
this.promqlCount++
},
removeExpression (index) {
if (this.promqlCount > 1) {
this.expressions.splice(index, 1)
this.promqlKeys.splice(index, 1)
this.promqlCount--
}
},
changeChartVisible: function () {
this.chartVisible = !this.chartVisible
},
changeTableVisible: function () {
this.tableVisible = !this.tableVisible
},
handleBox (show) {
this.rightBox.show = show
},
saveChart () {
const chart = {
name: '',
type: 'line',
span: 12,
height: '400',
unit: this.chartUnit,
param: {
url: '',
threshold: ''
},
elements: [],
panel: '',
sync: 0,
groupId: '',
remark: ''
}
this.expressions.forEach((exp) => {
chart.elements.push({ expression: exp, legend: '', type: 'expert', id: '' })
})
this.chart = chart
this.rightBox.show = true
},
createSuccess (type, response, param, panel) { // 添加chart成功
this.$confirm(this.$t('dashboard.metric.goPanelTip'), this.$t('tip.saveSuccess'), {
confirmButtonText: this.$t('tip.yes'),
cancelButtonText: this.$t('tip.no'),
type: 'success'
}).then(() => {
bus.$emit('menu-change', 'panel')
this.$store.commit('panelShowPanelChange', panel)
this.$router.push({
path: '/panel',
query: {
t: +new Date()
}
})
})
},
getPanelData () { // 获取panel数据
this.$get('visual/panel?pageNo=1&pageSize=-1').then(response => {
if (response.code === 200) {
this.panelData = response.data.list
}
})
},
jumpTo (data, id) {
bus.$emit('menu-change', data)
this.$router.push({
path: '/' + data,
query: {
t: +new Date()
}
})
},
queryMetrics () {
this.metricOptions = []
this.$get('prom/api/v1/label/__name__/values').then(response => {
if (response.status == 'success') {
const metrics = response.data.sort()
const metricMap = new Map()
metrics.forEach((item) => {
let key = ''
if (/^[a-zA-Z]+?_[a-zA-Z]*/.test(item)) {
key = item.split('_')[0]
} else if (/^_\w*/.test(item)) {
key = ' '
} else {
key = item
}
if (metricMap.get(key)) {
const values = metricMap.get(key)
values.push({ label: item, value: item })
} else {
const values = [{ label: item, value: item }]
metricMap.set(key, values)
}
// this.metricStore.push({label:item,value:item,insertText:item})
})
for (const key of metricMap.keys()) {
const option = {
label: key,
value: key
}
if (metricMap.get(key) && metricMap.get(key).length > 1) {
option.children = metricMap.get(key)
}
this.metricOptions.push(option)
}
}
})
},
getMetricOptions () {
return this.metricOptions
}
},
watch: {
promqlCount: function (n, o) {
this.expressionChange()
},
expressions: {
immediate: true,
handler: function (n, o) {
if (n.length == 1 && (!n[0] || n[0] == '')) {
this.showIntroduce = true
} else if (n.length > 1) {
const temp = n.find((item, index) => {
return item != ''
})
if (!temp) {
this.showIntroduce = true
} else {
this.showIntroduce = false
}
} else {
this.showIntroduce = false
} }
} }
} }
@@ -679,248 +43,11 @@ export default {
} }
</script> </script>
<style scoped lang="scss"> <style scoped>
.explore { .explores {
height: 100%; display: flex;
} padding: 10px 0 10px 10px;
background-color: #f6f6f6;
.explore .chart-room { box-sizing: border-box;
width: 100%;
height: 400px
}
.explore .chart-view, .table-view {
padding: 22px 10px 10px 10px;
border: 1px solid lightgrey;
box-sizing: inherit;
margin-bottom: 5px;
transition: height 1s;
}
.explore .chart-view:hover,.explore .table-view:hover {
cursor: default;
}
.shrink-view {
height: 30px;
.view-title i {
transform: rotate(180deg);
}
.chart-room, .table-room {
height: 0px;
visibility: hidden;
}
}
.table-room {
position: relative;
margin-top: 10px;
}
.explore .view-title {
font-weight: 500;
margin-right: 8px;
font-size: 14px;
box-shadow: none;
}
.introduce-view .info-room {
padding: 24px;
background-color: #e9edf2;
border-top: 3px solid #3274d9;
-webkit-box-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, .1), 1px 1px 0 0 rgba(0, 0, 0, .1);
box-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, .1), 1px 1px 0 0 rgba(0, 0, 0, .1);
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
-ms-flex-positive: 1;
flex-grow: 1;
}
.info-room .cheat-sheet-item__title {
font-size: 21px;
}
.info-room .cheat-sheet-item__label {
font-size: 13px;
}
.info-room code {
font-family: Menlo, Monaco, Consolas, Courier New, monospace;
font-size: 11px;
background-color: #e9edf2;
color: #52545c;
border: 1px solid #c7d0d9;
border-radius: 4px;
}
/*外部引用 样式start*/
.doc-content {
font-size: 16px;
}
.doc-content p, .doc-content.ul, .doc-content .alert {
margin: 15px 0 15px 0;
line-height: 1.5;
}
.doc-content .content-divider{
height: 1px;
width:100%;
border-bottom: 2px solid #C0C4CC;
margin: 5px 0px;
}
.doc-content > h1 {
color: #e6522c;
font-size: 30px;
text-transform: uppercase;
}
.doc-content > h1 a {
color: #000 !important;
}
.doc-content.blog > h1 {
text-transform: none;
}
.doc-content.blog .sponsor-logos > a > img {
width: 250px;
display: inline-block !important;
margin: 15px 55px;
}
.doc-content > h1 {
color: #e6522c;
font-size: 22px;
}
.doc-content > h2 {
color: #e6522c;
font-size: 18px;
}
.doc-content > h2 code {
color: #e6522c;
background: none;
}
.doc-content > h3 {
font-size: 20px;
font-weight: bold;
}
.doc-content > h4 {
font-weight: bold;
font-size: 18px;
margin-top: 20px;
}
.doc-content a.header-anchor {
padding-left: 15px;
color: gray;
text-decoration: none;
}
.doc-content a.header-anchor:link,
.doc-content a.header-anchor:visited {
/*visibility: hidden;*/
}
.doc-content h1:hover a.header-anchor:hover,
.doc-content h2:hover a.header-anchor:hover,
.doc-content h3:hover a.header-anchor:hover,
.doc-content h4:hover a.header-anchor:hover,
.doc-content h5:hover a.header-anchor:hover,
.doc-content h6:hover a.header-anchor:hover {
/*color: #000;*/
}
.doc-content h1:hover a.header-anchor,
.doc-content h2:hover a.header-anchor,
.doc-content h3:hover a.header-anchor,
.doc-content h4:hover a.header-anchor,
.doc-content h5:hover a.header-anchor,
.doc-content h6:hover a.header-anchor {
/*color: #999;*/
/*visibility: visible;*/
}
.doc-content img {
width: 90%;
margin-left: auto;
margin-right: auto;
display: block;
}
.doc-content img.orig-size {
width: auto;
margin-left: 0;
}
.doc-content .open-source-notice {
color: #666;
background-color: #f5f5f5;
text-align: center;
padding: 0.8em;
margin-top: 1.5em;
}
.toc {
padding: 1em;
background-color: #f5f5f5;
}
.toc-right {
float: right;
width: 40%;
margin: 0 0 0.5em 0.5em;
}
.toc ul {
padding: 0 0 0 1.5em;
margin: 0;
}
.toc a code {
color: #337ab7;
background-color: transparent;
}
pre {
border: 1px solid #ddd;
border-left: 4px solid #e6522c;
border-radius: 0;
font-family: "Courier New", Monaco, Menlo, Consolas, monospace;
background-color: #f5f5f5;
color: #333;
padding: 15px;
}
pre code {
white-space: pre;
}
code {
color: #333;
}
aside {
color: #888;
padding-bottom: 8px;
border-bottom: 1px solid #aaa;
}
article {
margin: 10px 0 60px 0;
}
/*外部引用 样式end*/
</style>
<style lang="scss">
.explore-table tr td .cell {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.explore .promqlInput:not(:first-of-type) {
margin-top: 10px;
} }
</style> </style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,377 @@
<template>
<div class="log-detail">
<div id="logChart" class="log-chart">
<!-- <chart ref="logChart" :unit="unit" chart-type="logBar" :show-toolbox="false"></chart>-->
</div>
<div class="log-operations">
<div class="log-operation">
<span class="operation-label">{{$t('overall.time')}}</span>
<el-switch
v-model="time"
:active-color="theme.themeColor"
>
</el-switch>
</div>
<div class="log-operation">
<span class="operation-label">{{$t('dashboard.explore.descending')}}</span>
<el-switch
v-model="operations.descending"
:active-color="theme.themeColor"
>
</el-switch>
</div>
<div class="log-operation">
<span class="operation-label">{{$t('dashboard.explore.wrapLines')}}</span>
<el-switch
v-model="wrapLines"
:active-color="theme.themeColor"
>
</el-switch>
</div>
<div class="log-operation">
<span class="operation-label">{{$t('overall.limit')}}:</span>
<el-select v-model="operations.limit" size="small" style="width: 100px;">
<el-option v-for="option in limitOptions" :key="option" :value="option"></el-option>
</el-select>
</div>
<div class="log-operation">
<span class="operation-label">Result:</span>
<span>{{tableData.length}}</span>
</div>
<div class="log-operation log-operation--right">
<button class="top-tool-btn" type="button" @click="exportLog"><i class="nz-icon nz-icon-download"></i></button>
</div>
</div>
<div class="log-table">
<el-table
:cell-class-name="wrapLines ? '': 'log-row-wrap--no-wrap'"
:data="tableData"
:show-header="false"
class="nz-table2"
size="mini"
>
<el-table-column
type="expand"
>
<template slot-scope="{ row }">
<pre>{{row.labels}}</pre>
</template>
</el-table-column>
<el-table-column
v-if="time"
prop="timestamp"
width="140"
>
<template slot-scope="{ row }">{{row.timestamp}}</template>
</el-table-column>
<el-table-column
prop="message"
>
</el-table-column>
</el-table>
</div>
</div>
</template>
<script>
import * as echarts from 'echarts'
import axios from 'axios'
export default {
name: 'logTab',
props: {
unit: Number,
logData: Array
},
computed: {
tableTimeFormat () {
return this.timeFormat
}
},
data () {
return {
operations: {
levels: [0, 1, 2, 3, 4, 5, 6],
limit: 1000,
descending: true
},
time: true, // 换行和时间不需要处理数据
wrapLines: true,
limitOptions: [300, 1000, 3000, 10000],
levelOptions: [
{
type: 'trace',
keywords: ['trace'],
color: '#6ed0e0'
},
{
type: 'debug',
keywords: ['debug', 'dbug'],
color: '#1f78c1'
},
{
type: 'info',
keywords: ['info', 'information', 'informational', 'notice'],
color: '#7eb26d'
},
{
type: 'warn',
keywords: ['warn', 'warning'],
color: '#ff851b'
},
{
type: 'error',
keywords: ['error', 'err'],
color: '#e24d42'
},
{
type: 'fatal',
keywords: ['emerg', 'critical', 'fatal', 'crit'],
color: '#705da0'
},
{
type: 'unknown',
keywords: [],
color: '#dde4ed'
}
],
tableData: [],
timeFormatData: [],
tableChartData: [],
myChart: null
}
},
methods: {
exportLog () {
this.$emit('exportLog', this.operations)
},
timeFormat (timestamp) {
const timeLength = `${timestamp}`.length
// 判断时间是秒/毫秒/微秒/纳秒
let step = null
switch (timeLength) {
case 10:
step = 1000
break
case 13:
step = 1
break
case 16:
step = 0.001
break
case 19:
step = 0.000001
break
default: break
}
return this.utcTimeToTimezoneStr(timestamp * step)
},
loadChart () {
this.myChart = echarts.init(document.getElementById('logChart'))
if (this.tableChartData.length > 0) {
let series = this.tableChartData.map(d => ({
type: 'bar',
name: d.name,
stack: 'total',
barWidth: 6,
data: d.data.map(item => [item[0], item[1]]),
itemStyle: { color: this.levelOptions.find(l => l.type === d.name).color }
}))
series = series.sort((a, b) => {
return this.levelOptions.findIndex(l => a.name === l.type) - this.levelOptions.findIndex(l => b.name === l.type)
})
const option = {
title: {
show: false
},
grid: {
top: 20,
left: 10,
right: 10,
bottom: 60,
containLabel: true
},
legend: {
bottom: 20
},
series,
xAxis: {
type: 'time',
axisTick: { show: false },
axisLabel: {
rotate: 0,
fontSize: 13 * window.devicePixelRatio,
formatter: '{HH}:{mm}:{ss}'
}
},
tooltip: {
trigger: 'axis'
},
yAxis: {
type: 'value',
axisLabel: {
fontSize: 13 * window.devicePixelRatio,
formatter (value, i) {
let y
if (value < 1000) {
y = value
} else if (value < 1000000) {
y = value / 1000 + ' K'
} else if (value >= 1000000) {
y = value / 1000000 + ' M'
} else {
y = value / 1000000000 + ' G'
}
return y
}
}
},
useUTC: false // 使用本地时间
}
this.myChart.setOption(option)
}
},
applyFilter (allTableData, filter) {
let data = [...allTableData]
// 过滤level
data = data.filter(d => {
const hit = filter.levels.some(l => {
return this.levelOptions[l].type === d.level.toLowerCase()
})
return hit
})
// limit
data = data.slice(0, filter.limit)
// 升降序
data = data.sort((a, b) => {
return filter.descending ? b.timestamp - a.timestamp : a.timestamp - b.timestamp
})
// logs内部上方的图表数据
const tableChartData = {}
data.forEach(d => {
tableChartData[d.level] || (tableChartData[d.level] = {})
tableChartData[d.level][`${d.timestamp}`] ? tableChartData[d.level][`${d.timestamp}`]++ : tableChartData[d.level][`${d.timestamp}`] = 1
})
const temp = []
for (const d in tableChartData) {
const level = { name: d, data: [] }
for (const time in tableChartData[d]) {
level.data.push([time, tableChartData[d][time]])
}
temp.push(level)
}
return { tableData: data, tableChartData: temp }
},
filterLogType (data) {
const logData = data.filter(l => l.resultType === 'streamsFormat')
let allTableData = []
// 合并
logData.forEach(d => {
allTableData = [...allTableData, ...d.result]
})
// 去重
const temp = []
allTableData = allTableData.reduce((cur, next) => {
if (temp.indexOf(next.uuid) === -1) {
temp.push(next.uuid)
cur.push(next)
}
return cur
}, [])
// 把时间调整为毫秒,并合并同毫秒的数据
allTableData = allTableData.map(d => ({
...d,
timestamp: this.timeFormat(d.timestamp)
}))
return { logData: allTableData }
}
},
watch: {
logData: {
deep: true,
immediate: true,
handler (n, o) {
const { logData } = this.filterLogType(n) // 过滤出不同resultType合并去重
const { tableData, tableChartData } = this.applyFilter(logData, this.operations) // 应用operation区域的过滤项
this.tableData = tableData
this.tableChartData = tableChartData
this.loadChart()
}
},
operations: {
deep: true,
handler (n, o) {
const { logData } = this.filterLogType(this.logData)
const { tableData, tableChartData } = this.applyFilter(logData, this.operations) // 应用operation区域的过滤项
this.tableData = tableData
this.tableChartData = tableChartData
this.loadChart()
}
}
}
}
</script>
<style lang="scss">
.log-detail {
* {
box-sizing: border-box;
}
.log-table .nz-table2 {
padding: 10px 0 0 0;
.el-table__body {
border-collapse: separate;
border-spacing: 0 6px;
td {
vertical-align: top;
padding: 1px 0;
border: none;
}
td.el-table__expanded-cell {
padding: 0 0 0 60px;
background-color: #fafafa;
}
// 左侧边框
td:first-child {
border-left: 3px solid $--right-box-border-color;
}
td.el-table__expanded-cell:first-child {
border-left: none;
}
}
}
.log-chart {
height: 300px;
width: 100%;
}
.log-operations {
display: flex;
align-items: center;
height: 50px;
width: 100%;
padding: 0 10px 0 20px;
border: 1px solid #E4E8EB;
border-radius: 2px;
.log-operation {
display: flex;
align-items: center;
&.log-operation--right {
margin-left: auto;
}
&:not(:last-of-type) {
margin-right: 30px;
}
.operation-label {
padding-right: 10px;
color: #666666;
}
}
}
}
</style>

View File

@@ -6,11 +6,11 @@
<div v-if="plugins.indexOf('metric-selector') > -1"> <div v-if="plugins.indexOf('metric-selector') > -1">
<el-dropdown class="metric-selector"> <el-dropdown class="metric-selector">
<el-dropdown-menu style="display: none"></el-dropdown-menu> <el-dropdown-menu style="display: none"></el-dropdown-menu>
<button class="top-tool-btn top-tool-btn--text" type="button" @click="toggleDropdown">{{ $t("overall.metric") }} <button class="top-tool-btn top-tool-btn--text" type="button" @click="toggleDropdown">{{type === 'log' ? $t("overall.logLabels") : $t("overall.metric") }}
&nbsp;<i class="nz-icon nz-icon-arrow-down" style="font-size: 12px"></i></button> &nbsp;<i class="nz-icon nz-icon-arrow-down" style="font-size: 12px"></i></button>
<el-cascader-panel v-show="dropDownVisible" ref="metricSelector" slot="dropdown" v-model="cascaderValue" <el-cascader-panel v-show="dropDownVisible" ref="metricSelector" slot="dropdown" v-model="cascaderValue"
v-clickoutside="closeDropdown" v-loading="tempBoxShowLoading" :loading="loading" :options="metricOptions" v-clickoutside="closeDropdown" v-loading="tempBoxShowLoading" :loading="loading" :options="metricOptions"
:props="{emitPath:false,}" @change="metricChangeNew"> v-if="type !== 'log'" :props="cascaderProps" @change="metricChangeNew">
<template slot-scope="{ node, data }"> <template slot-scope="{ node, data }">
<div :class="['nz-cascade',data.temp&&!data.child?'nz-cascade-temp':'']" @click="()=>{lazyLoad(node,data)}" :title="data.label"> <div :class="['nz-cascade',data.temp&&!data.child?'nz-cascade-temp':'']" @click="()=>{lazyLoad(node,data)}" :title="data.label">
@@ -18,7 +18,15 @@
{{data.label}} {{data.label}}
</div> </div>
</template> </template>
</el-cascader-panel>
<el-cascader-panel v-else v-show="dropDownVisible" ref="metricSelector" slot="dropdown"
v-model="cascaderValue" v-clickoutside="closeDropdown" v-loading="tempBoxShowLoading"
:loading="loading" :props="cascaderProps" @change="logLabelChange">
<template slot-scope="{ node, data }">
<div :title="data.label" class="nz-cascade">
{{data.label}}
</div>
</template>
</el-cascader-panel> </el-cascader-panel>
</el-dropdown> </el-dropdown>
</div> </div>
@@ -146,6 +154,7 @@
<script> <script>
import selectAlertSilence from '../../../common/alert/selectAlertSilence' import selectAlertSilence from '../../../common/alert/selectAlertSilence'
import editor from './editor' import editor from './editor'
import { get } from '@/http'
export default { export default {
name: 'promqlInput', name: 'promqlInput',
components: { components: {
@@ -176,6 +185,9 @@ export default {
fromFatherData: { fromFatherData: {
type: Boolean, type: Boolean,
default: false default: false
},
type: {
type: String // metric和log两种为空时视为metric
} }
// metricOptions: {type: Array}, // metricOptions: {type: Array},
// metricStore: {type: Array} // metricStore: {type: Array}
@@ -206,6 +218,47 @@ export default {
loading: false loading: false
} }
}, },
computed: {
cascaderProps () {
if (this.type === 'log') {
return {
lazy: true,
lazyLoad (node, resolve) {
const { level } = node
if (level === 0) {
get('/logs/loki/api/v1/labels').then(res => {
if (res.data) {
const nodes = res.data.sort().map(d => ({
value: d,
label: d,
leaf: false
}))
resolve(nodes)
} else {
resolve([])
}
})
} else if (level === 1) {
get(`/logs/loki/api/v1/label/${node.value}/values`).then(res => {
if (res.data) {
const nodes = res.data.sort().map(d => ({
value: d,
label: d,
leaf: true
}))
resolve(nodes)
} else {
resolve([])
}
})
}
}
}
} else {
return { emitPath: false }
}
}
},
mounted () { mounted () {
if (!this.fromFatherData) { if (!this.fromFatherData) {
this.queryMetrics() this.queryMetrics()
@@ -304,6 +357,14 @@ export default {
this.$forceUpdate() this.$forceUpdate()
this.cascaderValue = '' this.cascaderValue = ''
}, },
logLabelChange (value) {
if (!value || value.length === 0) return
this.expressionList[this.index] = `{${value[0]}="${value[1]}"}`
this.dropDownVisible = false
this.$emit('change', value)
this.$forceUpdate()
this.cascaderValue = ''
},
metricKeyDown (val) { metricKeyDown (val) {
if (this.required) { if (this.required) {
this.metricChange(val) this.metricChange(val)
@@ -313,7 +374,6 @@ export default {
this.$emit('change') this.$emit('change')
}, },
setError: function (errMsg) { setError: function (errMsg) {
// console.log(errMsg)
this.errorMsg = errMsg this.errorMsg = errMsg
}, },
getExprTemp () { getExprTemp () {
@@ -528,7 +588,7 @@ export default {
} */ } */
}, },
watch: { watch: {
dropDownVisible: function (n, o) { dropDownVisible (n, o) {
if (this.$refs.metricSelector) { if (this.$refs.metricSelector) {
this.$refs.metricSelector.dropDownVisible = n this.$refs.metricSelector.dropDownVisible = n
if (!this.expressionList[this.index] || this.expressionList[this.index] == '') { if (!this.expressionList[this.index] || this.expressionList[this.index] == '') {

View File

@@ -413,6 +413,7 @@ export default {
defaultTooltipFormatter: function (params) { defaultTooltipFormatter: function (params) {
let minusFlag = true let minusFlag = true
let str = '<div>' let str = '<div>'
params instanceof Array || (params = [params])
params.forEach((item, i) => { params.forEach((item, i) => {
const alias = this.queryAlias(i) const alias = this.queryAlias(i)
if (i === 0 && alias.indexOf('Previous ') === -1) { if (i === 0 && alias.indexOf('Previous ') === -1) {

View File

@@ -54,7 +54,7 @@ const commonOption = {
tooltip: { // 和 option.tooltip 的配置项相同 tooltip: { // 和 option.tooltip 的配置项相同
show: true, show: true,
position: 'top', position: 'top',
formatter: function (param) { formatter (param) {
return param.title // 自定义的 DOM 结构 return param.title // 自定义的 DOM 结构
}, },
backgroundColor: 'rgba(255,255,255,0)', backgroundColor: 'rgba(255,255,255,0)',
@@ -103,7 +103,7 @@ const commonOption = {
axisLabel: { axisLabel: {
interval: 0, interval: 0,
rotate: 0, rotate: 0,
formatter: function (value, index) { formatter (value, index) {
const tData = new Date(value) const tData = new Date(value)
return [tData.getFullYear(), tData.getMonth() + 1, tData.getDate()].join('-') + '\n' + return [tData.getFullYear(), tData.getMonth() + 1, tData.getDate()].join('-') + '\n' +
[tData.getHours(), tData.getMinutes()].join(':') [tData.getHours(), tData.getMinutes()].join(':')
@@ -251,7 +251,7 @@ const alertMessageBarByRule = {
axisLabel: { axisLabel: {
show: true, show: true,
fontSize: 12, fontSize: 12,
formatter: function (value) { formatter (value) {
if (value.length > 15) { if (value.length > 15) {
return '...' + value.substring(value.length - 12, value.length) return '...' + value.substring(value.length - 12, value.length)
} }
@@ -305,7 +305,7 @@ const alertMessageBarByAsset = {
axisLabel: { axisLabel: {
show: true, show: true,
fontSize: 12, fontSize: 12,
formatter: function (value) { formatter (value) {
let r = value let r = value
if (r.length > 4) { if (r.length > 4) {
r = value.substring(0, 3) + '...' r = value.substring(0, 3) + '...'
@@ -316,7 +316,62 @@ const alertMessageBarByAsset = {
triggerEvent: true triggerEvent: true
} }
} }
const logBar = {
title: {
show: false
},
xAxis: {
type: 'time',
axisLabel: {
rotate: 0,
fontSize: 13 * window.devicePixelRatio
},
axisPointer: { // y轴上显示指针对应的值
show: true
},
splitLine: {
show: false
},
axisLine: {
show: false
},
axisTick: {
show: false
}
},
tooltip: {
show: true
},
yAxis: {
type: 'value',
splitLine: {
show: true
},
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
fontSize: 13 * window.devicePixelRatio,
formatter (value, i) {
let y
if (value < 1000) {
} else if (value < 1000000) {
y = value / 1000 + ' K'
} else if (value >= 1000000) {
y = value / 1000000 + ' M'
} else {
y = value / 1000000000 + ' G'
}
return y
}
}
},
useUTC: false, // 使用本地时间
series: []
}
const overviewLine = { const overviewLine = {
title: { title: {
show: false show: false
@@ -332,7 +387,7 @@ const overviewLine = {
trigger: 'axis', trigger: 'axis',
confine: false, confine: false,
extraCssText: 'z-index:1000;', extraCssText: 'z-index:1000;',
formatter: function (v, i) { formatter (v, i) {
return v return v
} }
}, },
@@ -375,7 +430,7 @@ const overviewLine = {
}, },
axisLabel: { axisLabel: {
fontSize: 13 * window.devicePixelRatio, fontSize: 13 * window.devicePixelRatio,
formatter: function (value, i) { formatter (value, i) {
let y let y
if (value < 1000) { if (value < 1000) {
y = value + ' Bs' y = value + ' Bs'
@@ -524,22 +579,23 @@ const chartTypes = {
bar: { name: 'alertMessage', option: alertMessageBarByAsset }, bar: { name: 'alertMessage', option: alertMessageBarByAsset },
ruleBar: { name: 'ruleMessage', option: alertMessageBarByRule }, ruleBar: { name: 'ruleMessage', option: alertMessageBarByRule },
assetBar: { name: 'assetMessage', option: alertMessageBarByAsset }, assetBar: { name: 'assetMessage', option: alertMessageBarByAsset },
logBar: { name: 'logBar', option: logBar },
noData: { name: 'noData', option: noDataOption }, noData: { name: 'noData', option: noDataOption },
tooltipPie: { option: tooltipPieOption }, tooltipPie: { option: tooltipPieOption },
topoPie: { option: topoPieOption } topoPie: { option: topoPieOption }
} }
export default { export default {
getOption: function (type) { getOption (type) {
return JSON.parse(JSON.stringify(chartTypes[type].option)) return JSON.parse(JSON.stringify(chartTypes[type].option))
}, },
getOptionNoData: function (type) { getOptionNoData (type) {
chartTypes[type].option.xAxis.data = createTempTimes() chartTypes[type].option.xAxis.data = createTempTimes()
return JSON.parse(JSON.stringify(chartTypes[type].option)) return JSON.parse(JSON.stringify(chartTypes[type].option))
}, },
setMap: function (map) { setMap (map) {
mapOptions.geo.map = map mapOptions.geo.map = map
}, },
getBgColorList: function () { getBgColorList () {
return Object.assign([], bgColorList) return Object.assign([], bgColorList)
} }
} }

View File

@@ -30,7 +30,7 @@
</el-input> </el-input>
</div> </div>
<pick-time id="panel" ref="pickTime" v-model="searchTime" :refresh-data-func="dateChange" :use-chart-unit="false"></pick-time> <pick-time id="panel" ref="pickTime" v-model="searchTime" :refresh-data-func="dateChange" :use-chart-unit="false" class="margin-r-10"></pick-time>
<button id="panel-add-chart" v-has="'panel_chart_add'" :title="$t('overall.createChart')" class="top-tool-btn margin-r-10" <button id="panel-add-chart" v-has="'panel_chart_add'" :title="$t('overall.createChart')" class="top-tool-btn margin-r-10"
type="button" @click="addChart"> type="button" @click="addChart">

View File

@@ -27,7 +27,7 @@ export default new Router({
}, },
{ {
path: '/explore', path: '/explore',
component: resolve => require(['../components/page/dashboard/explore/explore.vue'], resolve) component: resolve => require(['@/components/page/dashboard/explore/explore'], resolve)
}, },
{ {
path: '/overview', path: '/overview',