CN-139 feat: entity列表改版

This commit is contained in:
chenjinsong
2021-09-30 00:50:11 +08:00
parent b1cd2b662a
commit 7cc59a7f2f
8 changed files with 369 additions and 112 deletions

View File

@@ -321,6 +321,54 @@ const singleValueLine = {
}
]
}
export const entityListLineOption = {
tooltip: {
appendToBody: true,
trigger: 'axis',
textStyle: {
width: '20px',
overflow: 'truncate'
},
formatter: axiosFormatter,
show: true,
className: 'nz-chart-tooltip',
extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important'
},
xAxis: {
type: 'time',
show: false
},
yAxis: {
type: 'value',
show: false
},
animation: false,
grid: {
left: 0,
bottom: 2,
top: 5,
right: 0
},
color: chartColor,
legend: {
show: false
},
series: [
{
type: 'line',
legendHoverLink: false,
itemStyle: {
normal: {
lineStyle: {
width: 2
}
}
},
data: [],
showSymbol: false
}
]
}
const relationShip = {
grid: {
left: 0,

View File

@@ -8,6 +8,10 @@ export const tableTitleMapping = {
label: i18n.global.t('overall.serverIp'),
prop: 'serverIp'
},
ip: {
label: 'IP',
prop: 'ip'
},
appId: {
label: 'APP ID',
prop: 'appId'
@@ -37,6 +41,9 @@ export const legendMapping = {
bytes_received_rate: i18n.global.t('trafficSummary.throughputPerSecondC2s'),
bytes_sent_rate: i18n.global.t('trafficSummary.throughputPerSecondS2c'),
bytes_rate: i18n.global.t('trafficSummary.throughputPerSecond'),
ip_bytes_received_rate: i18n.global.t('trafficSummary.receivedPerSecond'),
ip_bytes_sent_rate: i18n.global.t('trafficSummary.sentPerSecond'),
ip_bytes_rate: i18n.global.t('trafficSummary.throughputPerSecond'),
session_rate: i18n.global.t('trafficSummary.sessionsPerSecond'),
packets_rate: i18n.global.t('trafficSummary.packetsPerSecond'),
packets_received_rate: i18n.global.t('trafficSummary.packetsPerSecondC2s'),

View File

@@ -120,7 +120,9 @@
width: 100%;
}
}
&>.cn-chart__whois>.cn-chart__body {
overflow: auto;
}
&>.cn-chart__echarts, &>.cn-chart__table, &>.cn-chart__map, &>.cn-chart__group, &>.cn-chart__whois, &>.cn-chart__dns-record, &>.cn-chart__app-basic {
display: flex;
flex-direction: column;

View File

@@ -1,7 +1,7 @@
<template>
<div class="entity-list">
<div class="entity-list__content">
<div class="cn-entity" v-for="d in listData" :key="d.id">
<div class="cn-entity" v-for="(d, i) in entityList" :key="i">
<div class="cn-entity__header">
<div class="header__content" :title="d.ip||d.domainName||d.appName">
<div class="header__icon"><i :class="iconClass"></i></div>
@@ -15,7 +15,7 @@
{{d.domainName || 'Unknown'}}
</div>
<div class="content__desc" >
<span class="desc__label">{{$t('entities.reputationLevel')}}:</span>
<span class="desc__label">{{$t('entities.reputationLevel')}}:&nbsp;</span>
<span>{{d.reputationLevel || '-'}}</span>
</div>
</template>
@@ -24,7 +24,7 @@
{{d.appName || 'Unknown'}}
</div>
<div class="content__desc" >
<span class="desc__label">{{$t('entities.risk')}}:</span>
<span class="desc__label">{{$t('entities.risk')}}:&nbsp;</span>
<span>{{d.appRisk || '-'}}</span>
</div>
</template>
@@ -45,13 +45,13 @@
<span class="body__row-label"><i class="cn-icon cn-icon-cloud"></i>{{$t('entities.asn')}}</span>
<div class="body__row-value" :title="d.asn">{{d.asn || '-'}}</div>
</div>
<!-- 吞吐量-->
<div class="entity-statics-down" style=" "><i class="cn-icon cn-icon-fall entity-statics-icon" style=""></i>22 bps</div>
<div class="entity-statics-up" ><i class="cn-icon cn-icon-rise" style=""></i>0 bps</div>
<div class="body__detail"
@click="entityDetail({ip: d.ip, type: 4})">{{$t('overall.detail')}}></div>
<!-- 曲线-->
<div class="body__drawing-box">
<div class="body__drawing" :id="`entityListChart${d.id}`"></div>
</div>
<div class="entity-statics-down"><i class="cn-icon cn-icon-fall entity-statics-icon" style=""></i>{{d.latestReceived}} bps</div>
<div class="entity-statics-up" ><i class="cn-icon cn-icon-rise" style=""></i>{{d.latestSent}} bps</div>
<div class="body__detail" @click="entityDetail({ip: d.ip, type: 4})">{{$t('overall.detail')}}></div>
</template>
<template v-else-if="from === 'domain'">
<div class="body__row">
@@ -66,10 +66,12 @@
<span class="body__row-label"><i class="cn-icon cn-icon-credit"></i>{{$t('entities.credit')}}</span>
<div class="body__row-value" :title="d.reputationScore">{{d.reputationScore || '-'}}</div>
</div>
<!-- 吞吐量-->
<div class="entity-statics-down" style=" "><i class="cn-icon cn-icon-fall entity-statics-icon" style=""></i>22 bps</div>
<div class="entity-statics-up" ><i class="cn-icon cn-icon-rise" style=""></i>0 bps</div>
<!-- 曲线-->
<div class="body__drawing-box">
<div class="body__drawing" :id="`entityListChart${d.id}`"></div>
</div>
<div class="entity-statics-down" style=" "><i class="cn-icon cn-icon-fall entity-statics-icon" style=""></i>{{d.latestReceived}} bps</div>
<div class="entity-statics-up" ><i class="cn-icon cn-icon-rise" style=""></i>{{d.latestSent}} bps</div>
<div class="body__detail" @click="entityDetail({domain: d.domainName, type: 5})">{{$t('overall.detail')}}></div>
</template>
<template v-else-if="from === 'app'">
@@ -85,10 +87,12 @@
<span class="body__row-label"><i class="cn-icon cn-icon-sub-category"></i>{{$t('entities.subcategory')}}</span>
<div class="body__row-value" :title="d.appSubategory">{{d.appSubategory || '-'}}</div>
</div>
<!-- 吞吐量-->
<div class="entity-statics-down" style=" "><i class="cn-icon cn-icon-fall entity-statics-icon" style=""></i>22 bps</div>
<div class="entity-statics-up" ><i class="cn-icon cn-icon-rise" style=""></i>0 bps</div>
<!-- 曲线-->
<div class="body__drawing-box">
<div class="body__drawing" :id="`entityListChart${d.id}`"></div>
</div>
<div class="entity-statics-down" style=" "><i class="cn-icon cn-icon-fall entity-statics-icon" style=""></i>{{d.latestReceived}} bps</div>
<div class="entity-statics-up" ><i class="cn-icon cn-icon-rise" style=""></i>{{d.latestSent}} bps</div>
<div class="body__detail" @click="entityDetail({app: d.appName, type: 6})">{{$t('overall.detail')}}></div>
</template>
</div>
@@ -110,6 +114,13 @@
</template>
<script>
import { get } from '@/utils/http'
import { api } from '@/utils/api'
import * as echarts from 'echarts'
import { getChartColor, entityListLineOption } from '@/components/charts/chart-options'
import { legendMapping } from '@/components/charts/chart-table-title'
import unitConvert from '@/utils/unit-convert'
import { unitTypes } from '@/utils/constants'
export default {
name: 'EntityList',
@@ -124,7 +135,8 @@
data () {
return {
showDetail: false,
typeName: ''
typeName: '',
entityList: []
}
},
computed: {
@@ -193,6 +205,155 @@
entityDetail (params) {
this.$emit('showDetail', { ...params, icon: this.iconClass })
}
},
setup () {
return {
chartOption: entityListLineOption
}
},
watch: {
listData: {
deep: true,
immediate: true,
handler (n, o) {
if (!this.$_.isEmpty(n) && !this.$_.isEqual(n, o)) {
this.entityList = []
setTimeout(() => {
const now = new Date()
const queryParams = { startTime: Math.floor(now.getTime() / 1000 - 3600), endTime: Math.floor(now.getTime() / 1000) }
switch (this.from) {
case ('ip'): {
n.forEach(data => {
const entity = { ...data }
let chartOption
get(api.ipThroughput, { ...queryParams, ip: entity.ip }).then(response => {
if (response.code === 200) {
const seriesTemplate = this.chartOption.series[0]
const series = response.data.result.filter(r => r.legend !== 'bytes_rate').map((r, i) => {
if (r.legend === 'bytes_sent_rate') {
entity.latestSent = unitConvert(r.values[r.values.length - 1][1], unitTypes.byte)[0]
} else if (r.legend === 'bytes_received_rate') {
entity.latestReceived = unitConvert(r.values[r.values.length - 1][1], unitTypes.byte)[0]
}
return {
...seriesTemplate,
name: legendMapping[`ip_${r.legend}`],
data: r.values.map(v => [Number(v[0]) * 1000, Number(v[1]), unitTypes.byte]),
itemStyle: {
normal: {
color: getChartColor(i),
lineStyle: {
width: 2
}
}
}
}
})
chartOption = {
...this.chartOption,
series
}
}
}).finally(() => {
this.entityList.push(entity)
this.$nextTick(() => {
const myChart = echarts.init(document.getElementById(`entityListChart${entity.id}`))
myChart.setOption(chartOption)
})
})
})
break
}
case ('domain'): {
n.forEach(data => {
const entity = { ...data }
let chartOption
get(api.domainThroughput, { ...queryParams, domain: entity.domainName }).then(response => {
if (response.code === 200) {
const seriesTemplate = this.chartOption.series[0]
const series = response.data.result.filter(r => r.legend !== 'bytes_rate').map((r, i) => {
if (r.legend === 'bytes_sent_rate') {
entity.latestSent = unitConvert(r.values[r.values.length - 1][1], unitTypes.byte)[0]
} else if (r.legend === 'bytes_received_rate') {
entity.latestReceived = unitConvert(r.values[r.values.length - 1][1], unitTypes.byte)[0]
}
return {
...seriesTemplate,
name: legendMapping[r.legend],
data: r.values.map(v => [Number(v[0]) * 1000, Number(v[1]), unitTypes.byte]),
itemStyle: {
normal: {
color: getChartColor(i),
lineStyle: {
width: 2
}
}
}
}
})
chartOption = {
...this.chartOption,
series
}
}
}).finally(() => {
this.entityList.push(entity)
this.$nextTick(() => {
const myChart = echarts.init(document.getElementById(`entityListChart${entity.id}`))
myChart.setOption(chartOption)
})
})
})
break
}
case ('app'): {
n.forEach(data => {
const entity = { ...data }
let chartOption
get(api.appThroughput, { ...queryParams, app: entity.appName }).then(response => {
if (response.code === 200) {
const seriesTemplate = this.chartOption.series[0]
const series = response.data.result.filter(r => r.legend !== 'bytes_rate').map((r, i) => {
if (r.legend === 'bytes_sent_rate') {
entity.latestSent = unitConvert(r.values[r.values.length - 1][1], unitTypes.byte)[0]
} else if (r.legend === 'bytes_received_rate') {
entity.latestReceived = unitConvert(r.values[r.values.length - 1][1], unitTypes.byte)[0]
}
return {
...seriesTemplate,
name: legendMapping[r.legend],
data: r.values.map(v => [Number(v[0]) * 1000, Number(v[1]), unitTypes.byte]),
itemStyle: {
normal: {
color: getChartColor(i),
lineStyle: {
width: 2
}
}
}
}
})
chartOption = {
...this.chartOption,
series
}
}
}).finally(() => {
this.entityList.push(entity)
this.$nextTick(() => {
const myChart = echarts.init(document.getElementById(`entityListChart${entity.id}`))
myChart.setOption(chartOption)
})
})
})
break
}
default: break
}
}, 1000)
}
}
}
}
}
</script>

View File

@@ -165,6 +165,20 @@
padding: 5px 0;
justify-content: left;
}
.body__drawing-box {
position: relative;
height: 100px;
.chart__loading {
top: 0;
height: 100%;
}
}
.body__drawing {
position: absolute;
padding: 10px 30px;
height: 100px;
width: 100%;
}
.body__row-label {
padding-right: 0px;

View File

@@ -21,7 +21,10 @@ export const api = {
chart: '/visual/chart',
entityList: '/interface/entity/list',
entityCount: '/interface/entity/total',
entityFilter: '/interface/entity/filter'
entityFilter: '/interface/entity/filter',
ipThroughput: '/interface/entity/ip/detail/throughput',
domainThroughput: '/interface/entity/domain/detail/throughput',
appThroughput: '/interface/entity/app/detail/throughput'
}
/* panel */

View File

@@ -409,10 +409,10 @@
<div class="cn-chart__body">
<div style="display: flex; justify-content: space-between; width: 100%;">
<el-descriptions :column="1" style="padding: 20px 30px;">
<el-descriptions-item :label="$t('overall.appName')">{{detailData ? detailData.name : '-'}}</el-descriptions-item>
<el-descriptions-item :label="$t('overall.appFullName') + ':'">{{detailData ? detailData.allName : '-'}}</el-descriptions-item>
<el-descriptions-item :label="$t('overall.technology')">{{detailData ? detailData.tech : '-'}}</el-descriptions-item>
<el-descriptions-item :label="$t('overall.remark')">{{detailData ? detailData.remark : '-'}}</el-descriptions-item>
<el-descriptions-item :label="$t('overall.appName') + ':'">{{detailData.name || '-'}}</el-descriptions-item>
<el-descriptions-item :label="$t('overall.appFullName') + ':'">{{detailData.allName || '-'}}</el-descriptions-item>
<el-descriptions-item :label="$t('overall.technology') + ':'">{{detailData.tech || '-'}}</el-descriptions-item>
<el-descriptions-item :label="$t('overall.remark') + ':'">{{detailData.description || '-'}}</el-descriptions-item>
</el-descriptions>
<div style="display: flex;">
<single-value
@@ -425,7 +425,7 @@
<span>{{$t('entities.category')}}</span>
</template>
<template #data>
<span>{{detailData ? detailData.category : '-'}}</span>
<span>{{detailData.category ? detailData.category : '-'}}</span>
</template>
</single-value>
<single-value
@@ -438,7 +438,7 @@
<span>{{$t('entities.subcategory')}}</span>
</template>
<template #data>
<span>{{detailData ? detailData.subcategory : '-'}}</span>
<span>{{detailData.subcategory ? detailData.subcategory : '-'}}</span>
</template>
</single-value>
<single-value
@@ -451,7 +451,7 @@
<span>{{$t('entities.reputationLevel')}}</span>
</template>
<template #data>
<span>{{detailData ? detailData.risk : '-'}}</span>
<span>{{detailData.risk ? detailData.risk : '-'}}</span>
</template>
</single-value>
</div>
@@ -725,6 +725,20 @@ export default {
this.isError = true
this.errorInfo = e
})
} else if (this.isAppBasicInfo) {
const queryParams = { app: this.entity.app }
get(replaceUrlPlaceholder(chartParams.url, queryParams)).then(response => {
if (response.code === 200) {
this.detailData = response.data.result
} else {
this.isError = true
this.noData = true
this.errorInfo = response.msg || response.message || 'Unknown'
}
}).catch(e => {
this.isError = true
this.errorInfo = e
})
}
} catch (e) {
console.error(e)
@@ -970,7 +984,7 @@ export default {
if (data.length > 0) {
const dataColumns = Object.keys(data[0]) // 返回数据的字段
const columns = dataColumns.map(c => tableTitleMapping[c]) // 展示字段
const keys = ['clientIp', 'serverIp', 'appId', 'app', 'domain']
const keys = ['clientIp', 'serverIp', 'ip', 'appId', 'app', 'domain']
return columns.sort((a, b) => {
if (keys.indexOf(a.prop) > -1) {
return -1
@@ -1048,7 +1062,7 @@ export default {
this.chartOption.series = response.data.result.map(r => {
return {
...seriesTemplate,
name: legendMapping[r.legend] ? legendMapping[r.legend] : lineToSpace(r.legend),
name: legendMapping[`${this.entity.ip ? 'ip_' : ''}${r.legend}`] ? legendMapping[`${this.entity.ip ? 'ip_' : ''}${r.legend}`] : lineToSpace(r.legend),
data: r.values.map(v => [Number(v[0]) * 1000, Number(v[1]), chartParams.unitType])
}
})
@@ -1240,11 +1254,12 @@ export default {
]
this.myChart.setOption(this.chartOption)
this.myChart2.setOption(this.chartOption)
/* get(replaceUrlPlaceholder(chartParams.url, { app: this.entity.app })).then(response => {
get(replaceUrlPlaceholder(chartParams.url, { app: this.entity.app })).then(response => {
if (response.code === 200) {
if (this.$_.isEmpty(response.data.result)) {
this.noData = true
} else {
this.detailData = response.data.result
this.noData = false
}
}
@@ -1252,7 +1267,7 @@ export default {
setTimeout(() => {
this.loading = false
}, 250)
}) */
})
},
initEchartsWithStatistics (chartParams) {
const queryParams = { startTime: parseInt(this.timeFilter.startTime / 1000), endTime: parseInt(this.timeFilter.endTime / 1000), ...this.entity }
@@ -1640,6 +1655,9 @@ export default {
.hosted-domain__list-row, .related-domain__list-row {
padding: 5px 30px;
color: #3976CB;
i {
color: #B8C1D1;
}
}
}
.hosted-domain__chart, .related-domain__chart {
@@ -1671,14 +1689,14 @@ export default {
.domain-detail-list__label {
display: table-cell;
padding: 15px 30px;
padding: 13px 30px;
border-bottom: 1px solid $--content-right-background-color;
width: 170px;
color: #6B717B;
}
.domain-detail-list__content {
display: table-cell;
padding: 15px 0 ;
padding: 13px 0 ;
border-bottom: 1px solid $--content-right-background-color;
color: #3976CB;
}

View File

@@ -122,7 +122,12 @@ export default {
},
async search () {
const params = { from: this.from, q: doubleQuotationToSingle(this.searchContent) }
this.listData = await getEntityList({ ...this.pageObjRight, ...params })
this.listData = (await getEntityList({ ...this.pageObjRight, ...params })).map(d => ({
...d,
id: window.btoa(d.ip || d.domainName || d.appId),
latestSent: null,
latestReceived: null
}))
this.pageObjRight.total = await getEntityCount(params)
const { topFilterData, bottomFilterData } = await this.queryFilterData(params)
this.topFilterData = topFilterData
@@ -316,7 +321,6 @@ export default {
this.search()
},
entityDetail (entity, tabs) {
const entityName = entity.domain || entity.ip || entity.appId
this.typeName = `${this.from.toLowerCase()}EntityDetail`
this.currentEntity = entity
this.showDetail = true