CN-1384 IP实体详情页behavior pattern tab功能开发
This commit is contained in:
@@ -83,6 +83,59 @@
|
|||||||
|
|
||||||
.entity-detail-event-block {
|
.entity-detail-event-block {
|
||||||
width: calc(100% - 2px);
|
width: calc(100% - 2px);
|
||||||
|
.behavior-pattern {
|
||||||
|
height:100% ;
|
||||||
|
position: relative;
|
||||||
|
display:flex;
|
||||||
|
flex-direction: row;
|
||||||
|
.behavior-pattern-legend {
|
||||||
|
display:flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
padding:10px 18px 10px 18px;
|
||||||
|
width:500px;
|
||||||
|
display: flex;
|
||||||
|
.behavior-pattern-legend__item {
|
||||||
|
display:flex;
|
||||||
|
flex-direction: row;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #353636;
|
||||||
|
line-height: 12px;
|
||||||
|
margin-bottom:11px;
|
||||||
|
.legend-icon {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
margin: 3px 8px 0 0;
|
||||||
|
border-radius: 1px;;
|
||||||
|
}
|
||||||
|
.legend-name {
|
||||||
|
width:200px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
.legend-value{
|
||||||
|
display: flex;
|
||||||
|
justify-content: left;
|
||||||
|
margin-left:30px;
|
||||||
|
width:100px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.legend-percent {
|
||||||
|
margin-left:30px;
|
||||||
|
width:80px;
|
||||||
|
justify-content: left;
|
||||||
|
display: flex;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.behavior-pattern-chart{
|
||||||
|
height: calc(100% - 50px);
|
||||||
|
width:calc(100% - 600px);
|
||||||
|
position: relative
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.entity-detail-event-error {
|
.entity-detail-event-error {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "cn-icon"; /* Project id 2614877 */
|
font-family: "cn-icon"; /* Project id 2614877 */
|
||||||
src: url('iconfont.woff2?t=1693386443164') format('woff2'),
|
src: url('iconfont.woff2?t=1697794140569') format('woff2'),
|
||||||
url('iconfont.woff?t=1693386443164') format('woff'),
|
url('iconfont.woff?t=1697794140569') format('woff'),
|
||||||
url('iconfont.ttf?t=1693386443164') format('truetype');
|
url('iconfont.ttf?t=1697794140569') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.cn-icon {
|
.cn-icon {
|
||||||
@@ -13,6 +13,10 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cn-icon-behavior:before {
|
||||||
|
content: "\e61c";
|
||||||
|
}
|
||||||
|
|
||||||
.cn-icon-category-group:before {
|
.cn-icon-category-group:before {
|
||||||
content: "\e6c7";
|
content: "\e6c7";
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -261,6 +261,7 @@ export const api = {
|
|||||||
throughput: apiVersion + '/entity/detail/traffic/throughput',
|
throughput: apiVersion + '/entity/detail/traffic/throughput',
|
||||||
security: apiVersion + '/entity/detail/event/security',
|
security: apiVersion + '/entity/detail/event/security',
|
||||||
performance: apiVersion + '/entity/detail/event/performance',
|
performance: apiVersion + '/entity/detail/event/performance',
|
||||||
|
behaviorPattern: apiVersion + '/entity/detail/behavior/ip',
|
||||||
// 域名解析:ip相关app、domain
|
// 域名解析:ip相关app、domain
|
||||||
domainNameResolutionAboutAppsOfIp: apiVersion + '/entity/detail/ip/relate/apps',
|
domainNameResolutionAboutAppsOfIp: apiVersion + '/entity/detail/ip/relate/apps',
|
||||||
domainNameResolutionAboutDomainsOfIp: apiVersion + '/entity/detail/ip/relate/domains',
|
domainNameResolutionAboutDomainsOfIp: apiVersion + '/entity/detail/ip/relate/domains',
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -20,6 +20,7 @@
|
|||||||
<security-event v-else-if="tab.name === entityDetailTabsName.securityEvent && tab.name === activeTab" @toggleLoading="setLoading" :timeFilter="oneDayTimeFilter" @checkTag="setTag" />
|
<security-event v-else-if="tab.name === entityDetailTabsName.securityEvent && tab.name === activeTab" @toggleLoading="setLoading" :timeFilter="oneDayTimeFilter" @checkTag="setTag" />
|
||||||
<performance-event v-else-if="tab.name === entityDetailTabsName.performanceEvent && tab.name === activeTab" @toggleLoading="setLoading" :timeFilter="oneDayTimeFilter" @checkTag="setTag" />
|
<performance-event v-else-if="tab.name === entityDetailTabsName.performanceEvent && tab.name === activeTab" @toggleLoading="setLoading" :timeFilter="oneDayTimeFilter" @checkTag="setTag" />
|
||||||
<open-port v-else-if="tab.name === entityDetailTabsName.openPort && tab.name === activeTab" @toggleLoading="setLoading" :entity="entity" :timeFilter="oneDayTimeFilter" @checkTag="setTag"></open-port>
|
<open-port v-else-if="tab.name === entityDetailTabsName.openPort && tab.name === activeTab" @toggleLoading="setLoading" :entity="entity" :timeFilter="oneDayTimeFilter" @checkTag="setTag"></open-port>
|
||||||
|
<behavior-pattern v-else-if="tab.name === entityDetailTabsName.behaviorPattern && tab.name === activeTab" @toggleLoading="setLoading" :entity="entity" :timeFilter="oneDayTimeFilter" @checkTag="setTag"></behavior-pattern>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
@@ -34,6 +35,7 @@ import InformationAggregation from '@/views/charts2/charts/entityDetail/tabs/Inf
|
|||||||
import DomainNameResolution from '@/views/charts2/charts/entityDetail/tabs/DomainNameResolution'
|
import DomainNameResolution from '@/views/charts2/charts/entityDetail/tabs/DomainNameResolution'
|
||||||
import SecurityEvent from '@/views/charts2/charts/entityDetail/tabs/SecurityEvent'
|
import SecurityEvent from '@/views/charts2/charts/entityDetail/tabs/SecurityEvent'
|
||||||
import PerformanceEvent from '@/views/charts2/charts/entityDetail/tabs/PerformanceEvent'
|
import PerformanceEvent from '@/views/charts2/charts/entityDetail/tabs/PerformanceEvent'
|
||||||
|
import BehaviorPattern from '@/views/charts2/charts/entityDetail/tabs/BehaviorPattern'
|
||||||
import OpenPort from '@/views/charts2/charts/entityDetail/tabs/OpenPort'
|
import OpenPort from '@/views/charts2/charts/entityDetail/tabs/OpenPort'
|
||||||
import DigitalCertificate from '@/views/charts2/charts/entityDetail/tabs/DigitalCertificate'
|
import DigitalCertificate from '@/views/charts2/charts/entityDetail/tabs/DigitalCertificate'
|
||||||
import { overwriteUrl, urlParamsHandler } from '@/utils/tools'
|
import { overwriteUrl, urlParamsHandler } from '@/utils/tools'
|
||||||
@@ -47,6 +49,7 @@ export default {
|
|||||||
mixins: [chartMixin],
|
mixins: [chartMixin],
|
||||||
components: {
|
components: {
|
||||||
PerformanceEvent,
|
PerformanceEvent,
|
||||||
|
BehaviorPattern,
|
||||||
SecurityEvent,
|
SecurityEvent,
|
||||||
InformationAggregation,
|
InformationAggregation,
|
||||||
DomainNameResolution,
|
DomainNameResolution,
|
||||||
@@ -91,6 +94,9 @@ export default {
|
|||||||
if (entityType !== 'app') {
|
if (entityType !== 'app') {
|
||||||
tabs.unshift({ name: entityDetailTabsName.informationAggregation, label: i18n.global.t('entities.informationAggregation'), icon: 'cn-icon cn-icon-information-aggregation', tag: 0 })
|
tabs.unshift({ name: entityDetailTabsName.informationAggregation, label: i18n.global.t('entities.informationAggregation'), icon: 'cn-icon cn-icon-information-aggregation', tag: 0 })
|
||||||
}
|
}
|
||||||
|
if (entityType === 'ip') {
|
||||||
|
tabs.push({ name: entityDetailTabsName.behaviorPattern, label: i18n.global.t('entities.behaviorPattern'), icon: 'cn-icon cn-icon-behavior', tag: 0 })
|
||||||
|
}
|
||||||
const activeTab = ref(tabs[0].name)
|
const activeTab = ref(tabs[0].name)
|
||||||
|
|
||||||
const { query } = useRoute()
|
const { query } = useRoute()
|
||||||
|
|||||||
165
src/views/charts2/charts/entityDetail/tabs/BehaviorPattern.vue
Normal file
165
src/views/charts2/charts/entityDetail/tabs/BehaviorPattern.vue
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
<template>
|
||||||
|
<chart-error v-if="showError" :content="errorMsg" class="entity-detail-event-error"></chart-error>
|
||||||
|
<chart-no-data v-if="isNoData && !showError"></chart-no-data>
|
||||||
|
|
||||||
|
<div v-if="!isNoData && !showError" class="entity-detail-event-block" style="height: 100%;width: 100%;position: relative;">
|
||||||
|
<div class="behavior-pattern" >
|
||||||
|
<div class="behavior-pattern-legend" >
|
||||||
|
<div class="behavior-pattern-legend__item" v-for="(data, index) in tableData" :key="index">
|
||||||
|
<div class="legend-icon" :style="`background:${chartColorForBehaviorPattern[index%10]};`"></div>
|
||||||
|
<div class="legend-name">{{data.name}}</div>
|
||||||
|
<div class="legend-value" >{{ unitConvert(data.value, unitTypes.number).join('')}}</div>
|
||||||
|
<div class="legend-percent">{{ unitConvert(data.percent, unitTypes.percent).join('') }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="entityIpRoseType" class="behavior-pattern-chart"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { dateFormatByAppearance } from '@/utils/date-util'
|
||||||
|
import * as echarts from 'echarts'
|
||||||
|
import { pieChartOption4 } from '@/views/charts2/charts/options/echartOption'
|
||||||
|
import { shallowRef } from 'vue'
|
||||||
|
import { entityDetailTabsName, chartColorForBehaviorPattern, unitTypes } from '@/utils/constants'
|
||||||
|
import unitConvert from '@/utils/unit-convert'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { api } from '@/utils/api'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import chartMixin from '@/views/charts2/chart-mixin'
|
||||||
|
import ChartError from '@/components/common/Error'
|
||||||
|
import { toUpperCaseByString, reverseSortBy } from '@/utils/tools'
|
||||||
|
import ChartNoData from '@/views/charts/charts/ChartNoData'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'BehaviorPattern',
|
||||||
|
components: { ChartError, ChartNoData },
|
||||||
|
mixins: [chartMixin],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
showError: false,
|
||||||
|
errorMsg: '',
|
||||||
|
tableData: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup () {
|
||||||
|
const { query } = useRoute()
|
||||||
|
const entityType = query.entityType
|
||||||
|
const entityName = query.entityName
|
||||||
|
|
||||||
|
return {
|
||||||
|
entityType,
|
||||||
|
entityName,
|
||||||
|
myChart: shallowRef(null),
|
||||||
|
chartColorForBehaviorPattern,
|
||||||
|
unitTypes
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted () {
|
||||||
|
this.$emit('checkTag', entityDetailTabsName.behaviorPattern, 0)
|
||||||
|
await this.initData()
|
||||||
|
this.toggleLoading(true)
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
this.toggleLoading(false)
|
||||||
|
clearInterval(timer)
|
||||||
|
}, 200)
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.initEcharts()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
unitConvert,
|
||||||
|
toUpperCaseByString,
|
||||||
|
dateFormatByAppearance,
|
||||||
|
initEcharts () {
|
||||||
|
this.chartOption = pieChartOption4
|
||||||
|
this.chartOption.angleAxis.data = []
|
||||||
|
this.chartOption.series[0].data = []
|
||||||
|
this.tableData.forEach((item, index) => {
|
||||||
|
this.chartOption.angleAxis.data.push(item.name)
|
||||||
|
this.chartOption.series[0].data.push(item.value)
|
||||||
|
})
|
||||||
|
const len = this.tableData.length - 1
|
||||||
|
this.tableData.forEach((item, i) => {
|
||||||
|
if (i !== len) {
|
||||||
|
this.chartOption.angleAxis.data.push(item.name + '2')
|
||||||
|
this.chartOption.series[0].data.push(0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const self = this
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (self.myChart) {
|
||||||
|
self.myChart.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
const dom = document.getElementById('entityIpRoseType')
|
||||||
|
if (dom) {
|
||||||
|
self.myChart = echarts.init(dom)
|
||||||
|
self.myChart.setOption(this.chartOption)
|
||||||
|
|
||||||
|
self.myChart.dispatchAction({
|
||||||
|
type: 'takeGlobalCursor',
|
||||||
|
key: 'brush',
|
||||||
|
brushOption: {
|
||||||
|
brushType: 'lineX',
|
||||||
|
xAxisIndex: 'all',
|
||||||
|
brushMode: 'single',
|
||||||
|
throttleType: 'debounce'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async initData () {
|
||||||
|
const params = {
|
||||||
|
resource: this.entityName
|
||||||
|
}
|
||||||
|
this.toggleLoading(true)
|
||||||
|
await axios.get(`${api.entity.behaviorPattern}`, { params: params }).then(response => {
|
||||||
|
const res = response.data
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
this.isNoData = res.data.result.length === 0
|
||||||
|
this.$emit('checkTag', entityDetailTabsName.behaviorPattern, res.data.result.length)
|
||||||
|
this.showError = false
|
||||||
|
if (!this.isNoData) {
|
||||||
|
const data = res.data.result
|
||||||
|
this.tableData = []
|
||||||
|
let sum = 0
|
||||||
|
if (data && data[0]) {
|
||||||
|
const dataObject = data[0]
|
||||||
|
Object.keys(dataObject).forEach(key => {
|
||||||
|
const value = Number(dataObject[key]) ? Number(dataObject[key]) : 0
|
||||||
|
sum = sum + value
|
||||||
|
this.tableData.push({
|
||||||
|
name: key,
|
||||||
|
value: value
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.tableData.forEach(item => {
|
||||||
|
item.percent = item.value / sum
|
||||||
|
})
|
||||||
|
this.tableData = this.tableData.sort(reverseSortBy('value'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.httpError(res)
|
||||||
|
}
|
||||||
|
}).catch(e => {
|
||||||
|
console.error(e)
|
||||||
|
this.httpError(e)
|
||||||
|
}).finally(() => {
|
||||||
|
this.toggleLoading(false)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
httpError (e) {
|
||||||
|
this.isNoData = false
|
||||||
|
this.showError = true
|
||||||
|
this.errorMsg = this.errorMsgHandler(e)
|
||||||
|
this.$emit('checkTag', entityDetailTabsName.behaviorPattern, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
chartColor3,
|
chartColor3,
|
||||||
chartColor5,
|
chartColor5,
|
||||||
chartColor6,
|
chartColor6,
|
||||||
|
chartColorForBehaviorPattern,
|
||||||
unitTypes
|
unitTypes
|
||||||
} from '@/utils/constants'
|
} from '@/utils/constants'
|
||||||
import unitConvert, { valueToRangeValue } from '@/utils/unit-convert'
|
import unitConvert, { valueToRangeValue } from '@/utils/unit-convert'
|
||||||
@@ -156,6 +157,111 @@ export const pieChartOption3 = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const pieChartOption4 = {
|
||||||
|
color: chartColorForBehaviorPattern,
|
||||||
|
polar: {
|
||||||
|
radius: [30, '150%'],
|
||||||
|
center: ['50%', '99.9%']// 为了显示出来半圆底部左侧的边
|
||||||
|
},
|
||||||
|
radiusAxis: {
|
||||||
|
min: 0,
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#d3d3d3',
|
||||||
|
type: 'dashed'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#d3d3d3',
|
||||||
|
type: 'dashed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
angleAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: [], // 'a', 'b', 'c', 'd','aa', 'ab', 'ac', 'ad','a', 'b', 'c', 'd','aa', 'ab', 'ac', 'ad'
|
||||||
|
startAngle: 180,
|
||||||
|
// splitNumber: 30,
|
||||||
|
axisLine: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
show: true,
|
||||||
|
// alignWithLabel:true,//可以保证刻度线和标签对齐
|
||||||
|
interval: 0, // 强制显示所有标签
|
||||||
|
// hideOverlap:true//从 v5.2.0 开始支持
|
||||||
|
formatter: function (params, index) {
|
||||||
|
if (index === 7) {
|
||||||
|
return params + '\n'
|
||||||
|
} else if (index === 8) {
|
||||||
|
return '\n' + params
|
||||||
|
} else if (index === 15) {
|
||||||
|
return params + '\n'
|
||||||
|
} else {
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
alignWithLabel: true,
|
||||||
|
interval: 0,
|
||||||
|
length: 5
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: ['#e2e5ec'],
|
||||||
|
type: 'dashed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
formatter: function (item) {
|
||||||
|
let str = '<div style="display:flex;flex-direction: row;align-items: center;">'
|
||||||
|
str += '<div style="width: 8px;\n' +
|
||||||
|
' height: 8px;\n' +
|
||||||
|
' margin: 3px 8px 0 0;\n' +
|
||||||
|
' border-radius: 1px;;background:'
|
||||||
|
str += item.color
|
||||||
|
str += ';"></div>'
|
||||||
|
str += item.name + ': ' + unitConvert(item.value, unitTypes.number).join('')
|
||||||
|
str += '</div>'
|
||||||
|
str += '</div>'
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [{
|
||||||
|
type: 'bar',
|
||||||
|
data: [], // 8,7,6,5,4,3,2,1,0,0,0,0,0,0,0,0
|
||||||
|
coordinateSystem: 'polar',
|
||||||
|
axisTick: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: function (params) {
|
||||||
|
return chartColorForBehaviorPattern[params.dataIndex]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: false,
|
||||||
|
position: 'middle',
|
||||||
|
formatter: '{b}: {c}'
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
animation: false
|
||||||
|
}
|
||||||
|
|
||||||
export const stackedLineChartOption = {
|
export const stackedLineChartOption = {
|
||||||
color: chartColor3,
|
color: chartColor3,
|
||||||
tooltip: {
|
tooltip: {
|
||||||
|
|||||||
Reference in New Issue
Block a user