diff --git a/public/images/entity-symbol/app-active.svg b/public/images/entity-symbol/app-active.svg new file mode 100644 index 00000000..41e10409 --- /dev/null +++ b/public/images/entity-symbol/app-active.svg @@ -0,0 +1,16 @@ + + + 编组 5 + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/images/entity-symbol/app.svg b/public/images/entity-symbol/app.svg new file mode 100644 index 00000000..bf0b1be4 --- /dev/null +++ b/public/images/entity-symbol/app.svg @@ -0,0 +1,14 @@ + + + 编组 + + + + + + + + + + + \ No newline at end of file diff --git a/public/images/entity-symbol/domain-active.svg b/public/images/entity-symbol/domain-active.svg new file mode 100644 index 00000000..1661ad61 --- /dev/null +++ b/public/images/entity-symbol/domain-active.svg @@ -0,0 +1,18 @@ + + + 编组 6 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/images/entity-symbol/domain.svg b/public/images/entity-symbol/domain.svg new file mode 100644 index 00000000..84b3bd8e --- /dev/null +++ b/public/images/entity-symbol/domain.svg @@ -0,0 +1,20 @@ + + + 编组 2 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/images/entity-symbol/ip-active.svg b/public/images/entity-symbol/ip-active.svg new file mode 100644 index 00000000..0f70074f --- /dev/null +++ b/public/images/entity-symbol/ip-active.svg @@ -0,0 +1,18 @@ + + + 编组 3 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/images/entity-symbol/ip.svg b/public/images/entity-symbol/ip.svg new file mode 100644 index 00000000..bc03ff59 --- /dev/null +++ b/public/images/entity-symbol/ip.svg @@ -0,0 +1,18 @@ + + + 编组 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/css/components/index.scss b/src/assets/css/components/index.scss index 416dcbd8..6e48cd4d 100644 --- a/src/assets/css/components/index.scss +++ b/src/assets/css/components/index.scss @@ -41,6 +41,7 @@ @import './views/charts/chartAlarmInfo'; @import './views/chartHeader'; @import './views/charts/chartMap'; +@import 'views/charts/chartRelationShipList'; @import './views/report/builtinReport'; diff --git a/src/assets/css/components/views/charts/chartRelationShipList.scss b/src/assets/css/components/views/charts/chartRelationShipList.scss new file mode 100644 index 00000000..3d976d16 --- /dev/null +++ b/src/assets/css/components/views/charts/chartRelationShipList.scss @@ -0,0 +1,141 @@ +.ship { + width: 320px; + background: #FFFFFF; + border: 1px solid rgba(119,131,145,0.60); + box-shadow: 0 6px 16px 0 rgba(0,0,0,0.08); + border-radius: 3px; + position: absolute; + display: flex; + flex-direction: column; + .ship-title { + display: flex; + height: 49px; + border-bottom: 1px solid #E6EAED; + padding-left: 10px; + .ship-title-name { + line-height: 49px; + font-size: 16px; + color: #333333; + letter-spacing: 0; + font-weight: 500; + } + .ship-title-symbol { + padding-right: 5px; + line-height: 65px; + img { + width: 30px; + height: 30px; + } + } + } + .ship-body { + .ship-body-basicInfo { + position: relative; + } + display: flex; + flex-direction: column; + margin: 7px; + .ship-body-list.ship-body-child { + display: flex; + flex-direction: column; + .ship-body-list-child { + position: relative; + .ship-body-list-child-left { + margin: 3px 0 3px 10px; + display: flex; + font-size: 12px; + color: #999999; + font-weight: 400; + .ship-body-list-child-title { + width: 120px; + } + .ship-body-list-child-trip { + width: 160px; + } + .ship-body-list-child-value { + display: flex; + .ship-body-list-child-msg { + margin-right: 7px; + } + .ship-body-list-child-loading { + .ship-body-list-child-charts { + width: 38px; + height: 16px; + } + } + } + } + } + } + .ship-body-list { + margin:0 0 5px 20px; + text-align: left; + display: flex; + width: calc(100% - 20px); + position: relative; + .chart__loading { + i.el-icon-loading { + font-size: 16px; + top: calc(50% - 8.5px); + } + } + .ship-body-list-title { + width: 130px; + font-size: 14px; + color: #666666; + letter-spacing: 0; + font-weight: 400; + } + .security.ship-body-list-security-length { + width: 140px; + } + .ship-body-list-value { + font-size: 14px; + color: #333333; + letter-spacing: 0; + font-weight: 400; + word-wrap: break-word; + } + .ship-body-list-value.security { + font-size: 12px; + color: #FFFFFF; + font-weight: 400; + text-align: center; + border-radius: 2px; + margin: 0 3px; + word-wrap: break-word; + .ship-body-list-security-alert { + padding: 1px 2px; + } + .ship-body-list-security-length { + margin: 0 10px 0 3px; + font-size: 14px; + color: #333333; + letter-spacing: 0; + font-weight: 400; + } + .no-recent-alerts { + font-size: 12px; + color: #333333; + font-weight: 400; + line-height: 19px; + } + .ship-body-list-security-alert.critical { + background-color: #D84C4C; + } + .ship-body-list-security-alert.high { + background-color: #FF9A79; + } + .ship-body-list-security-alert.info { + background-color: #D1BD50; + } + .ship-body-list-security-alert.medium { + background-color: #FFB65A; + } + .ship-body-list-security-alert.low { + background-color: #FFD82D; + } + } + } + } +} diff --git a/src/utils/api.js b/src/utils/api.js index 22744dde..0d2ce3c1 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -67,6 +67,8 @@ export const api = { entityAppDetailSecurity: '/interface/entity/detail/overview/app/securityEvent', entityAppRelatedServerDomain: '/interface/entity/detail/overview/app/relatedDomain', entityAppRelatedServerIp: '/interface/entity/detail/overview/app/relatedServerIp', + entityAppRelatedServerAppOverview: '/interface/entity/detail/app/serviceOverview', + entityAppDetailBasic: '/interface/entity/detail/app/basic', // domain detail entityDomainDetailBasic: '/interface/entity/detail/overview/domain/basic', entityDomainDetailTraffic: '/interface/entity/detail/overview/domain/traffic', @@ -80,6 +82,7 @@ export const api = { entityDomainRelatedServerApp: '/interface/entity/detail/overview/domain/relatedApp', entityDetectionsIp: '/interface/entity/detail/overview/ip/dnsInfo', entityDetectionsIpQueryRate: '/interface/entity/detail/overview/ip/dnsQueryRate', + entityDomainRelatedServerDomainOverview: '/interface/entity/detail/domain/serviceOverview', // ip detail entityIpDetailTraffic: '/interface/entity/detail/overview/ip/traffic', entityIpDetailTrafficMap: '/interface/entity/detail/ip/trafficMap', @@ -93,6 +96,8 @@ export const api = { entityIpDetailSecurity: '/interface/entity/detail/overview/ip/securityEvent', entityIpRelatedServerDomain: '/interface/entity/detail/overview/ip/relatedDomain', entityIpRelatedServerApp: '/interface/entity/detail/overview/ip/relatedApp', + entityIpRelatedServerIpOverview: '/interface/entity/detail/ip/serviceOverview', + entityIpDetailBasic: '/interface/entity/detail/ip/basic', // detection detection: { securityEvent: { diff --git a/src/views/charts/Chart.vue b/src/views/charts/Chart.vue index b09a267b..23f6d557 100644 --- a/src/views/charts/Chart.vue +++ b/src/views/charts/Chart.vue @@ -148,6 +148,7 @@ :chart-data="chartData" :time-filter="timeFilter" :query-params="queryParams" + :entity="entity" > -
+
+
+
+
+
{{ $_.get(entityData.tableData, 'name') || '-' }}
+
+
+
+ +
+
{{$t('overall.location')}}
+
{{ipLocationRegion(entityData)}}
+
+
+
ASN
+
{{entityData.asn || '-'}}
+
+
+
+ +
+
APP ID
+
{{entityData.appId|| '-'}}
+
+
+
{{$t('entities.category')}}
+
{{entityData.category|| '-'}}
+
+
+
{{$t('entities.subcategory')}}
+
{{entityData.subcategory || '-'}}
+
+
+
{{$t('entities.riskLevel')}}
+
{{appRisk(entityData.appRisk) || '-'}}
+
+
+
+ +
+
{{$t('entities.category')}}
+
{{entityData.domainCategory || '-'}}
+
+
+
{{$t('entities.domainDetail.categoryGroup')}}
+
{{entityData.domainCategoryGroup || '-'}}
+
+
+
{{$t('entities.reputationLevel')}}
+
{{entityData.domainReputationScore || '-'}}
+
+
+
{{$t('entities.registration')}}
+
{{entityData.domainWhoisAddress || '-'}}
+
+
+
{{$t('entities.org')}}
+
{{entityData.domainWhoisOrg || '-'}}
+
+
+
+
{{$t('overall.traffic')}}
+
+ +
+
{{$t('overall.received')}}
+
+
{{unitConvert(entityData.bytesReceivedRate, unitTypes.byte).join(' ')}}ps
+
+
+
+
+
+
+
{{$t('overall.sent')}}
+
+
{{unitConvert(entityData.bytesSentRate, unitTypes.byte).join(' ')}}ps
+
+
+
+
+
+
+
+
+
{{$t('relationShip.serviceQuality')}}
+
+ +
+
{{$t('networkAppPerformance.tripTime')}}:
{{unitConvert(entityData.establishLatencyMs, unitTypes.time).join(' ')}} +
+
+
{{$t('overall.packetLoss')}}:
{{unitConvert(entityData.pktRetransPercent, unitTypes.percent).join(' ')}} +
+
+
+
+ +
{{$t('relationShip.alert')}}
+
+ + {{$t('relationShip.noRecentAlerts')}} +
+
+
+
- - diff --git a/src/views/charts/charts/chart-relation-ship.js b/src/views/charts/charts/chart-relation-ship.js new file mode 100644 index 00000000..90ce502c --- /dev/null +++ b/src/views/charts/charts/chart-relation-ship.js @@ -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) + } +}