CN-1087 feat: 实体关系图完善

This commit is contained in:
chenjinsong
2023-07-14 16:50:30 +08:00
parent 19ca35b738
commit f334746a70
11 changed files with 139 additions and 49 deletions

View File

@@ -32,7 +32,7 @@ $font-size: 12px;
.graph-basic-info-name { .graph-basic-info-name {
padding-right: 10px; padding-right: 10px;
width: 260px; max-width: 260px;
font-size: 20px; font-size: 20px;
color: #353636; color: #353636;
font-weight: 700; font-weight: 700;

View File

@@ -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=1688009911333') format('woff2'), src: url('iconfont.woff2?t=1689317280458') format('woff2'),
url('iconfont.woff?t=1688009911333') format('woff'), url('iconfont.woff?t=1689317280458') format('woff'),
url('iconfont.ttf?t=1688009911333') format('truetype'); url('iconfont.ttf?t=1689317280458') format('truetype');
} }
.cn-icon { .cn-icon {
@@ -13,6 +13,38 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.cn-icon-add-knowledge-base:before {
content: "\e802";
}
.cn-icon-update-knowledge-base:before {
content: "\e803";
}
.cn-icon-zoom-out:before {
content: "\e7fd";
}
.cn-icon-to-default:before {
content: "\e7fe";
}
.cn-icon-reset:before {
content: "\e7ff";
}
.cn-icon-next-step:before {
content: "\e800";
}
.cn-icon-pre-step:before {
content: "\e801";
}
.cn-icon-zoom-in:before {
content: "\e7f";
}
.cn-icon-expand-continue:before { .cn-icon-expand-continue:before {
content: "\e7fc"; content: "\e7fc";
} }

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -57,6 +57,7 @@ import { api } from '@/utils/api'
import axios from 'axios' import axios from 'axios'
import G6, { Algorithm } from '@antv/g6' import G6, { Algorithm } from '@antv/g6'
import { entityDetailTags } from '@/utils/constants' import { entityDetailTags } from '@/utils/constants'
import { ElMessage } from 'element-plus'
export default { export default {
name: 'EntityRelationship', name: 'EntityRelationship',
@@ -102,6 +103,7 @@ export default {
async init () { async init () {
const _this = this const _this = this
const tooltip = this.buildTooltip() // tooltip组件 const tooltip = this.buildTooltip() // tooltip组件
// const toolbar = this.buildToolbar() // 工具栏组装件
this.chartOption.plugins = [tooltip] this.chartOption.plugins = [tooltip]
this.graph = new G6.Graph(this.chartOption) this.graph = new G6.Graph(this.chartOption)
const rootNode = await this.generateRootNode() const rootNode = await this.generateRootNode()
@@ -112,7 +114,7 @@ export default {
const rootEdges = this.generateEdges(rootNode) const rootEdges = this.generateEdges(rootNode)
const secondEdges = this.generateEdges(secondLevelNodes) const secondEdges = this.generateEdges(secondLevelNodes)
this.graphData.edges = [...rootEdges, ...secondEdges] this.graphData.edges = [...rootEdges, ...secondEdges]
console.info(this.graphData) // console.info(this.graphData)
this.graph.data(this.graphData) this.graph.data(this.graphData)
this.graph.render() this.graph.render()
this.graph.on('node:dragstart', function (e) { this.graph.on('node:dragstart', function (e) {
@@ -206,7 +208,7 @@ export default {
n.label = _this.$t('entities.subdomain') + `(${n.data.count})` n.label = _this.$t('entities.subdomain') + `(${n.data.count})`
} else { } else {
n.data.count = relatedEntityCount.domainCount n.data.count = relatedEntityCount.domainCount
n.label = _this.$t('entity.graph.resolveDomain') + `(${n.data.count})` n.label = _this.$t('entity.graph.resolvedDomain') + `(${n.data.count})`
} }
break break
} }
@@ -260,7 +262,7 @@ export default {
node.label = _this.$t('entities.subdomain') + `(${node.data.count})` node.label = _this.$t('entities.subdomain') + `(${node.data.count})`
} else { } else {
node.data.count = queryRelatedEntityCount.domainCount node.data.count = queryRelatedEntityCount.domainCount
node.label = _this.$t('entity.graph.resolveDomain') + `(${node.data.count})` node.label = _this.$t('entity.graph.resolvedDomain') + `(${node.data.count})`
} }
break break
} }
@@ -317,7 +319,7 @@ export default {
n.label = _this.$t('entities.subdomain') + `(${n.data.count})` n.label = _this.$t('entities.subdomain') + `(${n.data.count})`
} else { } else {
n.data.count = relatedEntityCount.domainCount n.data.count = relatedEntityCount.domainCount
n.label = _this.$t('entity.graph.resolveDomain') + `(${n.data.count})` n.label = _this.$t('entity.graph.resolvedDomain') + `(${n.data.count})`
} }
break break
} }
@@ -378,7 +380,7 @@ export default {
// TODO 高亮 // TODO 高亮
} else { } else {
// TODO 提示无法拓展 this.$message.error(this.$t('entities.graph.expandedLevelMaxLimit'))
} }
_this.addNodes(entityNodes) _this.addNodes(entityNodes)
_this.addEdges(edges) _this.addEdges(edges)
@@ -610,7 +612,11 @@ export default {
case 'ip': { case 'ip': {
width = 24 width = 24
height = 22 height = 22
label = this.$t('entities.graph.resolveIp') if (node.data.type === 'app') {
label = this.$t('entities.tab.relatedIp')
} else if (node.data.type === 'ip') {
label = this.$t('entities.graph.resolveIp')
}
break break
} }
case 'subdomain': { case 'subdomain': {
@@ -622,7 +628,11 @@ export default {
case 'domain': { case 'domain': {
width = 24 width = 24
height = 24 height = 24
label = this.$t('entity.graph.resolveDomain') if (node.data.type === 'app') {
label = this.$t('entities.relatedDomain')
} else if (node.data.type === 'ip') {
label = this.$t('entities.graph.resolvedDomain')
}
break break
} }
case 'app': { case 'app': {
@@ -698,7 +708,7 @@ export default {
const domainNode = this.generatePrimaryNode({ const domainNode = this.generatePrimaryNode({
id: 'domain-1', id: 'domain-1',
parentId: rootNode.id, parentId: rootNode.id,
label: this.$t('entity.graph.resolveDomain'), label: this.$t('entity.graph.resolvedDomain'),
x: 260, x: 260,
y: -150, y: -150,
data: { data: {
@@ -1130,7 +1140,7 @@ export default {
} }
case 'domain': { case 'domain': {
iconClass = 'cn-icon cn-icon-subdomain' iconClass = 'cn-icon cn-icon-subdomain'
title = _this.$t('entity.graph.resolveDomain') title = _this.$t('entity.graph.resolvedDomain')
break break
} }
case 'app': { case 'app': {
@@ -1142,8 +1152,8 @@ export default {
return `<div class="primary-node-tooltip"> return `<div class="primary-node-tooltip">
<div class="tooltip__header"><i class="${iconClass}"></i><span class="tooltip__title">${title}</span></div> <div class="tooltip__header"><i class="${iconClass}"></i><span class="tooltip__title">${title}</span></div>
<div class="tooltip__content"> <div class="tooltip__content">
<span>${_this.$t('entity.graph.associatedQuantity')}:&nbsp;${node.data.count || 0}</span> <span>${_this.$t('entity.graph.associatedCount')}:&nbsp;${node.data.count || 0}</span>
<span>${_this.$t('entity.graph.expandedEntityQuantity')}:&nbsp;${node.data.loaded ? node.data.loaded.length : 0}</span> <span>${_this.$t('entity.graph.expandedEntityCount')}:&nbsp;${node.data.loaded ? node.data.loaded.length : 0}</span>
</div> </div>
</div>` </div>`
} else if (node.nodeType === 'entity' || node.nodeType === 'root') { } else if (node.nodeType === 'entity' || node.nodeType === 'root') {
@@ -1180,28 +1190,57 @@ export default {
} }
}) })
}, },
buildToolbar () {
const tc = document.createElement('div')
tc.id = 'toolbarContainer'
tc.className = 'graph-toolbar'
document.body.appendChild(tc)
const toolbar = new G6.ToolBar({
container: tc,
className: 'toolbar__tools',
getContent: () => {
return `<ul>
<li code='zoomOut'><i class="cn-icon cn-icon-zoom-in"></i></li>
<li code='zoomIn'><i class="cn-icon cn-icon-zoom-out"></i></li>
<li code='undo'><i class="cn-icon cn-icon-next-step"></i></li>
<li code='redo'><i class="cn-icon cn-icon-pre-step"></i></li>
<li code='autoZoom'><i class="cn-icon cn-icon-auto-zoom"></i></li>
</ul>`
},
handleClick: (code, graph) => {
console.info(code)
toolbar.handleDefaultOperator(code)
}
})
return toolbar
},
async expandList (sourceNodeId, nodeId) { async expandList (sourceNodeId, nodeId) {
this.entity.loading = true
const sourceNode = this.graphData.nodes.find(n => n.id === sourceNodeId) const sourceNode = this.graphData.nodes.find(n => n.id === sourceNodeId)
const node = this.graphData.nodes.find(n => n.id === nodeId) const node = this.graphData.nodes.find(n => n.id === nodeId)
if (node && sourceNode) { if (node && sourceNode) {
console.info(node)
if (node.data.childNodes && node.data.childNodes.length < 50) { if (node.data.childNodes && node.data.childNodes.length < 50) {
const queryEntityNodes = await this.generateHalfLevelNodes(node) if (node.data.childNodes.length >= node.data.count) {
const relatedEntityCount = await this.queryRelatedEntityCount(sourceNode.data.type, sourceNodeId) this.$message.error(this.$t('entities.graph.allEntitiesExpanded'))
// 赋值总数和已拓展数。entity node可从其childNodes取到已拓展数 } else {
this.entity = { this.entity.loading = true
...this.entity, const queryEntityNodes = await this.generateHalfLevelNodes(node)
relatedEntityCount: this.handleRelatedEntityCount(sourceNode, relatedEntityCount), const relatedEntityCount = await this.queryRelatedEntityCount(sourceNode.data.type, sourceNodeId)
listData: node.data.loaded, // 赋值总数和已拓展数。entity node可从其childNodes取到已拓展数
loading: false this.entity = {
} ...this.entity,
sourceNode.data.relatedEntityCount = this.entity.relatedEntityCount relatedEntityCount: this.handleRelatedEntityCount(sourceNode, relatedEntityCount),
listData: node.data.loaded,
loading: false
}
sourceNode.data.relatedEntityCount = this.entity.relatedEntityCount
this.addNodes(queryEntityNodes) this.addNodes(queryEntityNodes)
this.addEdges(this.generateEdges(node, queryEntityNodes)) this.addEdges(this.generateEdges(node, queryEntityNodes))
this.graph.changeData(this.graphData) this.graph.changeData(this.graphData)
}
} else { } else {
// TODO 提示已达50上限 this.$message.error(this.$t('entities.graph.expandedNumberMaxLimit'))
} }
} }
}, },
@@ -1255,7 +1294,7 @@ export default {
edges.push(...this.generateEdges(primaryNode)) edges.push(...this.generateEdges(primaryNode))
// TODO 高亮 // TODO 高亮
} else { } else {
// TODO 提示无法拓展 this.$message.error(this.$t('entities.graph.expandedLevelMaxLimit'))
} }
this.addNodes(entityNodes) this.addNodes(entityNodes)
this.addEdges(edges) this.addEdges(edges)
@@ -1294,6 +1333,29 @@ export default {
} }
</script> </script>
<style lang="scss"> <style lang="scss">
.graph-toolbar {
position: fixed;
top: 100px;
left: 0;
.toolbar__tools {
display: flex;
padding: 0 10px;
li {
display: flex;
justify-content: center;
width: 32px;
margin-right: 4px;
cursor: pointer;
i {
color: #575757;
font-size: 18px;
}
}
}
}
.entity-graph { .entity-graph {
display: flex; display: flex;

View File

@@ -8,7 +8,7 @@
<span>{{ $t('entities.tab.relatedApp') }}</span> <span>{{ $t('entities.tab.relatedApp') }}</span>
</div> </div>
<div class="graph-list-header-number"> <div class="graph-list-header-number">
{{ $t('entity.graph.associatedQuantity') }}:&nbsp;<span>{{entity.count}}</span> {{ $t('entity.graph.associatedCount') }}:&nbsp;<span>{{entity.count}}</span>
</div> </div>
</div> </div>
@@ -25,7 +25,7 @@
<div class="digital-certificate-header padding-b-12"> <div class="digital-certificate-header padding-b-12">
<div class="digital-certificate-header__icon graph-header__icon"></div> <div class="digital-certificate-header__icon graph-header__icon"></div>
<div class="graph-list-content-header "> <div class="graph-list-content-header ">
{{ $t('entity.graph.expandedEntityQuantity') }}:&nbsp;&nbsp; {{ $t('entity.graph.expandedEntityCount') }}:&nbsp;&nbsp;
<span>{{ entity.listData ? entity.listData.length : 0 }}</span> <span>{{ entity.listData ? entity.listData.length : 0 }}</span>
</div> </div>
</div> </div>
@@ -107,9 +107,7 @@ export default {
}, },
expandList () { expandList () {
// 继续拓展ip列表传递信息调用接口 // 继续拓展ip列表传递信息调用接口
if (!this.expandBtnDisable) { this.$emit('expandList', this.entity.sourceName, this.entity.nodeId)
this.$emit('expandList', this.entity.sourceName, this.entity.nodeId)
}
} }
}, },
mounted () { mounted () {

View File

@@ -5,10 +5,10 @@
<div> <div>
<div class="graph-list-header-title"> <div class="graph-list-header-title">
<i class="graph-list-header-icon cn-icon cn-icon-subdomain"></i> <i class="graph-list-header-icon cn-icon cn-icon-subdomain"></i>
<span>{{ entity.isSubdomain ? $t('entities.subdomain') : $t('entity.graph.resolveDomain') }}</span> <span>{{ entity.isSubdomain ? $t('entities.subdomain') : (entity.sourceType === 'ip' ? $t('entity.graph.resolveDomain') : $t('entities.relatedDomain')) }}</span>
</div> </div>
<div class="graph-list-header-number"> <div class="graph-list-header-number">
{{ $t('entity.graph.associatedQuantity') }}:&nbsp;<span>{{entity.count}}</span> {{ $t('entity.graph.associatedCount') }}:&nbsp;<span>{{entity.count}}</span>
</div> </div>
</div> </div>
@@ -25,7 +25,7 @@
<div class="digital-certificate-header padding-b-12"> <div class="digital-certificate-header padding-b-12">
<div class="digital-certificate-header__icon graph-header__icon"></div> <div class="digital-certificate-header__icon graph-header__icon"></div>
<div class="graph-list-content-header "> <div class="graph-list-content-header ">
{{ $t('entity.graph.expandedEntityQuantity') }}:&nbsp;&nbsp; {{ $t('entity.graph.expandedEntityCount') }}:&nbsp;&nbsp;
<span>{{ entity.listData ? entity.listData.length : 0 }}</span> <span>{{ entity.listData ? entity.listData.length : 0 }}</span>
</div> </div>
</div> </div>
@@ -103,9 +103,7 @@ export default {
}, },
expandList () { expandList () {
// 继续拓展ip列表传递信息调用接口 // 继续拓展ip列表传递信息调用接口
if (!this.expandBtnDisable) { this.$emit('expandList', this.entity.sourceName, this.entity.nodeId)
this.$emit('expandList', this.entity.sourceName, this.entity.nodeId)
}
} }
}, },
mounted () { mounted () {

View File

@@ -153,7 +153,7 @@
</div> </div>
<!--标签--> <!--标签-->
<div v-if="entity.tags" class="digital-certificate graph-basic-info__block"> <div v-if="entity.tags && entity.tags.length > 1" class="digital-certificate graph-basic-info__block">
<div class="digital-certificate-header padding-b-10"> <div class="digital-certificate-header padding-b-10">
<div class="digital-certificate-header__icon graph-header__icon"></div> <div class="digital-certificate-header__icon graph-header__icon"></div>
<div class="graph-basic-info__block-title"> <div class="graph-basic-info__block-title">
@@ -473,14 +473,14 @@ export default {
{ {
icon: 'cn-icon cn-icon-resolve-ip', icon: 'cn-icon cn-icon-resolve-ip',
name: 'ip', name: 'ip',
label: this.$t('entities.graph.resolveIp'), label: this.$t('entities.tab.relatedIp'),
value: _.get(n.relatedEntityCount, 'ip.current', '0') || 0, value: _.get(n.relatedEntityCount, 'ip.current', '0') || 0,
total: _.get(n.relatedEntityCount, 'ip.total', '0') || 0 total: _.get(n.relatedEntityCount, 'ip.total', '0') || 0
}, },
{ {
icon: 'cn-icon cn-icon-subdomain', icon: 'cn-icon cn-icon-subdomain',
name: 'domain', name: 'domain',
label: this.$t('entity.graph.resolveDomain'), label: this.$t('entities.relatedDomain'),
value: _.get(n.relatedEntityCount, 'domain.current', '0') || 0, value: _.get(n.relatedEntityCount, 'domain.current', '0') || 0,
total: _.get(n.relatedEntityCount, 'domain.total', '0') || 0 total: _.get(n.relatedEntityCount, 'domain.total', '0') || 0
} }

View File

@@ -5,10 +5,10 @@
<div> <div>
<div class="graph-list-header-title"> <div class="graph-list-header-title">
<i class="cn-icon cn-icon-resolve-ip graph-list-header-icon"></i> <i class="cn-icon cn-icon-resolve-ip graph-list-header-icon"></i>
<span>{{ $t('entities.graph.resolveIp') }}</span> <span>{{ entity.sourceType === 'domain' ? $t('entities.graph.resolveIp') : $t('entities.tab.relatedIp') }}</span>
</div> </div>
<div class="graph-list-header-number"> <div class="graph-list-header-number">
{{ $t('entity.graph.associatedQuantity') }}:&nbsp;<span>{{entity.count}}</span> {{ $t('entity.graph.associatedCount') }}:&nbsp;<span>{{entity.count}}</span>
</div> </div>
</div> </div>
@@ -27,7 +27,7 @@
<div class="digital-certificate-header padding-b-16"> <div class="digital-certificate-header padding-b-16">
<div class="digital-certificate-header__icon graph-header__icon"></div> <div class="digital-certificate-header__icon graph-header__icon"></div>
<div class="graph-list-content-header "> <div class="graph-list-content-header ">
{{ $t('entity.graph.expandedEntityQuantity') }}:&nbsp;&nbsp; {{ $t('entity.graph.expandedEntityCount') }}:&nbsp;&nbsp;
<span>{{ entity.listData ? entity.listData.length : 0 }}</span> <span>{{ entity.listData ? entity.listData.length : 0 }}</span>
</div> </div>
</div> </div>