Merge branch 'dev-3.3' of https://git.mesalab.cn/nezha/nezha-fronted into dev-3.3

This commit is contained in:
likexuan
2022-04-19 14:16:25 +08:00
17 changed files with 732 additions and 135 deletions

View File

@@ -24,6 +24,11 @@ For a detailed explanation on how things work, check out the [guide](http://vuej
需要配置 config.json 为 {"baseUrl":"http://192.168.40.42:8080/", "version": "21.04"}
#自动化测试
npm run unit
根目录下 /test/jest.conf.js collectCoverageFrom 变量配置需要测试报告的文件 (因为暂时不测所有 只能一个个引入)
specs 配置对应的测试用例 https://docs.geedge.net/pages/viewpage.action?pageId=58310079 参考匹配器)
#思维导图
https://docs.geedge.net/pages/viewpage.action?pageId=67209306

View File

@@ -12,6 +12,13 @@
"unit": "jest --config test/unit/jest.conf.js --coverage"
},
"dependencies": {
"@codemirror/autocomplete": "^0.19.15",
"@codemirror/basic-setup": "^0.19.3",
"@codemirror/highlight": "^0.19.8",
"@codemirror/language": "^0.19.10",
"@codemirror/lint": "^0.19.6",
"@codemirror/state": "^0.19.9",
"@codemirror/view": "^0.19.48",
"@johmun/vue-tags-input": "^2.1.0",
"@riophae/vue-treeselect": "^0.4.0",
"@svgdotjs/svg.js": "^3.0.16",
@@ -24,6 +31,7 @@
"@topology/layout": "^0.3.0",
"@topology/sequence-diagram": "^0.3.0",
"axios": "^0.19.0",
"codemirror-promql": "^0.19.0",
"cytoscape": "^3.15.2",
"d3": "^6.7.0",
"d3-hexbin": "^0.2.2",

View File

@@ -2,6 +2,9 @@
position: relative;
width: 100%;
display: flex;
#editor{
width: 100%;
}
.no-resize{
background: rgba(255,255,255,0);
.el-textarea__inner {

View File

@@ -145,7 +145,6 @@ export default {
}
},
handleLegendAlias (legend, aliasExpression) {
console.log(legend, aliasExpression)
if (/\{\{.+\}\}/.test(aliasExpression)) {
const labelValue = aliasExpression.replace(/(\{\{.+?\}\})/g, function (i) {
const label = i.substr(i.indexOf('{{') + 2, i.indexOf('}}') - i.indexOf('{{') - 2)
@@ -294,7 +293,6 @@ export default {
}
},
mounted () {
this.chartId = `${this.chartInfo.id}${this.isFullscreen ? '-fullscreen' : ''}`
},
beforeDestroy () {

View File

@@ -34,6 +34,7 @@ export function getHexagon (key) {
const topologyCache = {}
export function getTopology (key) {
// console.log(topologyCache, 'topologyCache')
return topologyCache[`topology${key}`]
}
@@ -41,6 +42,18 @@ export function setTopology (key, value) {
topologyCache[`topology${key}`] = value
}
// const topologyImgList = localStorage.getItem('nz-imgList') ? JSON.parse(localStorage.getItem('nz-imgList')) : {}
export function getTopologyImg (key) {
// console.log(topologyCache, 'topologyCache')
// console.log(localStorage.getItem('nz-img-' + key), !localStorage.getItem('nz-img-' + key))
return localStorage.getItem('nz-img-' + key)
}
export function setTopologyImg (key, img) {
localStorage.setItem('nz-img-' + key, img)
}
export function setHexagon (key, value) {
hexagonCache[`hexagon${key}`] = value
}

View File

@@ -351,7 +351,7 @@ import {
myCubec,
myCubeAnchors
} from './L5/services/canvas.js'
import { getTopology, setTopology } from '../js/common'
import { getTopology, setTopology, getTopologyImg, setTopologyImg } from '../js/common'
import CanvasProps from './L5/CanvasProps'
import topologyTopTool from './L5//topologyTopTool'
import CanvasContextMenu from './L5/CanvasContextMenu'
@@ -850,7 +850,7 @@ export default {
if (!this.imgInit) {
return
}
const promiseArr = []
const promiseArr = [this.$get('stat/module/abnormal', { projectId: this.obj.id })]
const self = this
for (let i = 0; i < data.pens.length; i++) {
const line = data.pens[i]
@@ -867,13 +867,13 @@ export default {
item.image = img ? img.image : imgDefault
}
if (item.type === 0) {
promiseArr.push(this.$get('stat/module/abnormal', { moduleId: item.data.moduleId }))
// promiseArr.push(this.$get('stat/module/abnormal', { moduleId: item.data.moduleId }))
item.data.state = {}
item.data.state.asset = false
item.data.state.endpoint = false
item.data.state.alert = false
} else {
promiseArr.push({ type: 1 })
// promiseArr.push({ type: 1 })
item.data.state = {}
item.data.state.asset = false
item.data.state.endpoint = false
@@ -898,13 +898,13 @@ export default {
data.bkColor = '#FFFFFF00'
}
Promise.all(promiseArr).then(res => {
res.forEach((response, index) => {
const item = data.pens[index]
res[0].data.list.forEach((module, index) => {
const item = data.pens.find(pens => module.id === pens.data.moduleId)
if (item.type === 0 && item.data.state && res[index].data.list.length) {
item.data.state.error = item.data.error = !res[index].data.list[0].state
item.data.state.asset = !!res[index].data.list[0].asset
item.data.state.alert = !!res[index].data.list[0].alert
item.data.state.endpoint = !!res[index].data.list[0].endpoint
item.data.state.error = item.data.error = !module.state
item.data.state.asset = !!module.asset
item.data.state.alert = !!module.alert
item.data.state.endpoint = !!module.endpoint
} else {
item.data.state = {
error: false,
@@ -1723,13 +1723,21 @@ export default {
res.data.list.forEach((item, index) => {
item.imageName = item.name
delete item.name
promiseArr.push(this.dealImg(`monitor/project/topo/icon/${item.id}/1`))
if (getTopologyImg(item.id)) {
promiseArr.push(getTopologyImg(item.id))
} else {
promiseArr.push(this.dealImg(`monitor/project/topo/icon/${item.id}/1`))
}
imgArr.push({ ...item })
})
Promise.all(promiseArr).then((res2, header) => {
this.iconArray = [...res.data.list]
this.iconArray.forEach((item, index) => {
item.image = res2[index].data
console.log(res2[index])
item.image = res2[index].data || res2[index]
if (!getTopologyImg(item.id)) {
setTopologyImg(item.id, item.image)
}
const group = this.tools.find(tool => tool.group === item.unit)
if (group) {
group.children.push({
@@ -1780,7 +1788,11 @@ export default {
}
imgidList.forEach((item, index) => {
if (item.data.imageId && imageAllId.data.list.find(image => item.data.imageId === image.id)) {
promiseArr.push(this.dealImg(`monitor/project/topo/icon/${item.data.imageId}/1`))
if (getTopologyImg(item.data.imageId)) {
promiseArr.push(getTopologyImg(item.data.imageId))
} else {
promiseArr.push(this.dealImg(`monitor/project/topo/icon/${item.data.imageId}/1`))
}
} else if (item.data.imageId) {
promiseArr.push(imgDefault)
} else {
@@ -1795,7 +1807,10 @@ export default {
})
this.iconArray.forEach((item, index) => {
if (item.id) {
item.image = res2[index].data
item.image = res2[index].data || res2[index]
if (!getTopologyImg(item.id)) {
setTopologyImg(item.id, item.image)
}
}
})
this.imgInit = true
@@ -2254,6 +2269,10 @@ export default {
if (getTopology(this.topologyIndex)) {
getTopology(this.topologyIndex).destroy()
setTopology(this.topologyIndex, null)
window.topology = null
window.Le5leTopologyPoint = null
window.topologyPoint = null
window.topologyRect = null
}
if (document.getElementById('topology-canvas' + this.topologyIndexF)) {
document.getElementById('topology-canvas' + this.topologyIndexF).removeEventListener('mousemove', this.canvasMove)

View File

@@ -348,6 +348,7 @@ import { noSpecialChar, nzNumber } from '../js/validate'
import editRigthBox from '../mixin/editRigthBox'
import richTextEditor from '@/components/chart/richTextEditor'
import promqlInputMixin from '@/components/common/mixin/promqlInput'
export default {
name: 'alertRuleBox',
props: {

View File

@@ -0,0 +1,183 @@
import { HighlightStyle, tags } from '@codemirror/highlight';
import { EditorView } from '@codemirror/view';
export const baseTheme = EditorView.theme({
'&': {
'&.cm-focused': {
outline: 'none',
outline_fallback: 'none',
},
},
'.cm-scroller': {
overflow: 'hidden',
fontFamily: '"DejaVu Sans Mono", monospace',
},
'.cm-placeholder': {
fontFamily:
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"',
},
'.cm-matchingBracket': {
color: '#000',
backgroundColor: '#dedede',
fontWeight: 'bold',
outline: '1px dashed transparent',
},
'.cm-nonmatchingBracket': { borderColor: 'red' },
'.cm-tooltip': {
backgroundColor: '#f8f8f8',
borderColor: 'rgba(52, 79, 113, 0.2)',
},
'.cm-tooltip.cm-tooltip-autocomplete': {
'& > ul': {
maxHeight: '350px',
fontFamily: '"DejaVu Sans Mono", monospace',
maxWidth: 'unset',
},
'& > ul > li': {
padding: '2px 1em 2px 3px',
},
'& li:hover': {
backgroundColor: '#ddd',
},
'& > ul > li[aria-selected]': {
backgroundColor: '#d6ebff',
color: 'unset',
},
minWidth: '30%',
},
'.cm-completionDetail': {
float: 'right',
color: '#999',
},
'.cm-tooltip.cm-completionInfo': {
marginTop: '-11px',
padding: '10px',
fontFamily: "'Open Sans', 'Lucida Sans Unicode', 'Lucida Grande', sans-serif;",
border: 'none',
backgroundColor: '#d6ebff',
minWidth: '250px',
maxWidth: 'min-content',
},
'.cm-completionInfo.cm-completionInfo-right': {
'&:before': {
content: "' '",
height: '0',
position: 'absolute',
width: '0',
left: '-20px',
border: '10px solid transparent',
borderRightColor: '#d6ebff',
},
marginLeft: '12px',
},
'.cm-completionInfo.cm-completionInfo-left': {
'&:before': {
content: "' '",
height: '0',
position: 'absolute',
width: '0',
right: '-20px',
border: '10px solid transparent',
borderLeftColor: '#d6ebff',
},
marginRight: '12px',
},
'.cm-completionMatchedText': {
textDecoration: 'none',
fontWeight: 'bold',
color: '#0066bf',
},
'.cm-line': {
'&::selection': {
backgroundColor: '#add6ff',
},
'& > span::selection': {
backgroundColor: '#add6ff',
},
},
'.cm-selectionMatch': {
backgroundColor: '#e6f3ff',
},
'.cm-diagnostic': {
'&.cm-diagnostic-error': {
borderLeft: '3px solid #e65013',
},
},
'.cm-completionIcon': {
boxSizing: 'content-box',
fontSize: '16px',
lineHeight: '1',
marginRight: '10px',
verticalAlign: 'top',
'&:after': { content: "'\\ea88'" },
fontFamily: 'codicon',
paddingRight: '0',
opacity: '1',
color: '#007acc',
},
'.cm-completionIcon-function, .cm-completionIcon-method': {
'&:after': { content: "'\\ea8c'" },
color: '#652d90',
},
'.cm-completionIcon-class': {
'&:after': { content: "'○'" },
},
'.cm-completionIcon-interface': {
'&:after': { content: "'◌'" },
},
'.cm-completionIcon-variable': {
'&:after': { content: "'𝑥'" },
},
'.cm-completionIcon-constant': {
'&:after': { content: "'\\eb5f'" },
color: '#007acc',
},
'.cm-completionIcon-type': {
'&:after': { content: "'𝑡'" },
},
'.cm-completionIcon-enum': {
'&:after': { content: "''" },
},
'.cm-completionIcon-property': {
'&:after': { content: "'□'" },
},
'.cm-completionIcon-keyword': {
'&:after': { content: "'\\eb62'" },
color: '#616161',
},
'.cm-completionIcon-namespace': {
'&:after': { content: "'▢'" },
},
'.cm-completionIcon-text': {
'&:after': { content: "'\\ea95'" },
color: '#ee9d28',
},
});
export const promqlHighlighter = HighlightStyle.define([
{ tag: tags.name, color: '#000' },
{ tag: tags.number, color: '#09885a' },
{ tag: tags.string, color: '#a31515' },
{ tag: tags.keyword, color: '#008080' },
{ tag: tags.function(tags.variableName), color: '#008080' },
{ tag: tags.labelName, color: '#800000' },
{ tag: tags.operator },
{ tag: tags.modifier, color: '#008080' },
{ tag: tags.paren },
{ tag: tags.squareBracket },
{ tag: tags.brace },
{ tag: tags.invalid, color: 'red' },
{ tag: tags.comment, color: '#888', fontStyle: 'italic' },
]);

View File

@@ -6,100 +6,268 @@
<div v-if="plugins.indexOf('metric-selector') > -1">
<el-dropdown class="metric-selector">
<el-dropdown-menu style="display: none"></el-dropdown-menu>
<button class="top-tool-btn top-tool-btn--text" type="button" @click="toggleDropdown">{{type === 'log' ? $t("overall.logLabels") : $t("overall.metric") }}
<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"
v-clickoutside="closeDropdown" v-my-loading="tempBoxShowLoading" :loading="loading" :options="metricOptions"
v-if="type !== 'log'" :props="cascaderProps" @change="metricChangeNew" style="margin-top: 5px">
<button
class="top-tool-btn top-tool-btn--text"
type="button"
@click="toggleDropdown"
>
{{
type === "log" ? $t("overall.logLabels") : $t("overall.metric")
}}
<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"
v-clickoutside="closeDropdown"
v-my-loading="tempBoxShowLoading"
:loading="loading"
:options="metricOptions"
v-if="type !== 'log'"
:props="cascaderProps"
@change="metricChangeNew"
style="margin-top: 5px"
>
<template slot-scope="{ node, data }">
<div :class="['nz-cascade',data.temp&&!data.child?'nz-cascade-temp':'',data.more?'cascader-panel-more':'']" @click="()=>{lazyLoad(node,data)}" :title="data.label">
<div
:class="[
'nz-cascade',
data.temp && !data.child ? 'nz-cascade-temp' : '',
data.more ? 'cascader-panel-more' : '',
]"
@click="
() => {
lazyLoad(node, data);
}
"
:title="data.label"
>
<i class="nz-icon nz-icon-template2"></i>
{{data.label}}
{{ data.label }}
<i v-if="data.more" class="nz-icon nz-icon-arrow-down"></i>
</div>
</template>
</el-cascader-panel>
<el-cascader-panel v-else v-show="dropDownVisible" ref="metricSelector" slot="dropdown"
v-model="cascaderValue" v-clickoutside="closeDropdown" v-my-loading="tempBoxShowLoading"
:loading="loading" :props="cascaderProps" @change="logLabelChange" style="margin-top: 5px;">
<el-cascader-panel
v-else
v-show="dropDownVisible"
ref="metricSelector"
slot="dropdown"
v-model="cascaderValue"
v-clickoutside="closeDropdown"
v-my-loading="tempBoxShowLoading"
:loading="loading"
:props="cascaderProps"
@change="logLabelChange"
style="margin-top: 5px"
>
<template slot-scope="{ node, data }">
<div :title="data.label" class="nz-cascade">
{{data.label}}
{{ data.label }}
</div>
</template>
</el-cascader-panel>
</el-dropdown>
</div>
<div v-if="plugins.indexOf('metric-input') > -1" class="input-box" @click="dropDownVisible=false">
<el-input :id="inputId" v-model="expressionList[index]" :autosize="{ minRows: 1, maxRows: 6}"
class="not-fixed-height no-resize" type="textarea"
@input="metricKeyDown" @keyup.enter.native="expressionChange" ref="elInput"></el-input>
<div v-if="errorMsg" class="append-msg error"><span>{{errorMsg}}</span></div>
<div v-if="appendMsg" class="append-msg error"><span>{{appendMsg}}</span></div>
<div
v-if="plugins.indexOf('metric-input') > -1"
class="input-box"
@click="dropDownVisible = false"
>
<div id="editor"
>
</div>
<!-- <el-input
:id="inputId"
v-model="expressionList[index]"
:autosize="{ minRows: 1, maxRows: 6 }"
class="not-fixed-height no-resize"
type="textarea"
@input="metricKeyDown"
@keyup.enter.native="expressionChange"
ref="elInput"
></el-input> -->
<div v-if="errorMsg" class="append-msg error">
<span>{{ errorMsg }}</span>
</div>
<div v-if="appendMsg" class="append-msg error">
<span>{{ appendMsg }}</span>
</div>
</div>
<div class="top-tool-btn-group">
<button v-if="plugins.indexOf('add') > -1" class="top-tool-btn"
@click="addExpression"><i class="nz-icon nz-icon-plus"></i></button>
<button v-if="plugins.indexOf('copy') > -1" class="top-tool-btn"
@click="copyExpression"><i class="nz-icon nz-icon-override"></i></button>
<button v-if="plugins.indexOf('remove') > -1" class="top-tool-btn"
@click="removeExpression"><i class="nz-icon nz-icon-minus"></i></button>
<button
v-if="plugins.indexOf('add') > -1"
class="top-tool-btn"
@click="addExpression"
>
<i class="nz-icon nz-icon-plus"></i>
</button>
<button
v-if="plugins.indexOf('copy') > -1"
class="top-tool-btn"
@click="copyExpression"
>
<i class="nz-icon nz-icon-override"></i>
</button>
<button
v-if="plugins.indexOf('remove') > -1"
class="top-tool-btn"
@click="removeExpression"
>
<i class="nz-icon nz-icon-minus"></i>
</button>
</div>
</template>
<!--right-box里的样式-->
<template v-if="styleType === 2 || styleType === 3">
<el-row v-if="plugins.indexOf('metric-input') > -1 || plugins.indexOf('metric-selector') > -1"
style="width: 100%;">
<el-col
:class="[plugins.indexOf('metric-selector') > -1 ?'metric-selector-title':'metric-null-title']">
<el-dropdown class="metric-selector" v-if="plugins.indexOf('metric-selector') > -1">
<el-dropdown-menu style="display: none"></el-dropdown-menu>
<span :class="{'expr-title':projectRightBox}" style="cursor: pointer;" @click="toggleDropdown">{{type === 'log' ? $t("overall.logLabels") : $t("overall.metric") }}<i
<el-row
v-if="
plugins.indexOf('metric-input') > -1 ||
plugins.indexOf('metric-selector') > -1
"
style="width: 100%"
>
<el-col
:class="[
plugins.indexOf('metric-selector') > -1
? 'metric-selector-title'
: 'metric-null-title',
]"
>
<el-dropdown
class="metric-selector"
v-if="plugins.indexOf('metric-selector') > -1"
>
<el-dropdown-menu style="display: none"></el-dropdown-menu>
<span
:class="{ 'expr-title': projectRightBox }"
style="cursor: pointer"
@click="toggleDropdown"
>{{
type === "log"
? $t("overall.logLabels")
: $t("overall.metric")
}}<i
class="nz-icon nz-icon-arrow-down"
style="font-size: 14px; -webkit-transform:scale(0.75);display:inline-block;"></i></span>
<el-cascader-panel v-my-loading="tempBoxShowLoading" v-show="dropDownVisible" v-clickoutside="closeDropdown" v-model="cascaderValue"
style="text-align: left;margin-top: 5px" slot="dropdown" ref="metricSelector"
v-if="type !== 'log'" :props="{emitPath:false}" :options="metricOptions" @change="metricChangeNew">
style="
font-size: 14px;
-webkit-transform: scale(0.75);
display: inline-block;
"
></i
></span>
<el-cascader-panel
v-my-loading="tempBoxShowLoading"
v-show="dropDownVisible"
v-clickoutside="closeDropdown"
v-model="cascaderValue"
style="text-align: left; margin-top: 5px"
slot="dropdown"
ref="metricSelector"
v-if="type !== 'log'"
:props="{ emitPath: false }"
:options="metricOptions"
@change="metricChangeNew"
>
<template slot-scope="{ node, data }">
<div
:class="[
'nz-cascade',
data.temp && !data.child ? 'nz-cascade-temp' : '',
data.more ? 'cascader-panel-more' : '',
]"
@click="
() => {
lazyLoad(node, data);
}
"
:title="data.label"
>
<i class="nz-icon nz-icon-template2"></i>
{{ data.label }}
<i v-if="data.more" class="nz-icon nz-icon-arrow-down"></i>
</div>
</template>
</el-cascader-panel>
<el-cascader-panel
v-else
v-show="dropDownVisible"
ref="metricSelector"
slot="dropdown"
v-model="cascaderValue"
v-clickoutside="closeDropdown"
v-my-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-dropdown>
</el-col>
<el-col
:class="
plugins.indexOf('metric-selector') > -1
? 'metric-selector-input-box'
: 'metric-null-input-box'
"
:style="{ height: '100%' }"
>
<template slot-scope="{ node, data }">
<div :class="['nz-cascade',data.temp&&!data.child?'nz-cascade-temp':'',data.more?'cascader-panel-more':'']" @click="()=>{lazyLoad(node,data)}" :title="data.label">
<i class="nz-icon nz-icon-template2"></i>
{{data.label}}
<i v-if="data.more" class="nz-icon nz-icon-arrow-down"></i>
</div>
</template>
<div
class="input-box"
@click="dropDownVisible = false"
v-if="plugins.indexOf('metric-input') > -1"
>
<div id="editor"
class="not-fixed-height no-resize"
ref="elInput"
</el-cascader-panel>
<el-cascader-panel v-else v-show="dropDownVisible" ref="metricSelector" slot="dropdown"
v-model="cascaderValue" v-clickoutside="closeDropdown" v-my-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-dropdown>
</el-col>
<el-col
:class="plugins.indexOf('metric-selector') > -1 ?'metric-selector-input-box':'metric-null-input-box'"
:style="{height: '100%'}">
<div class="input-box" @click="dropDownVisible=false" v-if="plugins.indexOf('metric-input') > -1">
<el-input v-model="expressionList[index]" @input="metricKeyDown" type="textarea" :maxlength="styleType === 3 ? 512 : 4096" show-word-limit
:autosize="{ minRows: 1, maxRows: 6}" class="not-fixed-height no-resize" ref="elInput"></el-input>
<div class="append-msg error" v-if="errorMsg"><span>{{errorMsg}}</span></div>
<div class="append-msg error" v-if="appendMsg"><span>{{appendMsg}}</span></div>
></div>
<!-- <el-input
v-model="expressionList[index]"
@input="metricKeyDown"
type="textarea"
:maxlength="styleType === 3 ? 512 : 4096"
show-word-limit
:autosize="{ minRows: 1, maxRows: 6 }"
class="not-fixed-height no-resize"
ref="elInput"
></el-input> -->
<div class="append-msg error" v-if="errorMsg">
<span>{{ errorMsg }}</span>
</div>
</el-col>
</el-row>
</template>
<div class="append-msg error" v-if="appendMsg">
<span>{{ appendMsg }}</span>
</div>
</div>
</el-col>
</el-row>
</template>
</div>
<div v-if="styleType == 2&&showRemove">
<div class="option" @click="addExpression" v-if="plugins.indexOf('add') > -1"><i class="nz-icon nz-icon-plus"></i>
<div v-if="styleType == 2 && showRemove">
<div
class="option"
@click="addExpression"
v-if="plugins.indexOf('add') > -1"
>
<i class="nz-icon nz-icon-plus"></i>
</div>
<div
class="option"
style="margin-left: 5px; line-height: 32px"
@click="removeExpression"
v-if="plugins.indexOf('remove') > -1"
>
<i class="nz-icon nz-icon-minus"></i>
</div>
<div class="option" style="margin-left: 5px; line-height: 32px;" @click="removeExpression"
v-if="plugins.indexOf('remove') > -1"><i class="nz-icon nz-icon-minus"></i></div>
</div>
<el-dialog
@@ -109,56 +277,141 @@
:custom-class="'nz-temp-box'"
:destroy-on-close="true"
@closed="tempBoxClose"
center>
<el-form v-model="tempBox" class="temp-form-box" ref="tempFormBox" v-if="tempBoxShow">
center
>
<el-form
v-model="tempBox"
class="temp-form-box"
ref="tempFormBox"
v-if="tempBoxShow"
>
<span class="temp-form-box-title">Expression</span>
<el-form-item prop="expression">
<el-input class="temp-form-box-input" v-model="tempBox.expression" size="small" disabled></el-input>
<el-input
class="temp-form-box-input"
v-model="tempBox.expression"
size="small"
disabled
></el-input>
</el-form-item>
<span class="temp-form-box-title" v-if="tempBox.vars.length">Variable</span>
<el-form-item v-for="(item,index) in tempBox.vars" :prop="item" :key="index">
<span class="temp-form-box-title" v-if="tempBox.vars.length"
>Variable</span
>
<el-form-item
v-for="(item, index) in tempBox.vars"
:prop="item"
:key="index"
>
<el-row>
<el-col :span="7" class="temp-form-box-col">
<el-input class="temp-form-box-input" v-model="tempBox.vars[index]" :id="'tempBox'+index" size="small" :disabled="true"></el-input>
<el-input
class="temp-form-box-input"
v-model="tempBox.vars[index]"
:id="'tempBox' + index"
size="small"
:disabled="true"
></el-input>
</el-col>
<el-col :span="16" v-if="format(item).key">
<select-alert-silence :filter-silence="filterSilence" :silence-data="format(item).arr" :panel-lock="false" :placement="'bottom-start'" :typeContentLoading="typeContentLoading"
@selectSilence="(val)=>{silenceChange(val,item)}" ref="selectPanel" style="width: 240px;">
<select-alert-silence
:filter-silence="filterSilence"
:silence-data="format(item).arr"
:panel-lock="false"
:placement="'bottom-start'"
:typeContentLoading="typeContentLoading"
@selectSilence="
(val) => {
silenceChange(val, item);
}
"
ref="selectPanel"
style="width: 240px"
>
<template v-slot:header>
<div class="explore-select-header">
<el-input :placeholder="$t('overall.search')" clearable size="mini" style="width: 300px;padding: 0 10px;" v-model="filterSilence" id="panel-list-search"></el-input>
<el-input
:placeholder="$t('overall.search')"
clearable
size="mini"
style="width: 300px; padding: 0 10px"
v-model="filterSilence"
id="panel-list-search"
></el-input>
</div>
</template>
<template v-slot:trigger>
<el-input class="panel-name" placeholder="" readonly="readonly" v-model="tempBox[item]" size="small">
<span slot="suffix" class="el-input__icon el-icon-circle-close el-input__clear" @click.stop="clearValue(item)"></span>
<el-input
class="panel-name"
placeholder=""
readonly="readonly"
v-model="tempBox[item]"
size="small"
>
<span
slot="suffix"
class="
el-input__icon
el-icon-circle-close
el-input__clear
"
@click.stop="clearValue(item)"
></span>
</el-input>
</template>
</select-alert-silence>
</el-col>
<el-col :span="17" v-else>
<el-input v-model="tempBox[item]" :id="'tempBox'+item" size="small"></el-input>
<el-input
v-model="tempBox[item]"
:id="'tempBox' + item"
size="small"
></el-input>
</el-col>
</el-row>
</el-form-item>
</el-form>
<span slot="footer">
<button id="temp-box-esc" class="nz-btn nz-btn-size-normal nz-btn-style-light" @click="tempBoxShowChange(false)">
<span>{{$t('overall.cancel')}}</span>
<button
id="temp-box-esc"
class="nz-btn nz-btn-size-normal nz-btn-style-light"
@click="tempBoxShowChange(false)"
>
<span>{{ $t("overall.cancel") }}</span>
</button>
<button
id="chart-box-save"
v-has="`expressionTemplate_add`"
:disabled="prevent_opt.save"
class="nz-btn nz-btn-size-normal nz-btn-style-normal"
@click="tempBoxShowChange(true)"
>
<span>{{ $t("overall.save") }}</span>
</button>
<button id="chart-box-save" v-has="`expressionTemplate_add`" :disabled="prevent_opt.save" class="nz-btn nz-btn-size-normal nz-btn-style-normal" @click="tempBoxShowChange(true)" >
<span>{{$t('overall.save')}}</span>
</button>
</span>
</el-dialog>
</div>
</template>
<script>
import selectAlertSilence from '../../../common/alert/selectAlertSilence'
import { get } from '@/http'
import selectAlertSilence from '../../../common/alert/selectAlertSilence';
import { get } from '@/http';
import { PromQLExtension } from 'codemirror-promql';
import { basicSetup } from '@codemirror/basic-setup';
// import { EditorState } from '@codemirror/state';
import { highlightSelectionMatches } from '@codemirror/search';
import { EditorState, Prec, Compartment } from '@codemirror/state';
import { indentOnInput, syntaxTree } from '@codemirror/language';
import { EditorView , highlightSpecialChars, keymap, ViewUpdate, placeholder} from '@codemirror/view';
import { history, historyKeymap } from '@codemirror/history';
import { bracketMatching } from '@codemirror/matchbrackets';
import { defaultKeymap, insertNewlineAndIndent } from '@codemirror/commands';
import { commentKeymap } from '@codemirror/comment';
import { lintKeymap } from '@codemirror/lint';
import { baseTheme, lightTheme, darkTheme, promqlHighlighter } from './CMTheme.tsx';
import { closeBrackets, closeBracketsKeymap } from '@codemirror/closebrackets';
import { autocompletion, completionKeymap, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
export default {
name: 'promqlInput',
components: {
@@ -262,6 +515,38 @@ export default {
}
},
mounted () {
let promQL = new PromQLExtension().setComplete(
// {
// remote: { url: 'https://prometheus.land' }
// }
)
// const dynamicConfigCompartment = new Compartment();
new EditorView({
state: EditorState.create({
extensions: [
baseTheme,
highlightSpecialChars(),
history(),
indentOnInput(),
bracketMatching(),
closeBrackets(),
autocompletion(),
highlightSelectionMatches(),
keymap.of([
...closeBracketsKeymap,
...defaultKeymap,
...historyKeymap,
...commentKeymap,
...completionKeymap,
...lintKeymap,
]),
placeholder('Expression (press Shift+Enter for newlines)'),
basicSetup, promQL.asExtension()],
}),
parent: document.getElementById('editor')
});
if (!this.fromFatherData && this.type !== 'logs') {
this.queryMetrics()
}
@@ -363,11 +648,13 @@ export default {
this.cascaderValue = ''
},
metricKeyDown (val) {
console.log(val);
if (this.required) {
this.metricChange(val)
}
},
expressionChange: function () {
console.log('expressionChange');
this.$emit('change')
},
setError: function (errMsg) {
@@ -642,19 +929,19 @@ export default {
}
</script>
<style>
.input-box .el-input__inner {
height: 30px;
}
.nz-temp-box /deep/ .el-dialog__body{
padding: 10px 20px 0 20px;
}
.nz-temp-box /deep/ .el-dialog__footer{
margin-top: 0;
}
.nz-temp-box .nz-btn-style-light{
margin-right: 10px;
}
.nz-temp-box .nz-btn-style-normal{
margin-left: 10px;
}
.input-box .el-input__inner {
height: 30px;
}
.nz-temp-box /deep/ .el-dialog__body {
padding: 10px 20px 0 20px;
}
.nz-temp-box /deep/ .el-dialog__footer {
margin-top: 0;
}
.nz-temp-box .nz-btn-style-light {
margin-right: 10px;
}
.nz-temp-box .nz-btn-style-normal {
margin-left: 10px;
}
</style>

View File

@@ -36,6 +36,18 @@ import myDatePicker from '@/components/common/myDatePicker'
import vSelectPage from '@/components/common/v-selectpagenew'
import nzDataList from '@/components/common/table/nzDataList'
import htmlToPdf from '@/components/common/js/htmlToPdf'
import { registerNode } from '@topology/core'
import {
myAnchors,
myCubeAnchors,
myCubec,
myIconRect,
myShape,
myTextRect
} from '@/components/common/project/L5/services/canvas'
// 注册到画布
registerNode('rectangleImg', myShape, myAnchors, myIconRect, myTextRect)
registerNode('myCube', myCubec, myCubeAnchors, null, null)
Vue.use(htmlToPdf)
Vue.use(vSelectPage, {
dataLoad: function (vue, url, params) {

View File

@@ -2,6 +2,7 @@ import Vue from 'vue'
import Vuex from 'vuex'
import user from './user'
import panel from './panel'
// import topology from './topology'
Vue.use(Vuex)
const store = new Vuex.Store({

View File

@@ -0,0 +1,24 @@
const topology = {
state: {
imgList: localStorage.getItem('nz-topology-imgList') || {}
},
mutations: {
setImgList (state, imgList) {
state.imgList = imgList
},
setImgListItem (state, item) {
state.imgList[item.key] = item.img
}
},
getters: {
getImgList (state) {
return state.imgList
}
},
actions: {
dispatchAddImgList (store, obj) {
store.commit('setImgListItem', obj)
}
}
}
export default topology

View File

@@ -1,3 +1,7 @@
module.exports = {
get: jest.fn(() => Promise.resolve({ status: 200 }))
get: jest.fn(() => Promise.resolve({ status: 200 })),
post: jest.fn(() => Promise.resolve({ status: 200 })),
put: jest.fn(() => Promise.resolve({ status: 200 })),
del: jest.fn(() => Promise.resolve({ status: 200 })),
requestsArr: []
}

View File

@@ -1 +1,5 @@
module.exports = 'test-file-stub';
module.exports = {
'test-file-stub': 'test-file-stub',
getters: {},
commit: ()=>{}
}

View File

@@ -0,0 +1,3 @@
module.exports = {
t: () => { return 'i18n' }
}

View File

@@ -8,25 +8,39 @@ module.exports = {
'^.+\\.js$': '<rootDir>/node_modules/babel-jest',
'.*\\.(vue)$': '<rootDir>/node_modules/vue-jest'
},
// moduleFileExtensions: [
// 'js',
// 'json',
// 'vue'
// ],
moduleFileExtensions: [
'js',
'json',
'vue'
],
transformIgnorePatterns: ['/node_modules/(?!vue-awesome)', 'element-ui'],
moduleNameMapper: {
moduleNameMapper: { // 处理引入报错的文件 将其改为引入空文件 或者自定义的 function
'element-ui/*': '<rootDir>/test/unit/__mocks__/fileMock.js',
// 'libs/*': '<rootDir>/test/unit/__mocks__/fileMock.js',
'/i18n': '<rootDir>/test/unit/__mocks__/fileMock.js',
// 'libs/*': '<rootDir>/test/unit/__mocks__/fileMock.js',
'^@\/(.*?\.?(js|vue)?|)$': '<rootDir>/src/$1', // @路径转换,例如:@/components/Main.vue -> rootDir/src/components/Main.vue
'css/font/*': '<rootDir>/test/unit/__mocks__/fileMock.js',
'store/*': '<rootDir>/test/unit/__mocks__/fileMock.js',
'elSelect/MyElSelect': '<rootDir>/test/unit/__mocks__/fileMock.js',
'/chartList': '<rootDir>/test/unit/__mocks__/fileMock.js',
'/myDatePicker/': '<rootDir>/test/unit/__mocks__/fileMock.js',
'v-selectpage/': '<rootDir>/test/unit/__mocks__/fileMock.js',
'@topology/': '<rootDir>/test/unit/__mocks__/fileMock.js',
'/diagram': '<rootDir>/test/unit/__mocks__/fileMock.js',
'/topology': '<rootDir>/test/unit/__mocks__/fileMock.js',
'@svgdotjs': '<rootDir>/test/unit/__mocks__/fileMock.js',
'@interactjs': '<rootDir>/test/unit/__mocks__/fileMock.js',
'pl-table': '<rootDir>/test/unit/__mocks__/fileMock.js',
'/htmlToPdf': '<rootDir>/test/unit/__mocks__/fileMock.js',
'\/*\/http': '<rootDir>/test/unit/__mocks__/axios.js',
// '\/*\/permission': '<rootDir>/test/unit/__mocks__/fileMock.js',
'/i18n': '<rootDir>/test/unit/__mocks__/i18nMock.js',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/test/unit/__mocks__/fileMock.js', // 模拟加载静态文件
'\\.(css|less|scss|sass)$': '<rootDir>/test/unit/__mocks__/styleMock.js'  // 模拟加载样式文件
'\\.(css|less|scss|sass)$': '<rootDir>/test/unit/__mocks__/styleMock.js', // 模拟加载样式文件
'\\.(css|less|scss|sass|style)$': 'jest-css-modules',
'^@\/(.*?\.?(js|vue)?|)$': '<rootDir>/src/$1' // @路径转换,例如:@/components/Main.vue -> rootDir/src/components/Main.vue
},
testMatch: [ // 匹配测试用例的文件
'<rootDir>/test/unit/specs/*.spec.js',
'<rootDir>/test/unit/specs/lib/*.spec.js',
'<rootDir>/test/unit/specs/components/*.spec.js',
'<rootDir>/test/unit/specs/components/*.spec.js'
],
moduleDirectories: [
'node_modules'
@@ -41,8 +55,9 @@ module.exports = {
// 'test/unit/specs/*.(js)',
'src/components/common/js/example.js',
'src/libs/bus.js',
'src/main.js',
// 'src/components/common/js/tools.js',
'!src/*.(js)',
// '!src/*.(js)',
'!src/http.js',
'!src/router/index.js',
'!**/node_modules/**'

View File

@@ -0,0 +1,17 @@
import main from '@/main'
import bus from '@/libs/bus'
describe('时间函数', () => {
// 测试代码可读性最好
// 分组
// const str = 'node_load1{module="node-exporter",endpoint_id="69",project="Common",datacenter="xin_xi_gang_DC",asset_id="11",endpoint="node-exporter-192.168.44.18",module_id="165",nz_agent_id="75",project_id="17",olap="node_exporter_nacos",asset="Bifang-CM-Server2",datacenter_id="4"} '
// it('正常替换一个', () => {
// expect(dealLegendAlias(str, '{{module}}')).toBe('node-exporter')
// })
it('正常替换一个', () => {
expect(main.utcTimeToTimezone(1650006960000)).toBe(1650006960000)
})
it('2', () => {
expect(main.utcTimeToTimezone('2022-04-15 15:16:00')).toBe(1650006960000)
})
})