CN-64 feat: 搜索框联动

This commit is contained in:
chenjinsong
2021-08-11 22:14:23 +08:00
parent da0051726d
commit de12e1ce6b
9 changed files with 497 additions and 341 deletions

View File

@@ -44,13 +44,16 @@ export default {
data () { data () {
return { return {
loading: false, loading: false,
username: 'admin', username: '',
pin: 'Nezha2021' pin: ''
} }
}, },
methods: { methods: {
...mapActions(['loginSuccess']), ...mapActions(['loginSuccess']),
login () { login () {
if (!this.username || !this.pin) {
return
}
if (!this.blockOperation.query) { if (!this.blockOperation.query) {
this.blockOperation.query = true this.blockOperation.query = true
} else { } else {

View File

@@ -8,7 +8,7 @@
<slot name="operations"></slot> <slot name="operations"></slot>
</div> </div>
</div> </div>
<div class="cn-chart__body"> <div class="cn-chart__body" v-loading="loading">
<slot></slot> <slot></slot>
</div> </div>
<div class="cn-chart__footer"> <div class="cn-chart__footer">
@@ -20,7 +20,10 @@
<script> <script>
export default { export default {
name: 'ChartMap' name: 'ChartMap',
props: {
loading: Boolean
}
} }
</script> </script>

View File

@@ -8,7 +8,7 @@
<slot name="operations"></slot> <slot name="operations"></slot>
</div> </div>
</div> </div>
<div class="cn-chart__body"> <div class="cn-chart__body" v-loading="loading">
<el-table <el-table
style="width: 100%" style="width: 100%"
tooltip-effect="light" tooltip-effect="light"
@@ -54,7 +54,8 @@ export default {
name: 'ChartTable', name: 'ChartTable',
props: { props: {
tableColumns: Array, tableColumns: Array,
tableData: Array tableData: Array,
loading: Boolean
}, },
setup () { setup () {
return { return {

View File

@@ -8,10 +8,10 @@
<slot name="operations"></slot> <slot name="operations"></slot>
</div> </div>
</div> </div>
<div class="cn-chart__body" :class="{'pie-with-table': isPieWithTable}"> <div class="cn-chart__body" :class="{'pie-with-table': isPieWithTable}" v-loading="loading">
<slot></slot> <slot></slot>
</div> </div>
<div class="cn-chart__footer" v-if="layout.indexOf(layoutConstant.FOOTER) > -1" :class="{'pie-with-table': isPieWithTable}"> <div class="cn-chart__footer" v-if="layout.indexOf(layoutConstant.FOOTER) > -1" :class="{'pie-with-table': isPieWithTable}" v-loading="loading">
<slot name="footer"></slot> <slot name="footer"></slot>
</div> </div>
</div> </div>
@@ -23,7 +23,8 @@ export default {
name: 'EchartsFrame', name: 'EchartsFrame',
props: { props: {
layout: Array, layout: Array,
chartInfo: Object chartInfo: Object,
loading: Boolean
}, },
setup (props) { setup (props) {
return { return {

View File

@@ -4,32 +4,54 @@
<div class="filter__body"> <div class="filter__body">
<el-collapse v-model="active" class="filter__collapse"> <el-collapse v-model="active" class="filter__collapse">
<el-collapse-item <el-collapse-item
v-for="(f, i) in filterData" name="0"
:key="i"
:name="`${i}`"
> >
<template #title> <template #title>
<div class="collapse-header"><i :class="f.icon" class="collapse-header__icon"></i><span>{{f.title}}</span></div> <div class="collapse-header"><i :class="topFilterData.icon" class="collapse-header__icon"></i><span>{{topFilterData.title}}</span></div>
</template> </template>
<el-tree <el-tree
:data="f.data" :data="topFilterData.data"
:load="(node, resolve) => loadFilter(node, resolve, f)"
:node-key="f.key"
:props="{ isLeaf: 'leaf' }" :props="{ isLeaf: 'leaf' }"
:expand-on-click-node="false" :expand-on-click-node="false"
:lazy="i === 0" :load="loadLevel2"
:ref="`tree-${i}`" lazy
node-key="name"
ref="tree-0"
highlight-current highlight-current
@node-click="(data, node, component) => nodeClick(data, node, component, i)" @node-click="(data, node, component) => nodeClick(data, node, component, 0)"
> >
<template #default="{ node, data }"> <template #default="{ node, data }">
<div class="filter-item"> <div class="filter-item">
<div :title="node.level === 1 ? data[f.key] : (node.level === 2 ? data[f.childrenKey] : '')">{{node.level === 1 ? data[f.key] : (node.level === 2 ? data[f.childrenKey] : '')}}</div> <div>{{data.name}}</div>
<span>{{data.count}}</span> <span>{{data.count}}</span>
</div> </div>
</template> </template>
</el-tree> </el-tree>
<div class="filter__more" :class="{'filter__more--disabled': f.hasnotMore}" @click="showMore(f.key, f.hasnotMore)">{{$t('overall.showMore')}}</div> <div class="filter__more" :class="{'filter__more--disabled': topFilterData.hasnotMore}" @click="showMore(topFilterData)">{{$t('overall.showMore')}}</div>
</el-collapse-item>
<el-collapse-item
name="1"
>
<template #title>
<div class="collapse-header"><i :class="bottomFilterData.icon" class="collapse-header__icon"></i><span>{{bottomFilterData.title}}</span></div>
</template>
<el-tree
:data="bottomFilterData.data"
:props="{ isLeaf: 'leaf' }"
:expand-on-click-node="false"
node-key="name"
ref="tree-1"
highlight-current
@node-click="(data, node, component) => nodeClick(data, node, component, 1)"
>
<template #default="{ node, data }">
<div class="filter-item">
<div>{{data.name}}</div>
<span>{{data.count}}</span>
</div>
</template>
</el-tree>
<div class="filter__more" :class="{'filter__more--disabled': bottomFilterData.hasnotMore}" @click="showMore(bottomFilterData)">{{$t('overall.showMore')}}</div>
</el-collapse-item> </el-collapse-item>
</el-collapse> </el-collapse>
</div> </div>
@@ -40,7 +62,8 @@
export default { export default {
name: 'LeftFilter', name: 'LeftFilter',
props: { props: {
filterData: Array topFilterData: Object,
bottomFilterData: Object
}, },
data () { data () {
return { return {
@@ -50,8 +73,12 @@ export default {
} }
}, },
methods: { methods: {
async loadLevel2 (node, resolve) {
resolve(await this.$parent.loadLevel2FilterData(node, this.topFilterData.column))
},
nodeClick (data, node, component, index) { nodeClick (data, node, component, index) {
const currentData = index === 0 ? this.currentDataTop : this.currentDataBottom const currentData = index === 0 ? this.currentDataTop : this.currentDataBottom
const column = index === 0 ? this.topFilterData.column : this.bottomFilterData.column
if (this.dataEqual(currentData, data)) { if (this.dataEqual(currentData, data)) {
node.isCurrent = false node.isCurrent = false
} }
@@ -60,15 +87,14 @@ export default {
} else { } else {
this.currentDataBottom = this.$_.cloneDeep(data) this.currentDataBottom = this.$_.cloneDeep(data)
} }
this.$emit('select', data, node, index) this.$emit('select', data, node, index, column)
}, },
showMore (key, hasnotMore) { showMore (filterData) {
if (!hasnotMore) { if (!filterData.hasnotMore) {
this.$emit('showMore', key) this.$emit('showMore', filterData.column)
} }
}, },
loadFilter (node, resolve, f) { loadFilter (node, resolve, f) {
this.$emit('loadFilter', node, resolve, f.filterType, f.childrenKey, f.key)
}, },
dataEqual (obj1, obj2) { dataEqual (obj1, obj2) {
if (!obj1 || !obj2) { if (!obj1 || !obj2) {
@@ -82,15 +108,6 @@ export default {
}) })
return equal return equal
} }
},
watch: {
filterData: {
deep: true,
immediate: true,
handler (n) {
this.active = ['0', '1']
}
}
} }
} }
</script> </script>
@@ -155,6 +172,7 @@ export default {
} }
.el-tree-node__content { .el-tree-node__content {
height: 30px; height: 30px;
padding-right: 10px;
} }
} }
.filter-item { .filter-item {

View File

@@ -5,7 +5,7 @@
*/ */
import { get, post } from '@/utils/http' import { get, post } from '@/utils/http'
import { sortByOrderNum } from '@/permission' import { sortByOrderNum } from '@/permission'
import {storageKey} from "@/utils/constants"; import { storageKey } from '@/utils/constants'
export const api = { export const api = {
// 系统相关 // 系统相关
@@ -15,12 +15,9 @@ export const api = {
// 业务 // 业务
panel: '/visual/panel', panel: '/visual/panel',
chart: '/visual/chart', chart: '/visual/chart',
entityIpFilter: '/interface/entity/ip/filter', entityList: '/interface/entity/list',
entityDomainFilter: '/interface/entity/domain/filter', entityCount: '/interface/entity/total',
entityAppFilter: '/interface/entity/app/filter', entityFilter: '/interface/entity/filter'
entityIpList: '/interface/entity/ip/list',
entityDomainList: '/interface/entity/domain/list',
entityAppList: '/interface/entity/app/list'
} }
/* panel */ /* panel */
export async function getPanelList (params) { export async function getPanelList (params) {
@@ -36,50 +33,17 @@ export async function getChartList (params) {
export async function getChart (id) { export async function getChart (id) {
return await getData(`${api.chart}/${id}`) return await getData(`${api.chart}/${id}`)
} }
/* entity列表 */
export async function getEntityList (params) {
return await getData(api.entityList, params, true)
}
/* entity总数 */
export async function getEntityCount (params) {
return await getData(api.entityCount, params)
}
/* ip类型entity过滤器数据 */ /* ip类型entity过滤器数据 */
export async function getEntityIpFilterList (params) { export async function getEntityFilter (params) {
return await getData(api.entityIpFilter, params, true) return await getData(api.entityFilter, params, true)
}
/* domain类型entity过滤器数据 */
export async function getEntityDomainFilterList (params) {
return await getData(api.entityDomainFilter, params, true)
}
/* app类型entity过滤器数据 */
export async function getEntityAppFilterList (params) {
return await getData(api.entityAppFilter, params, true)
}
/* ip类型entity列表 */
export async function getEntityIpList (params) {
const request = new Promise(resolve => {
get(api.entityIpList, params).then(response => {
if (response.code === 200) {
resolve(response)
}
})
})
return await request
}
/* domain类型entity列表 */
export async function getEntityDomainList (params) {
const request = new Promise(resolve => {
get(api.entityDomainList, params).then(response => {
if (response.code === 200) {
resolve(response)
}
})
})
return await request
}
/* app类型entity列表 */
export async function getEntityAppList (params) {
const request = new Promise(resolve => {
get(api.entityAppList, params).then(response => {
if (response.code === 200) {
resolve(response)
}
})
})
return await request
} }
/* 字典 */ /* 字典 */
export async function getDictList (params) { export async function getDictList (params) {
@@ -87,12 +51,20 @@ export async function getDictList (params) {
} }
export async function getData (url, params = {}, isQueryList) { export async function getData (url, params = {}, isQueryList) {
const request = new Promise(resolve => { const request = new Promise((resolve, reject) => {
get(url, params).then(response => { try {
if (response.code === 200) { get(url, params).then(response => {
resolve(isQueryList ? response.data.list || response.data.result : response.data || response.data.result) if (response.code === 200) {
} resolve(isQueryList ? response.data.list || response.data.result : response.data.result || response.data)
}) } else {
reject(response)
}
})
} catch (e) {
reject(e)
}
}).catch(response => {
console.error(response)
}) })
return await request return await request
} }

View File

@@ -54,8 +54,22 @@ export const entityType = {
app: 'APP' app: 'APP'
} }
export const entityTypeMappingKey = { export const entityFilterType = {
ip: ['country', 'region'] ip: {
country: 'locate_country',
region: 'locate_region',
asn: 'asn'
},
domain: {
categoryGroup: 'category_group',
categoryName: 'category_name',
reputationLevel: 'reputation_level'
},
app: {
appCategory: 'app_category',
appSubcategory: 'app_subcategory',
appRisk: 'app_risk'
}
} }
export const unitTypes = { export const unitTypes = {

View File

@@ -28,6 +28,7 @@
<chart-map <chart-map
v-else-if="isMap" v-else-if="isMap"
:style="computePosition" :style="computePosition"
:loading="loading"
> >
<template #title>{{chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name}}</template> <template #title>{{chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name}}</template>
<template #operations> <template #operations>
@@ -44,6 +45,7 @@
:layout="layout" :layout="layout"
:style="computePosition" :style="computePosition"
:chartInfo="chartInfo" :chartInfo="chartInfo"
:loading="loading"
> >
<template #title v-if="layout.indexOf(layoutConstant.HEADER) > -1"> <template #title v-if="layout.indexOf(layoutConstant.HEADER) > -1">
{{chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name}} {{chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name}}
@@ -51,12 +53,12 @@
<template #operations v-if="layout.indexOf(layoutConstant.HEADER) > -1"> <template #operations v-if="layout.indexOf(layoutConstant.HEADER) > -1">
<div class="header__operation header__operation--echarts" v-if="chart.type === 31"> <div class="header__operation header__operation--echarts" v-if="chart.type === 31">
<el-select <el-select
size="mini" size="mini"
v-model="orderPieTable" v-model="orderPieTable"
class="option__select select-column" class="option__select select-column"
placeholder="" placeholder=""
popper-class="option-popper" popper-class="option-popper"
@change="orderPieTableChange" @change="orderPieTableChange"
> >
<el-option v-for="item in chartPieTableTopOptions" :key="item.value" :value="item.value">&nbsp{{item.name}}</el-option> <el-option v-for="item in chartPieTableTopOptions" :key="item.value" :value="item.value">&nbsp{{item.name}}</el-option>
</el-select> </el-select>
@@ -82,9 +84,8 @@
:type="chartInfo.type" :type="chartInfo.type"
:style="computePosition" :style="computePosition"
:icon="singleValue.icon" :icon="singleValue.icon"
:father="father" v-loading="loading"
> >
<div v-for="(item, index) in singleValue" :key="index"> {{item.result}}</div>
<template #title><span :title="chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name">{{chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name}}</span></template> <template #title><span :title="chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name">{{chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name}}</span></template>
<template #data> <template #data>
<span>{{handleSingleValue[0]}}</span> <span>{{handleSingleValue[0]}}</span>
@@ -107,6 +108,7 @@
:table-columns="table.tableColumns" :table-columns="table.tableColumns"
:table-data="table.currentPageData" :table-data="table.currentPageData"
:style="computePosition" :style="computePosition"
:loading="loading"
> >
<template #title>{{chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name}}</template> <template #title>{{chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name}}</template>
<template #operations> <template #operations>
@@ -224,7 +226,6 @@ export default {
currentPageData: [] // table当前页的数据 currentPageData: [] // table当前页的数据
}, },
pieTableData: [], pieTableData: [],
father: [],
singleValue: { singleValue: {
value: '-', value: '-',
icon: '' icon: ''
@@ -234,11 +235,13 @@ export default {
orderPieTable: chartPieTableTopOptions[0].value, orderPieTable: chartPieTableTopOptions[0].value,
selectPieChartName: '', selectPieChartName: '',
allSelectPieChartName: [], allSelectPieChartName: [],
chartOption: null chartOption: null,
loading: true
} }
}, },
methods: { methods: {
initChart () { initChart () {
this.loading = true
try { try {
const chartParams = this.chartInfo.params const chartParams = this.chartInfo.params
if (this.isMap) { if (this.isMap) {
@@ -271,44 +274,46 @@ export default {
get(replaceUrlPlaceholder(chartParams.url, queryParams)).then(response => { get(replaceUrlPlaceholder(chartParams.url, queryParams)).then(response => {
if (response.code === 200) { if (response.code === 200) {
this.singleValue.value = response.data.result this.singleValue.value = response.data.result
this.father = response.data.result
} }
}) if (this.isSingleValueWithEcharts) { // 带曲线的单值图
if (this.isSingleValueWithEcharts) { // 带曲线的单值图 const dom = document.getElementById(`chart${this.chartInfo.id}`)
const dom = document.getElementById(`chart${this.chartInfo.id}`) !this.myChart && (this.myChart = echarts.init(dom))
!this.myChart && (this.myChart = echarts.init(dom)) this.chartOption = this.$_.cloneDeep(getOption(this.chart.type))
this.chartOption = this.$_.cloneDeep(getOption(this.chart.type)) const seriesTemplate = this.chartOption.series[0]
const seriesTemplate = this.chartOption.series[0]
const queryParams = { startTime: parseInt(this.timeFilter.startTime / 1000), endTime: parseInt(this.timeFilter.endTime / 1000) } const queryParams = { startTime: parseInt(this.timeFilter.startTime / 1000), endTime: parseInt(this.timeFilter.endTime / 1000) }
get(replaceUrlPlaceholder(chartParams.urlLine, queryParams)).then(response => { get(replaceUrlPlaceholder(chartParams.urlLine, queryParams)).then(response => {
if (response.code === 200) { if (response.code === 200) {
response.data.result = [ response.data.result = [
{ {
legend: "session_rate", legend: "session_rate",
values:[ values:[
["1625122200","2"],["1625122500","2"],["1625122800","1"],["1625123100","1"],["1625123400","2"],["1625123700","2"],["1625124000","2"],["1625124300","3"],["1625124600","3"],["1625124900","3"] ["1625122200","2"],["1625122500","2"],["1625122800","1"],["1625123100","1"],["1625123400","2"],["1625123700","2"],["1625124000","2"],["1625124300","3"],["1625124600","3"],["1625124900","3"]
] ]
} }
] ]
this.chartOption.series = response.data.result.map((r, i) => { this.chartOption.series = response.data.result.map((r, i) => {
return { return {
...seriesTemplate, ...seriesTemplate,
name: r.legend, name: r.legend,
data: r.values.map(v => [Number(v[0]) * 1000, Number(v[1]), chartParams.unitType]), data: r.values.map(v => [Number(v[0]) * 1000, Number(v[1]), chartParams.unitType]),
lineStyle: { lineStyle: {
color: getChartColor[i] color: getChartColor[i]
}
} }
} })
}
this.myChart.setOption(this.chartOption)
this.$nextTick(() => {
this.myChart.resize()
}) })
} setTimeout(() => { this.loading = false }, 250)
this.myChart.setOption(this.chartOption)
this.$nextTick(() => {
this.myChart.resize()
}) })
}) } else {
} this.loading = false
}
})
} }
} else if (this.isTabs) { } else if (this.isTabs) {
if (!this.$_.isEmpty(this.chartInfo.children)) { if (!this.$_.isEmpty(this.chartInfo.children)) {
@@ -466,6 +471,7 @@ export default {
polygonTemplate.strokeWidth = 0.5 polygonTemplate.strokeWidth = 0.5
} }
} }
setTimeout(() => { this.loading = false }, 250)
}) })
}, },
pageJump (val) { pageJump (val) {
@@ -527,6 +533,7 @@ export default {
this.$nextTick(() => { this.$nextTick(() => {
this.myChart.resize() this.myChart.resize()
}) })
setTimeout(() => { this.loading = false }, 250)
}) })
}, },
initEchartsWithStatistics (chartParams) { initEchartsWithStatistics (chartParams) {
@@ -555,6 +562,7 @@ export default {
this.$nextTick(() => { this.$nextTick(() => {
this.myChart.resize() this.myChart.resize()
}) })
setTimeout(() => { this.loading = false }, 250)
}) })
}, },
initEchartsWithPieTable (chartParams) { initEchartsWithPieTable (chartParams) {
@@ -589,7 +597,10 @@ export default {
if (response2.code === 200) { if (response2.code === 200) {
this.pieTableData = response2.data.result this.pieTableData = response2.data.result
} }
this.loading = false
}) })
} else {
setTimeout(() => { this.loading = false }, 250)
} }
} }
}) })
@@ -652,6 +663,7 @@ export default {
this.table.tableColumns = this.getTableTitle(response.data.result) this.table.tableColumns = this.getTableTitle(response.data.result)
this.table.currentPageData = this.getTargetPageData(1, this.table.pageSize, this.table.tableData) this.table.currentPageData = this.getTargetPageData(1, this.table.pageSize, this.table.tableData)
} }
this.loading = false
}) })
} }
}, },

View File

@@ -2,16 +2,17 @@
<div class="outer-box" v-if="!showDetail"> <div class="outer-box" v-if="!showDetail">
<el-select <el-select
size="small" size="small"
v-model="filterType" v-model="from"
style="position: fixed; top: 9px; width: 130px; left:260px;" style="position: fixed; top: 9px; width: 130px; left:260px;"
> >
<el-option v-for="(value, key) in entityType" :key="key" :label="value" :value="key"></el-option> <el-option v-for="(value, key) in entityType" :key="key" :label="value" :value="key"></el-option>
</el-select> </el-select>
<div class="cn-entities"> <div class="cn-entities">
<el-input <el-input
v-model="searchContent" v-model="searchContentTemp"
style="width: 100%; grid-area: 1 / 1 / 1 / 3;" style="width: 100%; grid-area: 1 / 1 / 1 / 3;"
type="text" type="text"
@keyup.enter="enter"
> >
<template #prefix> <template #prefix>
<span style="padding-left: 4px"><i class="el-icon-search"></i></span> <span style="padding-left: 4px"><i class="el-icon-search"></i></span>
@@ -19,7 +20,8 @@
</el-input> </el-input>
<!-- 筛选区域 --> <!-- 筛选区域 -->
<left-filter <left-filter
:filter-data="filterData" :top-filter-data="topFilterData"
:bottom-filter-data="bottomFilterData"
@select="select" @select="select"
@showMore="showMore" @showMore="showMore"
@loadFilter="loadFilter" @loadFilter="loadFilter"
@@ -28,7 +30,7 @@
<!-- 内容区域 --> <!-- 内容区域 -->
<entity-list <entity-list
:list-data="listData" :list-data="listData"
:entity-type="filterType" :entity-type="from"
:page-obj="pageObjRight" :page-obj="pageObjRight"
@showDetail="entityDetail" @showDetail="entityDetail"
@pageSize="pageSizeRight" @pageSize="pageSizeRight"
@@ -41,18 +43,23 @@
</template> </template>
<script> <script>
import { entityType } from '@/utils/constants' import { entityType, entityFilterType } from '@/utils/constants'
import { ref } from 'vue' import { ref } from 'vue'
import LeftFilter from '@/components/entities/LeftFilter' import LeftFilter from '@/components/entities/LeftFilter'
import EntityList from '@/components/entities/EntityList' import EntityList from '@/components/entities/EntityList'
import { getEntityIpFilterList, getEntityDomainFilterList, getEntityAppFilterList, getEntityIpList, getEntityDomainList, getEntityAppList } from '@/utils/api' import { getEntityFilter, getEntityList, getEntityCount } from '@/utils/api'
import Panel from '@/views/charts/Panel' import Panel from '@/views/charts/Panel'
export default { export default {
name: 'EntityExplorer', name: 'EntityExplorer',
data () { data () {
return { return {
searchContent: '', searchContentTemp: '', // 搜索框内的文本内容按回车键后为searchContent赋值
filterData: [], searchContent: '', // 查询语句
searchParams: null, // 搜索参数,格式为[{ name: xxx, value: xxx }, ...]
filterObj: {}, // 被选中的左侧过滤条件
topFilterData: {}, // 左侧上方的过滤列表数据
bottomFilterData: {}, // 左侧下方的过滤列表数据
listData: [], // 右侧实体列表数据
pageObjLeftTop: { pageObjLeftTop: {
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
@@ -68,7 +75,6 @@ export default {
pageSize: 50, pageSize: 50,
total: 0 total: 0
}, },
listData: [],
showDetail: false, showDetail: false,
typeName: '', typeName: '',
currentEntity: {}, currentEntity: {},
@@ -81,175 +87,56 @@ export default {
'cn-panel': Panel 'cn-panel': Panel
}, },
methods: { methods: {
async showMore (key) { enter () {
if (!this.searchContentTemp) {
this.reset()
} else {
this.searchContent = this.searchContentTemp
}
},
async showMore (column) {
let index = 0 let index = 0
switch (key) { switch (column) {
case 'country': case entityFilterType.ip.country:
case 'categoryGroup': case entityFilterType.domain.categoryGroup:
case 'appCategory': { case entityFilterType.app.appCategory: {
this.pageObjLeftTop.pageNo++ this.pageObjLeftTop.pageNo++
break break
} }
case 'asn': case entityFilterType.ip.asn:
case 'reputationLevel': case entityFilterType.domain.reputationLevel:
case 'appRisk': { case entityFilterType.app.appRisk: {
this.pageObjLeftBottom.pageNo++
index = 1 index = 1
this.pageObjLeftBottom.pageNo++
break break
} }
default: break default: break
} }
const data = await this.loadFilterData(this.filterType, key) const { topFilterData, bottomFilterData } = await this.queryFilterData({ column, q: this.searchContent, from: this.from })
this.filterData[index].data = this.$_.concat(this.filterData[index].data, data) if (index === 1) {
this.filterData[index].hasnotMore = this.$_.isEmpty(data) || data.length < 10 this.bottomFilterData = {
}, ...this.bottomFilterData,
async loadFilterData (filterType, key) { data: this.$_.concat(this.bottomFilterData.data, bottomFilterData),
let data hasnotMore: bottomFilterData.length < 10
let params = { type: key }
switch (key) {
case 'country':
case 'categoryGroup':
case 'appCategory': {
params = { ...params, ...this.pageObjLeftTop }
break
} }
case 'asn':
case 'reputationLevel':
case 'appRisk': {
params = { ...params, ...this.pageObjLeftBottom }
break
}
default: break
}
switch (filterType) {
case 'ip': {
data = await getEntityIpFilterList(params)
break
}
case 'domain': {
data = await getEntityDomainFilterList(params)
break
}
case 'app': {
data = await getEntityAppFilterList(params)
break
}
default: break
}
return data
},
handleData (n) {
this.listData = []
this.filterData = []
const requests = []
const data = []
switch (n) {
case 'ip': {
requests.push(this.loadFilterData(n, 'country'))
requests.push(this.loadFilterData(n, 'asn'))
data.push({ filterType: n, title: this.$t('entities.countryOrRegion'), icon: 'cn-icon cn-icon-country', key: 'country', childrenKey: 'region', type: 'country', childrenType: 'region' })
data.push({ filterType: n, title: this.$t('entities.asn'), icon: 'cn-icon cn-icon-cloud', key: 'asn', type: 'asn' })
getEntityIpList({ ...this.pageObjRight }).then(res => {
this.listData = res.data.result
this.pageObjRight = { ...this.pageObjRight, total: res.statistics.result_size }
})
break
}
case 'domain': {
requests.push(this.loadFilterData(n, 'categoryGroup'))
requests.push(this.loadFilterData(n, 'reputationLevel'))
data.push({ filterType: n, title: this.$t('entities.groupAndName'), icon: 'cn-icon cn-icon-category', key: 'categoryGroup', childrenKey: 'categoryName' })
data.push({ filterType: n, title: this.$t('entities.creditLevel'), icon: 'cn-icon cn-icon-risk', key: 'reputationLevel' })
getEntityDomainList({ ...this.pageObjRight }).then(res => {
this.listData = res.data.result
this.pageObjRight = { ...this.pageObjRight, total: res.statistics.result_size }
})
break
}
case 'app': {
requests.push(this.loadFilterData(n, 'appCategory'))
requests.push(this.loadFilterData(n, 'appRisk'))
data.push({ filterType: n, title: this.$t('entities.categoryAndSub'), icon: 'cn-icon cn-icon-category', key: 'appCategory', childrenKey: 'appSubcategory' })
data.push({ filterType: n, title: this.$t('entities.riskLevel'), icon: 'cn-icon cn-icon-risk', key: 'appRisk' })
getEntityAppList({ ...this.pageObjRight }).then(res => {
this.listData = res.data.result
this.pageObjRight = { ...this.pageObjRight, total: res.statistics.result_size }
})
break
}
default: break
}
Promise.all(requests).then(responses => {
data.forEach((d, i) => {
d.data = responses[i]
d.hasnotMore = this.$_.isEmpty(responses[i]) || responses[i].length < 10
})
this.filterData = data
})
},
async select (data, node, index) {
const pageObjRight = { ...this.pageObjRight, ...data }
this.$_.forIn(data, (value, key) => {
if (value === this.pageObjRight[key]) {
delete pageObjRight[key]
}
})
this.pageObjRight = JSON.parse(JSON.stringify(pageObjRight))
const res = await loadList(node, this.filterType, this.pageObjRight)
this.listData = res.data.result
},
getEntityData (param) {
},
pageSizeRight (val) {
this.pageObjRight.pageSize = val
this.handleData(this.filterType)
},
pageNoRight (val) {
this.pageObjRight.pageNo = val
this.handleData(this.filterType)
},
loadFilter (node, resolve, filterType, key, parentKey) {
if (node.level === 0) {
resolve(node.data)
} else { } else {
const param = { type: key } this.topFilterData = {
param[parentKey] = (node.data)[parentKey] ...this.topFilterData,
let req = null data: this.$_.concat(this.topFilterData.data, topFilterData),
switch (filterType) { hasnotMore: topFilterData.length < 10
case 'ip': {
req = getEntityIpFilterList(param)
break
}
case 'domain': {
req = getEntityDomainFilterList(param)
break
}
case 'app': {
req = getEntityAppFilterList(param)
break
}
default: break
}
if (req !== null) {
req.then(res => {
res = res.map(r => {
return { ...r, leaf: true }
})
resolve(res)
})
} }
} }
}, },
entityDetail (entity, tabs) { async search () {
this.typeName = `${this.filterType.toLowerCase()}EntityDetail` const params = { from: this.from, q: this.searchContent }
this.currentEntity = entity this.listData = await getEntityList({ ...this.pageObjRight, ...params })
this.panelTabs = tabs this.pageObjRight.total = await getEntityCount(params)
this.showDetail = true const { topFilterData, bottomFilterData } = await this.queryFilterData(params)
} this.topFilterData = topFilterData
}, this.bottomFilterData = bottomFilterData
watch: { },
filterType (n) { /* 重置条件 */
reset () {
this.pageObjLeftTop = { this.pageObjLeftTop = {
pageNo: 1, pageNo: 1,
pageSize: 10 pageSize: 10
@@ -262,61 +149,306 @@ export default {
pageNo: 1, pageNo: 1,
pageSize: 50 pageSize: 50
} }
this.handleData(n) this.searchParams = null
this.searchParams = []
},
async queryFilterData (params) {
let topFilterParams = { ...params, ...this.pageObjLeftTop }
let bottomFilterParams = { ...params, ...this.pageObjLeftBottom }
const keys = Object.keys(this.filterObj)
let topLevel = 1 // 左上过滤条件默认是tree的第一层
if (!this.$_.isEmpty(keys)) {
let hasTopColumn = false
let hasBottomColumn = false
keys.forEach(key => {
switch (key) {
case entityFilterType.ip.country:
case entityFilterType.domain.categoryGroup:
case entityFilterType.app.appCategory: {
hasTopColumn = true
topFilterParams = { ...topFilterParams, column: key }
break
}
case entityFilterType.ip.region:
case entityFilterType.domain.categoryName:
case entityFilterType.app.appSubcategory: {
topLevel = 2
hasTopColumn = true
topFilterParams = { ...topFilterParams, column: key }
break
}
case entityFilterType.ip.asn:
case entityFilterType.domain.reputationLevel:
case entityFilterType.app.appRisk: {
hasBottomColumn = true
bottomFilterParams = { ...bottomFilterParams, column: key }
break
}
default: break
}
})
if (!hasTopColumn) {
topFilterParams = { ...topFilterParams, column: this.getDefaultTopColumn(this.from) }
}
if (!hasBottomColumn) {
bottomFilterParams = { ...bottomFilterParams, column: this.getDefaultBottomColumn(this.from) }
}
} else {
topFilterParams = { ...topFilterParams, column: this.getDefaultTopColumn(this.from) }
bottomFilterParams = { ...bottomFilterParams, column: this.getDefaultBottomColumn(this.from) }
}
const topFilterListData = await getEntityFilter(topFilterParams) || []
const bottomFilterListData = await getEntityFilter(bottomFilterParams) || []
let topFilterData = { data: topFilterListData, hasnotMore: topFilterListData.length < 10, column: topFilterParams.column }
let bottomFilterData = { data: bottomFilterListData, hasnotMore: bottomFilterListData.length < 10, column: bottomFilterParams.column }
switch (this.from) {
case 'ip': {
topFilterData = { ...topFilterData, icon: 'cn-icon cn-icon-country', title: this.$t('entities.countryOrRegion'), leaf: topLevel === 2 }
bottomFilterData = { ...bottomFilterData, icon: 'cn-icon cn-icon-cloud', title: this.$t('entities.asn') }
break
}
case 'domain': {
topFilterData = { ...topFilterData, icon: 'cn-icon cn-icon-category', title: this.$t('entities.groupAndName'), leaf: topLevel === 2 }
bottomFilterData = { ...bottomFilterData, icon: 'cn-icon cn-icon-risk', title: this.$t('entities.creditLevel') }
break
}
case 'app': {
topFilterData = { ...topFilterData, icon: 'cn-icon cn-icon-category', title: this.$t('entities.categoryAndSub'), leaf: topLevel === 2 }
bottomFilterData = { ...bottomFilterData, icon: 'cn-icon cn-icon-risk', title: this.$t('entities.riskLevel') }
break
}
default: break
}
return { topFilterData, bottomFilterData }
},
getDefaultTopColumn (from) {
let column = ''
switch (from) {
case 'ip': {
column = entityFilterType.ip.country
break
}
case 'domain': {
column = entityFilterType.domain.categoryGroup
break
}
case 'app': {
column = entityFilterType.app.appCategory
break
}
default: break
}
return column
},
getDefaultBottomColumn (from) {
let column = ''
switch (from) {
case 'ip': {
column = entityFilterType.ip.asn
break
}
case 'domain': {
column = entityFilterType.domain.reputationLevel
break
}
case 'app': {
column = entityFilterType.app.appRisk
break
}
default: break
}
return column
},
async select (data, node, index, column) {
this.filterObj[column] = data.name
},
async loadLevel2FilterData (node, parentColumn) {
let column
switch (parentColumn) {
case entityFilterType.ip.country: {
column = entityFilterType.ip.region
break
}
case entityFilterType.domain.categoryGroup: {
column = entityFilterType.domain.categoryName
break
}
case entityFilterType.app.appCategory: {
column = entityFilterType.app.appSubcategory
break
}
default: break
}
const params = {
q: this.searchContent,
from: this.from,
pageSize: -1,
pageNo: 1,
column
}
return await getEntityFilter(params)
},
pageSizeRight (val) {
this.pageObjRight.pageSize = val
},
pageNoRight (val) {
this.pageObjRight.pageNo = val
},
loadFilter (node, resolve, filterType, key, parentKey) {
if (node.level === 0) {
resolve(node.data)
} else {
const param = { column: key, from: filterType, q: this.searchContent }
const req = getEntityFilter(param)
if (req) {
req.then(res => {
res = res.map(r => {
return { ...r, leaf: true }
})
resolve(res)
})
}
}
},
entityDetail (entity, tabs) {
this.typeName = `${this.from.toLowerCase()}EntityDetail`
this.currentEntity = entity
this.panelTabs = tabs
this.showDetail = true
}
},
watch: {
/* entity类型切换时分页、搜索信息重置 */
from (n) {
this.reset()
},
/* 搜索框searchContent、左侧过滤条件filterObj互相联动
* 监听filterObj改动将改动同步给searchParams
* 监听searchContent改动将改动同步给searchParams
* 监听searchParams将改动同步给filterObj、searchContent之一并重新请求接口获取左侧过滤条件和右侧entity列表
* */
filterObj: {
deep: true,
handler (n) {
if (n) {
const searchParams = this.$_.cloneDeep(this.searchParams)
let change = false
Object.keys(n).forEach(key => {
let containKey = false
searchParams.forEach(item => {
if (item.name === key) {
containKey = true
if (item.value !== n[key]) {
change = true
item.value !== n[key] && (item.value = n[key])
}
}
})
if (!containKey) {
change = true
const name = key
const value = n[key]
const param = { name, value }
searchParams.push(param)
}
})
if (change) {
this.searchParams = searchParams
}
}
}
},
searchContent (n) {
if (n) {
const paramsArr = n.split(/\s[aA][nN][dD]\s/)
const paramsObj = {}
let change = false
paramsArr.forEach(string => {
const param = string.split('=')
if (param.length > 1) {
let value = param[1].trim()
const valueArr = value.split('"')
if (valueArr.length > 2) {
value = valueArr[1].trim()
}
paramsObj[param[0].trim()] = value
}
})
const searchParams = this.$_.cloneDeep(this.searchParams)
Object.keys(paramsObj).forEach(key => {
let containKey = false
searchParams.forEach(item => {
if (item.name === key) {
containKey = true
if (item.value !== paramsObj[key]) {
change = true
item.value = paramsObj[key]
}
}
})
if (!containKey) {
change = true
searchParams.push({ name: key, value: paramsObj[key] })
}
})
if (change) {
this.searchParams = searchParams
}
} else {
this.reset()
}
this.searchContentTemp !== n && (this.searchContentTemp = n)
},
searchParams: {
deep: true,
handler (n) {
if (n) {
let fromInput = false // input内容改变导致的变化
let fromFilter = false // 左侧filter过滤条件改变导致的变化
const filterKeys = Object.keys(this.filterObj)
if (n.length === 0) {
this.searchContentTemp = ''
this.searchContent = ''
this.filterObj = {}
} else if (filterKeys.length !== n.length) {
fromInput = true
} else {
fromFilter = true
}
if (fromInput) { // 是input导致的改变则同步给filter
const filterObj = {}
n.forEach(item => {
filterObj[item.name] = item.value
})
this.filterObj = filterObj
}
if (fromFilter) { // 是filter导致的改变则同步给input
let searchContent = ''
n.forEach(item => {
if (searchContent) {
searchContent += ' AND '
}
searchContent += `${item.name}="${item.value}"`
})
this.searchContent = searchContent
}
// 请求接口获取左侧过滤条件和右侧entity列表
this.search()
}
}
} }
}, },
async mounted () { async mounted () {
const country = await this.loadFilterData(this.filterType, 'country') this.searchParams = []
const asn = await this.loadFilterData(this.filterType, 'asn')
const res = await getEntityIpList({ ...this.pageObjRight })
this.listData = res.data.result
this.pageObjRight = { ...this.pageObjRight, total: res.statistics.result_size }
this.filterData.push({
title: this.$t('entities.countryOrRegion'),
key: 'country',
childrenKey: 'region',
icon: 'cn-icon cn-icon-country',
data: country,
hasnotMore: this.$_.isEmpty(country) || country.length < 10,
filterType: this.filterType
})
this.filterData.push({
title: this.$t('entities.asn'),
key: 'asn',
data: asn,
icon: 'cn-icon cn-icon-cloud',
hasnotMore: this.$_.isEmpty(asn) || asn.length < 10,
filterType: this.filterType
})
}, },
setup () { setup () {
const filterType = ref(Object.keys(entityType)[0]) const from = ref(Object.keys(entityType)[0])
return { return {
entityType, entityType, // 所有entity类型用于header下拉框选择
filterType from // 当前entity类型ip/domain/app
} }
} }
} }
const loadList = async (node, filterType, pageObj) => {
let res
const param = { ...pageObj }
switch (filterType) {
case 'ip': {
res = await getEntityIpList(param)
break
}
case 'domain': {
res = await getEntityDomainList(param)
break
}
case 'app': {
res = await getEntityAppList(param)
break
}
default: break
}
return res
}
</script> </script>
<style lang="scss"> <style lang="scss">