CN-64 feat: 搜索框联动
This commit is contained in:
@@ -44,13 +44,16 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
username: 'admin',
|
||||
pin: 'Nezha2021'
|
||||
username: '',
|
||||
pin: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['loginSuccess']),
|
||||
login () {
|
||||
if (!this.username || !this.pin) {
|
||||
return
|
||||
}
|
||||
if (!this.blockOperation.query) {
|
||||
this.blockOperation.query = true
|
||||
} else {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<slot name="operations"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cn-chart__body">
|
||||
<div class="cn-chart__body" v-loading="loading">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="cn-chart__footer">
|
||||
@@ -20,7 +20,10 @@
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'ChartMap'
|
||||
name: 'ChartMap',
|
||||
props: {
|
||||
loading: Boolean
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<slot name="operations"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cn-chart__body">
|
||||
<div class="cn-chart__body" v-loading="loading">
|
||||
<el-table
|
||||
style="width: 100%"
|
||||
tooltip-effect="light"
|
||||
@@ -54,7 +54,8 @@ export default {
|
||||
name: 'ChartTable',
|
||||
props: {
|
||||
tableColumns: Array,
|
||||
tableData: Array
|
||||
tableData: Array,
|
||||
loading: Boolean
|
||||
},
|
||||
setup () {
|
||||
return {
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
<slot name="operations"></slot>
|
||||
</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>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -23,7 +23,8 @@ export default {
|
||||
name: 'EchartsFrame',
|
||||
props: {
|
||||
layout: Array,
|
||||
chartInfo: Object
|
||||
chartInfo: Object,
|
||||
loading: Boolean
|
||||
},
|
||||
setup (props) {
|
||||
return {
|
||||
|
||||
@@ -4,32 +4,54 @@
|
||||
<div class="filter__body">
|
||||
<el-collapse v-model="active" class="filter__collapse">
|
||||
<el-collapse-item
|
||||
v-for="(f, i) in filterData"
|
||||
:key="i"
|
||||
:name="`${i}`"
|
||||
name="0"
|
||||
>
|
||||
<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>
|
||||
<el-tree
|
||||
:data="f.data"
|
||||
:load="(node, resolve) => loadFilter(node, resolve, f)"
|
||||
:node-key="f.key"
|
||||
:data="topFilterData.data"
|
||||
:props="{ isLeaf: 'leaf' }"
|
||||
:expand-on-click-node="false"
|
||||
:lazy="i === 0"
|
||||
:ref="`tree-${i}`"
|
||||
:load="loadLevel2"
|
||||
lazy
|
||||
node-key="name"
|
||||
ref="tree-0"
|
||||
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 }">
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
</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>
|
||||
</div>
|
||||
@@ -40,7 +62,8 @@
|
||||
export default {
|
||||
name: 'LeftFilter',
|
||||
props: {
|
||||
filterData: Array
|
||||
topFilterData: Object,
|
||||
bottomFilterData: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -50,8 +73,12 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadLevel2 (node, resolve) {
|
||||
resolve(await this.$parent.loadLevel2FilterData(node, this.topFilterData.column))
|
||||
},
|
||||
nodeClick (data, node, component, index) {
|
||||
const currentData = index === 0 ? this.currentDataTop : this.currentDataBottom
|
||||
const column = index === 0 ? this.topFilterData.column : this.bottomFilterData.column
|
||||
if (this.dataEqual(currentData, data)) {
|
||||
node.isCurrent = false
|
||||
}
|
||||
@@ -60,15 +87,14 @@ export default {
|
||||
} else {
|
||||
this.currentDataBottom = this.$_.cloneDeep(data)
|
||||
}
|
||||
this.$emit('select', data, node, index)
|
||||
this.$emit('select', data, node, index, column)
|
||||
},
|
||||
showMore (key, hasnotMore) {
|
||||
if (!hasnotMore) {
|
||||
this.$emit('showMore', key)
|
||||
showMore (filterData) {
|
||||
if (!filterData.hasnotMore) {
|
||||
this.$emit('showMore', filterData.column)
|
||||
}
|
||||
},
|
||||
loadFilter (node, resolve, f) {
|
||||
this.$emit('loadFilter', node, resolve, f.filterType, f.childrenKey, f.key)
|
||||
},
|
||||
dataEqual (obj1, obj2) {
|
||||
if (!obj1 || !obj2) {
|
||||
@@ -82,15 +108,6 @@ export default {
|
||||
})
|
||||
return equal
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
filterData: {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
handler (n) {
|
||||
this.active = ['0', '1']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -155,6 +172,7 @@ export default {
|
||||
}
|
||||
.el-tree-node__content {
|
||||
height: 30px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
.filter-item {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
import { get, post } from '@/utils/http'
|
||||
import { sortByOrderNum } from '@/permission'
|
||||
import {storageKey} from "@/utils/constants";
|
||||
import { storageKey } from '@/utils/constants'
|
||||
|
||||
export const api = {
|
||||
// 系统相关
|
||||
@@ -15,12 +15,9 @@ export const api = {
|
||||
// 业务
|
||||
panel: '/visual/panel',
|
||||
chart: '/visual/chart',
|
||||
entityIpFilter: '/interface/entity/ip/filter',
|
||||
entityDomainFilter: '/interface/entity/domain/filter',
|
||||
entityAppFilter: '/interface/entity/app/filter',
|
||||
entityIpList: '/interface/entity/ip/list',
|
||||
entityDomainList: '/interface/entity/domain/list',
|
||||
entityAppList: '/interface/entity/app/list'
|
||||
entityList: '/interface/entity/list',
|
||||
entityCount: '/interface/entity/total',
|
||||
entityFilter: '/interface/entity/filter'
|
||||
}
|
||||
/* panel */
|
||||
export async function getPanelList (params) {
|
||||
@@ -36,50 +33,17 @@ export async function getChartList (params) {
|
||||
export async function getChart (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过滤器数据 */
|
||||
export async function getEntityIpFilterList (params) {
|
||||
return await getData(api.entityIpFilter, 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 getEntityFilter (params) {
|
||||
return await getData(api.entityFilter, params, true)
|
||||
}
|
||||
/* 字典 */
|
||||
export async function getDictList (params) {
|
||||
@@ -87,12 +51,20 @@ export async function getDictList (params) {
|
||||
}
|
||||
|
||||
export async function getData (url, params = {}, isQueryList) {
|
||||
const request = new Promise(resolve => {
|
||||
const request = new Promise((resolve, reject) => {
|
||||
try {
|
||||
get(url, params).then(response => {
|
||||
if (response.code === 200) {
|
||||
resolve(isQueryList ? response.data.list || response.data.result : response.data || response.data.result)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -54,8 +54,22 @@ export const entityType = {
|
||||
app: 'APP'
|
||||
}
|
||||
|
||||
export const entityTypeMappingKey = {
|
||||
ip: ['country', 'region']
|
||||
export const entityFilterType = {
|
||||
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 = {
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
<chart-map
|
||||
v-else-if="isMap"
|
||||
:style="computePosition"
|
||||
:loading="loading"
|
||||
>
|
||||
<template #title>{{chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name}}</template>
|
||||
<template #operations>
|
||||
@@ -44,6 +45,7 @@
|
||||
:layout="layout"
|
||||
:style="computePosition"
|
||||
:chartInfo="chartInfo"
|
||||
:loading="loading"
|
||||
>
|
||||
<template #title v-if="layout.indexOf(layoutConstant.HEADER) > -1">
|
||||
{{chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name}}
|
||||
@@ -82,9 +84,8 @@
|
||||
:type="chartInfo.type"
|
||||
:style="computePosition"
|
||||
: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 #data>
|
||||
<span>{{handleSingleValue[0]}}</span>
|
||||
@@ -107,6 +108,7 @@
|
||||
:table-columns="table.tableColumns"
|
||||
:table-data="table.currentPageData"
|
||||
:style="computePosition"
|
||||
:loading="loading"
|
||||
>
|
||||
<template #title>{{chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name}}</template>
|
||||
<template #operations>
|
||||
@@ -224,7 +226,6 @@ export default {
|
||||
currentPageData: [] // table当前页的数据
|
||||
},
|
||||
pieTableData: [],
|
||||
father: [],
|
||||
singleValue: {
|
||||
value: '-',
|
||||
icon: ''
|
||||
@@ -234,11 +235,13 @@ export default {
|
||||
orderPieTable: chartPieTableTopOptions[0].value,
|
||||
selectPieChartName: '',
|
||||
allSelectPieChartName: [],
|
||||
chartOption: null
|
||||
chartOption: null,
|
||||
loading: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initChart () {
|
||||
this.loading = true
|
||||
try {
|
||||
const chartParams = this.chartInfo.params
|
||||
if (this.isMap) {
|
||||
@@ -271,9 +274,7 @@ export default {
|
||||
get(replaceUrlPlaceholder(chartParams.url, queryParams)).then(response => {
|
||||
if (response.code === 200) {
|
||||
this.singleValue.value = response.data.result
|
||||
this.father = response.data.result
|
||||
}
|
||||
})
|
||||
if (this.isSingleValueWithEcharts) { // 带曲线的单值图
|
||||
const dom = document.getElementById(`chart${this.chartInfo.id}`)
|
||||
!this.myChart && (this.myChart = echarts.init(dom))
|
||||
@@ -307,8 +308,12 @@ export default {
|
||||
this.$nextTick(() => {
|
||||
this.myChart.resize()
|
||||
})
|
||||
setTimeout(() => { this.loading = false }, 250)
|
||||
})
|
||||
} else {
|
||||
this.loading = false
|
||||
}
|
||||
})
|
||||
}
|
||||
} else if (this.isTabs) {
|
||||
if (!this.$_.isEmpty(this.chartInfo.children)) {
|
||||
@@ -466,6 +471,7 @@ export default {
|
||||
polygonTemplate.strokeWidth = 0.5
|
||||
}
|
||||
}
|
||||
setTimeout(() => { this.loading = false }, 250)
|
||||
})
|
||||
},
|
||||
pageJump (val) {
|
||||
@@ -527,6 +533,7 @@ export default {
|
||||
this.$nextTick(() => {
|
||||
this.myChart.resize()
|
||||
})
|
||||
setTimeout(() => { this.loading = false }, 250)
|
||||
})
|
||||
},
|
||||
initEchartsWithStatistics (chartParams) {
|
||||
@@ -555,6 +562,7 @@ export default {
|
||||
this.$nextTick(() => {
|
||||
this.myChart.resize()
|
||||
})
|
||||
setTimeout(() => { this.loading = false }, 250)
|
||||
})
|
||||
},
|
||||
initEchartsWithPieTable (chartParams) {
|
||||
@@ -589,7 +597,10 @@ export default {
|
||||
if (response2.code === 200) {
|
||||
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.currentPageData = this.getTargetPageData(1, this.table.pageSize, this.table.tableData)
|
||||
}
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2,16 +2,17 @@
|
||||
<div class="outer-box" v-if="!showDetail">
|
||||
<el-select
|
||||
size="small"
|
||||
v-model="filterType"
|
||||
v-model="from"
|
||||
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-select>
|
||||
<div class="cn-entities">
|
||||
<el-input
|
||||
v-model="searchContent"
|
||||
v-model="searchContentTemp"
|
||||
style="width: 100%; grid-area: 1 / 1 / 1 / 3;"
|
||||
type="text"
|
||||
@keyup.enter="enter"
|
||||
>
|
||||
<template #prefix>
|
||||
<span style="padding-left: 4px"><i class="el-icon-search"></i></span>
|
||||
@@ -19,7 +20,8 @@
|
||||
</el-input>
|
||||
<!-- 筛选区域 -->
|
||||
<left-filter
|
||||
:filter-data="filterData"
|
||||
:top-filter-data="topFilterData"
|
||||
:bottom-filter-data="bottomFilterData"
|
||||
@select="select"
|
||||
@showMore="showMore"
|
||||
@loadFilter="loadFilter"
|
||||
@@ -28,7 +30,7 @@
|
||||
<!-- 内容区域 -->
|
||||
<entity-list
|
||||
:list-data="listData"
|
||||
:entity-type="filterType"
|
||||
:entity-type="from"
|
||||
:page-obj="pageObjRight"
|
||||
@showDetail="entityDetail"
|
||||
@pageSize="pageSizeRight"
|
||||
@@ -41,18 +43,23 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { entityType } from '@/utils/constants'
|
||||
import { entityType, entityFilterType } from '@/utils/constants'
|
||||
import { ref } from 'vue'
|
||||
import LeftFilter from '@/components/entities/LeftFilter'
|
||||
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'
|
||||
export default {
|
||||
name: 'EntityExplorer',
|
||||
data () {
|
||||
return {
|
||||
searchContent: '',
|
||||
filterData: [],
|
||||
searchContentTemp: '', // 搜索框内的文本内容,按回车键后为searchContent赋值
|
||||
searchContent: '', // 查询语句
|
||||
searchParams: null, // 搜索参数,格式为[{ name: xxx, value: xxx }, ...]
|
||||
filterObj: {}, // 被选中的左侧过滤条件
|
||||
topFilterData: {}, // 左侧上方的过滤列表数据
|
||||
bottomFilterData: {}, // 左侧下方的过滤列表数据
|
||||
listData: [], // 右侧实体列表数据
|
||||
pageObjLeftTop: {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
@@ -68,7 +75,6 @@ export default {
|
||||
pageSize: 50,
|
||||
total: 0
|
||||
},
|
||||
listData: [],
|
||||
showDetail: false,
|
||||
typeName: '',
|
||||
currentEntity: {},
|
||||
@@ -81,175 +87,56 @@ export default {
|
||||
'cn-panel': Panel
|
||||
},
|
||||
methods: {
|
||||
async showMore (key) {
|
||||
enter () {
|
||||
if (!this.searchContentTemp) {
|
||||
this.reset()
|
||||
} else {
|
||||
this.searchContent = this.searchContentTemp
|
||||
}
|
||||
},
|
||||
async showMore (column) {
|
||||
let index = 0
|
||||
switch (key) {
|
||||
case 'country':
|
||||
case 'categoryGroup':
|
||||
case 'appCategory': {
|
||||
switch (column) {
|
||||
case entityFilterType.ip.country:
|
||||
case entityFilterType.domain.categoryGroup:
|
||||
case entityFilterType.app.appCategory: {
|
||||
this.pageObjLeftTop.pageNo++
|
||||
break
|
||||
}
|
||||
case 'asn':
|
||||
case 'reputationLevel':
|
||||
case 'appRisk': {
|
||||
this.pageObjLeftBottom.pageNo++
|
||||
case entityFilterType.ip.asn:
|
||||
case entityFilterType.domain.reputationLevel:
|
||||
case entityFilterType.app.appRisk: {
|
||||
index = 1
|
||||
this.pageObjLeftBottom.pageNo++
|
||||
break
|
||||
}
|
||||
default: break
|
||||
}
|
||||
const data = await this.loadFilterData(this.filterType, key)
|
||||
this.filterData[index].data = this.$_.concat(this.filterData[index].data, data)
|
||||
this.filterData[index].hasnotMore = this.$_.isEmpty(data) || data.length < 10
|
||||
},
|
||||
async loadFilterData (filterType, key) {
|
||||
let data
|
||||
let params = { type: key }
|
||||
switch (key) {
|
||||
case 'country':
|
||||
case 'categoryGroup':
|
||||
case 'appCategory': {
|
||||
params = { ...params, ...this.pageObjLeftTop }
|
||||
break
|
||||
const { topFilterData, bottomFilterData } = await this.queryFilterData({ column, q: this.searchContent, from: this.from })
|
||||
if (index === 1) {
|
||||
this.bottomFilterData = {
|
||||
...this.bottomFilterData,
|
||||
data: this.$_.concat(this.bottomFilterData.data, bottomFilterData),
|
||||
hasnotMore: bottomFilterData.length < 10
|
||||
}
|
||||
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 {
|
||||
const param = { type: key }
|
||||
param[parentKey] = (node.data)[parentKey]
|
||||
let req = null
|
||||
switch (filterType) {
|
||||
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)
|
||||
})
|
||||
this.topFilterData = {
|
||||
...this.topFilterData,
|
||||
data: this.$_.concat(this.topFilterData.data, topFilterData),
|
||||
hasnotMore: topFilterData.length < 10
|
||||
}
|
||||
}
|
||||
},
|
||||
entityDetail (entity, tabs) {
|
||||
this.typeName = `${this.filterType.toLowerCase()}EntityDetail`
|
||||
this.currentEntity = entity
|
||||
this.panelTabs = tabs
|
||||
this.showDetail = true
|
||||
}
|
||||
async search () {
|
||||
const params = { from: this.from, q: this.searchContent }
|
||||
this.listData = await getEntityList({ ...this.pageObjRight, ...params })
|
||||
this.pageObjRight.total = await getEntityCount(params)
|
||||
const { topFilterData, bottomFilterData } = await this.queryFilterData(params)
|
||||
this.topFilterData = topFilterData
|
||||
this.bottomFilterData = bottomFilterData
|
||||
},
|
||||
watch: {
|
||||
filterType (n) {
|
||||
/* 重置条件 */
|
||||
reset () {
|
||||
this.pageObjLeftTop = {
|
||||
pageNo: 1,
|
||||
pageSize: 10
|
||||
@@ -262,60 +149,305 @@ export default {
|
||||
pageNo: 1,
|
||||
pageSize: 50
|
||||
}
|
||||
this.handleData(n)
|
||||
}
|
||||
this.searchParams = null
|
||||
this.searchParams = []
|
||||
},
|
||||
async mounted () {
|
||||
const country = await this.loadFilterData(this.filterType, 'country')
|
||||
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 () {
|
||||
const filterType = ref(Object.keys(entityType)[0])
|
||||
return {
|
||||
entityType,
|
||||
filterType
|
||||
}
|
||||
}
|
||||
}
|
||||
const loadList = async (node, filterType, pageObj) => {
|
||||
let res
|
||||
const param = { ...pageObj }
|
||||
switch (filterType) {
|
||||
case 'ip': {
|
||||
res = await getEntityIpList(param)
|
||||
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 'domain': {
|
||||
res = await getEntityDomainList(param)
|
||||
case entityFilterType.ip.region:
|
||||
case entityFilterType.domain.categoryName:
|
||||
case entityFilterType.app.appSubcategory: {
|
||||
topLevel = 2
|
||||
hasTopColumn = true
|
||||
topFilterParams = { ...topFilterParams, column: key }
|
||||
break
|
||||
}
|
||||
case 'app': {
|
||||
res = await getEntityAppList(param)
|
||||
case entityFilterType.ip.asn:
|
||||
case entityFilterType.domain.reputationLevel:
|
||||
case entityFilterType.app.appRisk: {
|
||||
hasBottomColumn = true
|
||||
bottomFilterParams = { ...bottomFilterParams, column: key }
|
||||
break
|
||||
}
|
||||
default: break
|
||||
}
|
||||
return res
|
||||
})
|
||||
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 () {
|
||||
this.searchParams = []
|
||||
},
|
||||
setup () {
|
||||
const from = ref(Object.keys(entityType)[0])
|
||||
return {
|
||||
entityType, // 所有entity类型,用于header下拉框选择
|
||||
from // 当前entity类型,ip/domain/app
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user