CN-578 feat: 实体详情--关系图优化

This commit is contained in:
@changcode
2022-05-21 17:42:08 +08:00
parent efd299c569
commit 208560b34a
12 changed files with 767 additions and 22 deletions

View File

@@ -148,6 +148,7 @@
:chart-data="chartData"
:time-filter="timeFilter"
:query-params="queryParams"
:entity="entity"
></chart-relation-ship>
<chart-san-key

View File

@@ -1,34 +1,237 @@
<template>
<div class="chart-drawing" :id="`chart${chartInfo.id}`"></div>
<div class="chart-drawing" :id="`chart${chartInfo.id}`"></div>
<div class="ship" v-show="showList" v-ele-click-outside="shipChangeDown" id="ship">
<div class="ship-title">
<div class="ship-title-symbol" v-if="entityData.tableData.symbol"><img :src="entityData.tableData.symbol" /></div>
<div class="ship-title-name">{{ $_.get(entityData.tableData, 'name') || '-' }}</div>
</div>
<div class="ship-body">
<div class="ship-body-basicInfo" v-if="entityData.tableData.type === 'ip'">
<loading :loading="loadingIp"></loading>
<div class="ship-body-list">
<div class="ship-body-list-title">{{$t('overall.location')}}</div>
<div class="ship-body-list-value">{{ipLocationRegion(entityData)}}</div>
</div>
<div class="ship-body-list">
<div class="ship-body-list-title">ASN</div>
<div class="ship-body-list-value">{{entityData.asn || '-'}}</div>
</div>
</div>
<div class="ship-body-basicInfo" v-if="entityData.tableData.type === 'app'">
<loading :loading="loadingApp"></loading>
<div class="ship-body-list">
<div class="ship-body-list-title">APP ID</div>
<div class="ship-body-list-value">{{entityData.appId|| '-'}}</div>
</div>
<div class="ship-body-list">
<div class="ship-body-list-title">{{$t('entities.category')}}</div>
<div class="ship-body-list-value">{{entityData.category|| '-'}}</div>
</div>
<div class="ship-body-list">
<div class="ship-body-list-title">{{$t('entities.subcategory')}}</div>
<div class="ship-body-list-value">{{entityData.subcategory || '-'}}</div>
</div>
<div class="ship-body-list">
<div class="ship-body-list-title">{{$t('entities.riskLevel')}}</div>
<div class="ship-body-list-value">{{appRisk(entityData.appRisk) || '-'}}</div>
</div>
</div>
<div class="ship-body-basicInfo" v-if="entityData.tableData.type === 'domain'">
<loading :loading="loadingDomain"></loading>
<div class="ship-body-list">
<div class="ship-body-list-title">{{$t('entities.category')}}</div>
<div class="ship-body-list-value">{{entityData.domainCategory || '-'}}</div>
</div>
<div class="ship-body-list">
<div class="ship-body-list-title">{{$t('entities.domainDetail.categoryGroup')}}</div>
<div class="ship-body-list-value">{{entityData.domainCategoryGroup || '-'}}</div>
</div>
<div class="ship-body-list">
<div class="ship-body-list-title">{{$t('entities.reputationLevel')}}</div>
<div class="ship-body-list-value">{{entityData.domainReputationScore || '-'}}</div>
</div>
<div class="ship-body-list">
<div class="ship-body-list-title">{{$t('entities.registration')}}</div>
<div class="ship-body-list-value">{{entityData.domainWhoisAddress || '-'}}</div>
</div>
<div class="ship-body-list">
<div class="ship-body-list-title">{{$t('entities.org')}}</div>
<div class="ship-body-list-value">{{entityData.domainWhoisOrg || '-'}}</div>
</div>
</div>
<div class="ship-body-list ship-body-child">
<div class="ship-body-list-title">{{$t('overall.traffic')}}</div>
<div class="ship-body-list-child">
<loading :loading="loadingTraffic"></loading>
<div class="ship-body-list-child-left">
<div class="ship-body-list-child-title">{{$t('overall.received')}}</div>
<div class="ship-body-list-child-value">
<div class="ship-body-list-child-msg">{{unitConvert(entityData.bytesReceivedRate, unitTypes.byte).join(' ')}}ps</div>
<div class="ship-body-list-child-loading">
<div class="ship-body-list-child-charts" id="entityDetailReceived"></div>
</div>
</div>
</div>
<div class="ship-body-list-child-left">
<div class="ship-body-list-child-title">{{$t('overall.sent')}}</div>
<div class="ship-body-list-child-value">
<div class="ship-body-list-child-msg">{{unitConvert(entityData.bytesSentRate, unitTypes.byte).join(' ')}}ps</div>
<div class="ship-body-list-child-loading">
<div class="ship-body-list-child-charts" id="entityDetailSend"></div>
</div>
</div>
</div>
</div>
</div>
<div class="ship-body-list ship-body-child">
<div class="ship-body-list-title">{{$t('relationShip.serviceQuality')}}</div>
<div class="ship-body-list-child">
<loading :loading="loadingServiceQuality"></loading>
<div class="ship-body-list-child-left">
<div class="ship-body-list-child-trip">{{$t('networkAppPerformance.tripTime')}}: </div><span>{{unitConvert(entityData.establishLatencyMs, unitTypes.time).join(' ')}}</span>
</div>
<div class="ship-body-list-child-left">
<div class="ship-body-list-child-trip">{{$t('overall.packetLoss')}}: </div><span>{{unitConvert(entityData.pktRetransPercent, unitTypes.percent).join(' ')}}</span>
</div>
</div>
</div>
<div class="ship-body-list">
<loading :loading="loadingAlert"></loading>
<div class="ship-body-list-title">{{$t('relationShip.alert')}}</div>
<div class="ship-body-list-value security">
<template v-if="entityData.showShipServerity">
<template v-for="(item, index) in entityData.shipServerity" :key="index">
<template v-if="item.severityData && item.severityData.length > 0">
<span class="ship-body-list-security-alert" :class="iconClass(item)">{{ item.eventSeverity }}</span>
<span class="ship-body-list-security-length">{{ item.severityData.length }}</span>
</template>
</template>
</template>
<span v-else class="no-recent-alerts"><i class="cn-icon cn-icon-check"></i>{{$t('relationShip.noRecentAlerts')}}</span>
</div>
</div>
</div>
</div>
</template>
<script>
import * as echarts from 'echarts'
import loadsh from 'lodash'
import { relationShip } from './options/sankey'
import ChartRelationShip from '@/views/charts/charts/chart-relation-ship'
import { api } from '@/utils/api'
import { getSecond } from '@/utils/date-util'
import { unitTypes } from '@/utils/constants'
import unitConvert from '@/utils/unit-convert'
import loading from '@/components/common/Loading'
export default {
name: 'ChartRelationShip',
data () {
return {
myChart: null,
chartOption: null
chartOption: null,
showList: true,
timer: null,
relationShipIpUrl: {
entityType: 'ip',
trafficUrl: api.entityIpDetailTraffic,
performanceUrl: api.entityIpDetailPerformance,
ServerOverviewUrl: api.entityIpRelatedServerIpOverview,
basicProperties: api.entityIpDetailBasic
},
relationShipDomainUrl: {
entityType: 'domain',
trafficUrl: api.entityDomainDetailTraffic,
performanceUrl: api.entityDomainDetailPerformance,
ServerOverviewUrl: api.entityDomainRelatedServerDomainOverview,
basicProperties: api.entityDomainDetailBasic
},
relationShipAppUrl: {
entityType: 'app',
trafficUrl: api.entityAppDetailTraffic,
performanceUrl: api.entityAppDetailPerformance,
ServerOverviewUrl: api.entityAppRelatedServerAppOverview,
basicProperties: api.entityAppDetailBasic
},
loadingAlert: false,
loadingServiceQuality: false,
loadingTraffic: false,
loadingIp: false,
loadingDomain: false,
loadingApp: false,
tableName: ''
}
},
components: {
loading
},
mixins: [ChartRelationShip],
props: {
chartInfo: Object,
chartData: [Array, Object],
resultType: Object,
queryParams: Object
queryParams: Object,
entity: Object
},
methods: {
init (id) {
const chartParams = this.chartInfo.params
const name = this.$route.query.name
const path = window.location.protocol + '//' + window.location.host
const dom = document.getElementById(id)
const div = document.getElementById('ship')
!this.myChart && (this.myChart = echarts.init(dom))
this.chartOption = this.$_.cloneDeep(relationShip)
const data = []
const links = []
handleData(data, links, this.chartData)
const _this = this
let queryParams = {}
this.myChart.on('mouseover', function (params) {
this.tableName = params.data.name
_this.timer = setTimeout(() => {
_this.timers = setTimeout(() => {
console.log(dom.offsetHeight, params.event.event.offsetY, div.offsetTop)
if (dom.offsetHeight > (params.event.event.offsetY + div.offsetHeight)) {
console.log(1)
div.style.top = params.event.event.offsetY + 'px'
div.style.left = params.event.event.offsetX + 'px'
} else {
console.log(2)
div.style.top = params.event.event.offsetY - div.offsetHeight + 'px'
div.style.left = params.event.event.offsetX + 'px'
}
_this.showList = true
}, 300)
if (params.data.type === 'ip') {
queryParams = {
ip: params.data.name,
startTime: getSecond(_this.timeFilter.startTime),
endTime: getSecond(_this.timeFilter.endTime)
}
_this.queryEntityDetail(_this.relationShipIpUrl, params, queryParams)
} else if (params.data.type === 'app_name') {
queryParams = {
appName: params.data.name,
startTime: getSecond(_this.timeFilter.startTime),
endTime: getSecond(_this.timeFilter.endTime)
}
_this.queryEntityDetail(_this.relationShipAppUrl, params, queryParams)
} else if (params.data.type === 'domain') {
queryParams = {
domain: params.data.name,
startTime: getSecond(_this.timeFilter.startTime),
endTime: getSecond(_this.timeFilter.endTime)
}
_this.queryEntityDetail(_this.relationShipDomainUrl, params, queryParams)
}
}, 500)
})
this.myChart.on('mouseout', function (params) {
clearTimeout(_this.timer)
clearTimeout(_this.timers)
})
this.chartOption.series[0].data = data
this.chartOption.series[0].links = links
this.myChart.setOption(this.chartOption)
@@ -48,32 +251,51 @@ export default {
function handleStyle (item) {
const style = {}
style.label = {
position: 'top'
}
style.symbolSize = 50
style.label.color = '#999'
switch (item.type) {
case 'app_id': {
style.itemStyle = { color: '#73DEB3' }
style.symbol = 'circle'
case 'app_name': {
style.symbolSize = 55
style.type = item.type
if (name === item.name) {
style.label.color = '#333'
style.symbol = `image://${path}/images/entity-symbol/app-active.svg`
} else {
style.symbol = `image://${path}/images/entity-symbol/app.svg`
}
break
}
case 'domain': {
style.itemStyle = { color: '#73A0FA' }
style.symbol = 'circle'
style.symbolSize = 55
style.type = item.type
if (name === item.name) {
style.label.color = '#333'
style.symbol = `image://${path}/images/entity-symbol/domain-active.svg`
} else {
style.symbol = `image://${path}/images/entity-symbol/domain.svg`
}
break
}
case 'client_ip': {
style.itemStyle = { color: '#E8F6FF', borderColor: '#C9C9C9' }
style.symbol = 'roundRect'
style.symbolSize = [80, 25]
break
}
case 'server_ip': {
style.itemStyle = { color: '#E2FCEF', borderColor: '#C9C9C9' }
style.symbol = 'roundRect'
style.symbolSize = [80, 25]
case 'ip': {
style.symbolSize = 40
style.type = item.type
if (name === item.name) {
style.label.color = '#333'
style.symbol = `image://${path}/images/entity-symbol/ip-active.svg`
} else {
style.symbol = `image://${path}/images/entity-symbol/ip.svg`
}
break
}
}
return style
}
},
shipChangeDown () {
this.showList = false
}
},
watch: {
@@ -83,10 +305,12 @@ export default {
this.init(`chart${this.chartInfo.id}`)
}
}
},
setup () {
return {
unitConvert,
unitTypes
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,269 @@
import _ from 'lodash'
import { get } from '@/utils/http'
import * as echarts from 'echarts'
import { entityListLineOption } from '@/views/charts/charts/chart-options'
import { riskLevelMapping, unitTypes } from '@/utils/constants'
import unitConvert from '@/utils/unit-convert'
import { shallowRef } from 'vue'
export default {
props: {
entity: Object,
timeFilter: Object
},
data () {
return {
entityData: {
chartOptionSent: null,
chartOptionReceived: null,
chartOption: null,
sentChart: null,
receivedChart: null,
tableData: {}
},
chartOption: null,
echartsArray: []
}
},
computed: {
iconClass () {
return function (entityData) {
let className
switch (entityData.eventSeverity) {
case ('critical'): {
className = 'critical'
break
}
case ('high'): {
className = 'high'
break
}
case ('info'): {
className = 'info'
break
}
case ('medium'): {
className = 'medium'
break
}
case ('low'): {
className = 'low'
break
}
default:
break
}
return className
}
},
appRisk () {
return function (level) {
const m = riskLevelMapping.find(mapping => {
return mapping.value == level
})
return (m && m.name) || level
}
},
ipLocationRegion () {
return function (entityData) {
let result = ''
if (entityData.country) {
result += `${entityData.country},`
}
if (entityData.province) {
result += `${entityData.province},`
}
if (entityData.city) {
result += `${entityData.city},`
}
result = result.substr(0, result.length - 1)
if (!result) {
result = '-'
}
return result
}
}
},
methods: {
queryEntityDetailTraffic (urlObj, params, queryParams) {
if (!echarts.init(document.getElementById('entityDetailSend')) && !echarts.init(document.getElementById('entityDetailReceived'))) return ''
this.chartOption = _.cloneDeep(entityListLineOption)
this.sentChart = echarts.init(document.getElementById('entityDetailSend'))
this.receivedChart = echarts.init(document.getElementById('entityDetailReceived'))
this.loadingTraffic = true
get(urlObj.trafficUrl, queryParams).then(response => {
if (response.code === 200 && response.data.result && response.data.result.length > 0) {
response.data.result.forEach(t => {
if (t.legend === 'bytesSentRate') {
this.entityData.bytesSentRate = _.nth(t.values, -3)[1]
this.chartOptionSent = {
...this.chartOption,
series: [
{
name: this.$t('entities.sentThroughput'),
type: 'line',
legendHoverLink: false,
itemStyle: {
normal: {
lineStyle: {
width: 1
}
}
},
color: '#69b072',
data: _.dropRight(t.values, 2).map(v => [Number(v[0]) * 1000, Number(v[1]), unitTypes.byte]),
showSymbol: false
}
]
}
} else if (t.legend === 'bytesReceivedRate') {
this.entityData.bytesReceivedRate = t.aggregation.last
this.chartOptionReceived = {
...this.chartOption,
series: [
{
name: this.$t('entities.receivedThroughput'),
type: 'line',
legendHoverLink: false,
itemStyle: {
normal: {
lineStyle: {
width: 1
}
}
},
color: '#7899c6',
data: t.values.map(v => [Number(v[0]) * 1000, Number(v[1]), unitTypes.byte]),
showSymbol: false
}
]
}
}
})
this.echartsArray.push(shallowRef(this.sentChart), shallowRef(this.receivedChart))
this.sentChart.setOption(this.chartOptionSent)
this.receivedChart.setOption(this.chartOptionReceived)
this.loadingTraffic = false
} else {
this.loadingTraffic = false
}
})
},
queryEntityDetaServerOverview (urlObj, params, queryParams) {
this.loadingServiceQuality = true
get(urlObj.ServerOverviewUrl, queryParams).then(response => {
if (response.code === 200) {
this.entityData.establishLatencyMs = response.data.result.establishLatencyMs
this.entityData.pktRetransPercent = response.data.result.pktRetransPercent
}
this.loadingServiceQuality = false
})
},
queryEntityDetailPerformance (urlObj, params, queryParams) {
this.loadingAlert = true
get(urlObj.performanceUrl, queryParams).then(response => {
if (response.code === 200) {
this.entityData.shipServerity = []
const criticalList = response.data.result.filter(item => item.eventSeverity === 'critical')
const highList = response.data.result.filter(item => item.eventSeverity === 'high')
const infoList = response.data.result.filter(item => item.eventSeverity === 'info')
const mediumList = response.data.result.filter(item => item.eventSeverity === 'medium')
const lowList = response.data.result.filter(item => item.eventSeverity === 'low')
this.entityData.shipServerity.push(
{
severityData: criticalList,
eventSeverity: 'critical'
}, {
severityData: highList,
eventSeverity: 'high'
}, {
severityData: infoList,
eventSeverity: 'info'
}, {
severityData: mediumList,
eventSeverity: 'medium'
}, {
severityData: lowList,
eventSeverity: 'low'
}
)
this.entityData.shipServerity.forEach(item => {
if (item.severityData.length > 0) {
this.entityData.showShipServerity = true
} else {
this.entityData.showShipServerity = false
}
})
}
this.loadingAlert = false
})
},
getBasicProperties (urlObj, params, queryParams) {
this.loadingIp = true
this.loadingDomain = true
this.loadingApp = true
get(urlObj.basicProperties, queryParams).then(response => {
if (response.code === 200) {
if (urlObj.entityType === 'ip') {
this.entityData = {
...this.entityData,
asn: response.data.result.asn,
country: response.data.result.country,
province: response.data.result.province,
city: response.data.result.city
}
} else if (urlObj.entityType === 'domain') {
this.entityData = {
...this.entityData,
domainCategory: response.data.result.domainCategory,
domainCategoryGroup: response.data.result.domainCategoryGroup,
domainDescription: response.data.result.domainDescription,
domainReputationScore: response.data.result.domainReputationScore,
domainWhoisAddress: response.data.result.domainWhoisAddress,
domainWhoisOrg: response.data.result.domainWhoisOrg
}
} else if (urlObj.entityType === 'app') {
this.entityData = {
...this.entityData,
name: response.data.result.name,
appId: response.data.result.appId,
category: response.data.result.category,
subcategory: response.data.result.subcategory
}
}
}
this.loadingIp = false
this.loadingDomain = false
this.loadingApp = false
})
},
queryEntityDetail (urlObj, params, queryParams) {
this.entityData.tableData.name = params.data.name
this.entityData.tableData.symbol = params.data.symbol.slice(8)
this.entityData.tableData.type = urlObj.entityType
this.queryEntityDetailTraffic(urlObj, params, queryParams)
this.queryEntityDetailPerformance(urlObj, params, queryParams)
this.queryEntityDetaServerOverview(urlObj, params, queryParams)
this.getBasicProperties(urlObj, params, queryParams)
},
resize () {
this.echartsArray.forEach(item => { item.value.resize() })
}
},
setup () {
return {
unitTypes,
unitConvert
}
},
mounted () {
this.debounceFunc = this.$_.debounce(this.resize, 200)
window.addEventListener('resize', this.debounceFunc)
},
beforeUnmount () {
window.removeEventListener('resize', this.debounceFunc)
}
}