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

This commit is contained in:
chenjinsong
2023-07-12 17:23:42 +08:00
parent 97ae64943b
commit 7b4070f06a
9 changed files with 424 additions and 237 deletions

View File

@@ -24,11 +24,12 @@
display: flex; display: flex;
height: 134px; height: 134px;
align-items: center; align-items: center;
justify-content: space-around; justify-content: space-evenly;
.analysis-entry-item { .analysis-entry-item {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-width: 70px;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
@@ -69,7 +70,7 @@
.dividing-line { .dividing-line {
height: 1px; height: 1px;
width: calc(100% - 60px); width: 100%;
margin-top: 21px; margin-top: 21px;
background-color: #EFF2F5; background-color: #EFF2F5;
} }

View File

@@ -11,7 +11,6 @@ $font-size: 12px;
flex-direction: column; flex-direction: column;
.entity-graph-type { .entity-graph-type {
font-family: NotoSansSChineseRegular;
font-size: 12px; font-size: 12px;
color: #717171; color: #717171;
font-weight: 400; font-weight: 400;
@@ -33,10 +32,13 @@ $font-size: 12px;
.graph-basic-info-name { .graph-basic-info-name {
padding-right: 10px; padding-right: 10px;
font-family: Helvetica-Bold; width: 260px;
font-size: 20px; font-size: 20px;
color: #353636; color: #353636;
font-weight: 700; font-weight: 700;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
.graph-basic-info-icon { .graph-basic-info-icon {
@@ -197,7 +199,6 @@ $font-size: 12px;
} }
.graph-basic-info__block-title { .graph-basic-info__block-title {
font-family: NotoSansHans-Medium;
font-size: 13px; font-size: 13px;
color: #353636; color: #353636;
font-weight: 600; font-weight: 600;

View File

@@ -741,34 +741,38 @@ export function truncateText (text, limitWidth, fontSize = 12, ellipsis = '...')
} }
export function scrollToTop (dom, toTop, duration, direction) { export function scrollToTop (dom, toTop, duration, direction) {
const clientHeight = dom.clientHeight if (toTop && duration && direction) {
const currentTop = dom.scrollTop const clientHeight = dom.clientHeight
const totalScrollDistance = Math.abs(currentTop - toTop) const currentTop = dom.scrollTop
let scrollY = currentTop const totalScrollDistance = Math.abs(currentTop - toTop)
let oldTimestamp = null let scrollY = currentTop
let oldTimestamp = null
function step (newTimestamp) { function step (newTimestamp) {
if (oldTimestamp !== null) { if (oldTimestamp !== null) {
if (direction === 'up') { if (direction === 'up') {
scrollY -= totalScrollDistance * (newTimestamp - oldTimestamp) / duration scrollY -= totalScrollDistance * (newTimestamp - oldTimestamp) / duration
if (scrollY < 0) { if (scrollY < 0) {
dom.scrollTop = 0 dom.scrollTop = 0
return return
}
dom.scrollTop = scrollY
} else if (direction === 'down') {
scrollY += totalScrollDistance * (newTimestamp - oldTimestamp) / duration
if (scrollY > clientHeight) {
dom.scrollTop = clientHeight
return
}
dom.scrollTop = scrollY
} }
dom.scrollTop = scrollY
} else if (direction === 'down') {
scrollY += totalScrollDistance * (newTimestamp - oldTimestamp) / duration
if (scrollY > clientHeight) {
dom.scrollTop = clientHeight
return
}
dom.scrollTop = scrollY
} }
oldTimestamp = newTimestamp
window.requestAnimationFrame(step)
} }
oldTimestamp = newTimestamp
window.requestAnimationFrame(step) window.requestAnimationFrame(step)
} else {
const wraps = document.querySelector('.entity-graph__detail')
wraps.scrollTop = 0
} }
window.requestAnimationFrame(step)
} }
export function getChainRatio (current, prev) { export function getChainRatio (current, prev) {

View File

@@ -9,7 +9,7 @@
</div> </div>
<el-popover <el-popover
placement="bottom-end" placement="bottom-end"
:width="390" :width="450"
trigger="click" trigger="click"
popper-class="analysis-popper" popper-class="analysis-popper"
@show="analysisPopSwitch(true)" @show="analysisPopSwitch(true)"
@@ -254,6 +254,17 @@ export default {
label: i18n.global.t('dns.dnsInsights'), label: i18n.global.t('dns.dnsInsights'),
path: '/panel/dnsServiceInsights' path: '/panel/dnsServiceInsights'
} }
const graphItem = {
icon: 'cn-icon cn-icon-graph',
label: i18n.global.t('entities.graph'),
url: resolvePath({
path: '/entityGraph',
query: {
entityType: props.entity.entityType,
entityName: props.entity.entityName
}
})
}
switch (props.entity.entityType) { switch (props.entity.entityType) {
case 'ip': { case 'ip': {
const queryCondition = `common_client_ip='${props.entity.entityName}' OR common_server_ip='${props.entity.entityName}'` const queryCondition = `common_client_ip='${props.entity.entityName}' OR common_server_ip='${props.entity.entityName}'`
@@ -379,6 +390,7 @@ export default {
break break
} }
} }
analysisItems.value.push(graphItem)
// 底部各属性 // 底部各属性
const detailCards = ref([]) const detailCards = ref([])
switch (props.entity.entityType) { switch (props.entity.entityType) {

View File

@@ -55,7 +55,7 @@ import { ref, shallowRef } from 'vue'
import _ from 'lodash' import _ from 'lodash'
import { api } from '@/utils/api' import { api } from '@/utils/api'
import axios from 'axios' import axios from 'axios'
import G6 from '@antv/g6' import G6, { Algorithm } from '@antv/g6'
import { entityDetailTags } from '@/utils/constants' import { entityDetailTags } from '@/utils/constants'
export default { export default {
@@ -148,20 +148,31 @@ export default {
* */ * */
let change = false // 图数据是否有变更,需要刷新 let change = false // 图数据是否有变更,需要刷新
// 清除temp node和temp edge // 清除temp node和temp edge
const toDeleteTempNodeIds = []
const toDeleteTempEdgeIds = []
_this.graphData.nodes = _this.graphData.nodes.filter(n => { _this.graphData.nodes = _this.graphData.nodes.filter(n => {
if (n.data && !n.data.isTemp) { if (n.data && !n.data.isTemp) {
return true return true
} }
change = true toDeleteTempNodeIds.push(n.id)
// change = true
return false return false
}) })
_this.graphData.edges = _this.graphData.edges.filter(e => { _this.graphData.edges = _this.graphData.edges.filter(e => {
if (e.data && !e.data.isTemp) { if (e.data && !e.data.isTemp) {
return true return true
} }
change = true toDeleteTempEdgeIds.push(e.id)
// change = true
return false return false
}) })
toDeleteTempNodeIds.forEach(n => {
_this.graph.removeItem(n)
})
toDeleteTempEdgeIds.forEach(n => {
_this.graph.removeItem(n)
})
if (node.nodeType === 'root') { // 点击了root if (node.nodeType === 'root') { // 点击了root
_this.entity = { _this.entity = {
entityName: node.id, entityName: node.id,
@@ -170,10 +181,11 @@ export default {
tags: node.data.tags, tags: node.data.tags,
type: node.data.type, type: node.data.type,
detailData: node.data.data, detailData: node.data.data,
relatedEntityCount: node.data.relatedEntityCount,
isSubdomain: node.data.isSubdomain || false, isSubdomain: node.data.isSubdomain || false,
loading: true loading: false
} }
const relatedEntityCount = await _this.queryRelatedEntityCount(_this.entity.entityType, node.id) /* const relatedEntityCount = await _this.queryRelatedEntityCount(_this.entity.entityType, node.id)
_this.entity = { _this.entity = {
..._this.entity, ..._this.entity,
relatedEntityCount: _this.handleRelatedEntityCount(node, relatedEntityCount), relatedEntityCount: _this.handleRelatedEntityCount(node, relatedEntityCount),
@@ -206,9 +218,21 @@ export default {
} }
_this.graph.refreshItem(n.id) _this.graph.refreshItem(n.id)
}) })
} } */
} else if (node.nodeType === 'primary') { // 点击了primary } else if (node.nodeType === 'primary') { // 点击了primary
_this.entity.loading = true _this.entity = {
entityType: _this.entity.entityType,
entityName: _this.entity.entityName,
listData: node.data.loaded,
count: node.data.count,
isSubdomain: node.data.isSubdomain || false,
sourceType: node.data.sourceType,
sourceName: node.data.sourceName,
type: node.data.type,
nodeId: node.id,
loading: false
}
/* _this.entity.loading = true
const queryRelatedEntityCount = await _this.queryRelatedEntityCount(node.data.sourceType, node.data.sourceName) const queryRelatedEntityCount = await _this.queryRelatedEntityCount(node.data.sourceType, node.data.sourceName)
const sourceNode = _this.graphData.nodes.find(n => n.id === node.data.sourceName) const sourceNode = _this.graphData.nodes.find(n => n.id === node.data.sourceName)
_this.handleRelatedEntityCount(sourceNode, queryRelatedEntityCount) _this.handleRelatedEntityCount(sourceNode, queryRelatedEntityCount)
@@ -246,10 +270,26 @@ export default {
break break
} }
} }
_this.graph.refreshItem(node.id) _this.graph.refreshItem(node.id) */
// TODO 高亮 // TODO 高亮
} else if (node.nodeType === 'entity') { } else if (node.nodeType === 'entity') {
_this.entity.loading = true // 点击节点时看是否已查询过related count已查过则不再查
if (!node.data.childNodes) {
_this.entity.loading = true
const relatedEntityCount = await _this.queryRelatedEntityCount(node.data.type, node.id)
node.data.relatedEntityCount = _this.handleRelatedEntityCount(node, relatedEntityCount)
}
_this.entity = {
entityType: _this.entity.entityType,
entityName: _this.entity.entityName,
relatedEntityCount: node.data.relatedEntityCount,
detailData: node.data.data,
type: node.data.type,
isSubdomain: false,
name: node.id,
loading: false
}
/* _this.entity.loading = true
const relatedEntityCount = await _this.queryRelatedEntityCount(node.data.type, node.id) const relatedEntityCount = await _this.queryRelatedEntityCount(node.data.type, node.id)
// 赋值总数和已拓展数。entity node可从其childNodes取到已拓展数 // 赋值总数和已拓展数。entity node可从其childNodes取到已拓展数
_this.entity = { _this.entity = {
@@ -289,8 +329,8 @@ export default {
} }
_this.graph.refreshItem(n.id) _this.graph.refreshItem(n.id)
}) })
} } */
const { tempNodes, tempEdges } = _this.generateTempNodesAndEdges(node, relatedEntityCount) const { tempNodes, tempEdges } = _this.generateTempNodesAndEdges(node, node.data.relatedEntityCount)
if (tempNodes.length > 0) { if (tempNodes.length > 0) {
change = true change = true
_this.addNodes(tempNodes) _this.addNodes(tempNodes)
@@ -329,10 +369,13 @@ export default {
const entityNodes = [] const entityNodes = []
const edges = [] const edges = []
if (level < 9) { if (level < 9) {
_this.entity.loading = true
const queryEntityNodes = await _this.generateHalfLevelNodes(primaryNode) const queryEntityNodes = await _this.generateHalfLevelNodes(primaryNode)
_this.entity.loading = false
_this.updateRelatedCount(sourceNode, [primaryNode]) _this.updateRelatedCount(sourceNode, [primaryNode])
entityNodes.push(...queryEntityNodes) entityNodes.push(...queryEntityNodes)
edges.push(..._this.generateEdges(primaryNode)) edges.push(..._this.generateEdges(primaryNode))
// TODO 高亮 // TODO 高亮
} else { } else {
// TODO 提示无法拓展 // TODO 提示无法拓展
@@ -499,7 +542,22 @@ export default {
} }
} }
}, },
generateEntityNode (nodeData, data) { async generateEntityNode (nodeData, data) {
const tags = await this.queryTags(nodeData.type, data.vertex)
let _tags = []
Object.keys(tags).forEach(k => {
if (k !== 'userDefinedTags' && tags[k]) {
Object.keys(tags[k]).forEach(k2 => {
const find = entityDetailTags[this.entity.entityType].find(t => t.name === k2)
if (find) {
_tags.push({ key: k2, value: this.tagValueHandler(k, k2, tags[k][k2]), type: find.type })
}
})
}
})
if (_.isArray(tags.userDefinedTags)) {
_tags = _.concat(_tags, tags.userDefinedTags.map(tag => ({ value: tag.tagValue, type: 'normal' })))
}
let width = 0 let width = 0
let height = 0 let height = 0
switch (nodeData.type) { switch (nodeData.type) {
@@ -539,7 +597,8 @@ export default {
type: nodeData.type, type: nodeData.type,
isSubdomain: nodeData.isSubdomain || false, isSubdomain: nodeData.isSubdomain || false,
name: data.vertex, name: data.vertex,
data data,
tags: _tags
} }
} }
}, },
@@ -623,6 +682,7 @@ export default {
const nodes = [] const nodes = []
if (relatedEntityCount) { if (relatedEntityCount) {
this.entity.relatedEntityCount = this.handleRelatedEntityCount(rootNode, relatedEntityCount) this.entity.relatedEntityCount = this.handleRelatedEntityCount(rootNode, relatedEntityCount)
rootNode.data.relatedEntityCount = this.entity.relatedEntityCount
const ipNode = this.generatePrimaryNode({ const ipNode = this.generatePrimaryNode({
id: 'ip-1', id: 'ip-1',
parentId: rootNode.id, parentId: rootNode.id,
@@ -738,9 +798,9 @@ export default {
const data = await this.queryRelatedEntity(node.data.sourceType, node.data.sourceName, node.data.type, pageNo) const data = await this.queryRelatedEntity(node.data.sourceType, node.data.sourceName, node.data.type, pageNo)
if (data) { if (data) {
// 生成节点 // 生成节点
data.list.forEach(d => { for (const d of data.list) {
newNodes2.push(this.generateEntityNode(node.data, d)) newNodes2.push(await this.generateEntityNode(node.data, d))
}) }
// 更新源节点的已拓展数和列表 // 更新源节点的已拓展数和列表
this.handleLoaded(node, data, newNodes2) this.handleLoaded(node, data, newNodes2)
newNodes.push(...newNodes2) newNodes.push(...newNodes2)
@@ -807,6 +867,7 @@ export default {
} }
const response = await axios.get(url).catch(e => { const response = await axios.get(url).catch(e => {
console.error(e) console.error(e)
this.entity.loading = false
this.showError = true this.showError = true
this.errorMsg = this.errorMsgHandler(e) this.errorMsg = this.errorMsgHandler(e)
}) })
@@ -815,6 +876,7 @@ export default {
} else { } else {
console.error(response) console.error(response)
this.showError = true this.showError = true
this.entity.loading = false
this.errorMsg = this.errorMsgHandler(response) this.errorMsg = this.errorMsgHandler(response)
return null return null
} }
@@ -887,16 +949,16 @@ export default {
const tempEdges = [] const tempEdges = []
switch (node.data.type) { switch (node.data.type) {
case 'ip': { case 'ip': {
if (relatedEntityCount.domainCount && !this.hasChildNodeByType(node, 'domain')) { if (relatedEntityCount.domain.total && !this.hasChildNodeByType(node, 'domain')) {
const tempNode = this.generateTempNode('domain', node) const tempNode = this.generateTempNode('domain', node)
tempNode.data.count = relatedEntityCount.domainCount tempNode.data.count = relatedEntityCount.domain.total
const tempEdge = this.generateTempEdge(node, tempNode) const tempEdge = this.generateTempEdge(node, tempNode)
tempNodes.push(tempNode) tempNodes.push(tempNode)
tempEdges.push(tempEdge) tempEdges.push(tempEdge)
} }
if (relatedEntityCount.appCount && !this.hasChildNodeByType(node, 'app')) { if (relatedEntityCount.app.total && !this.hasChildNodeByType(node, 'app')) {
const tempNode = this.generateTempNode('app', node) const tempNode = this.generateTempNode('app', node)
tempNode.data.count = relatedEntityCount.appCount tempNode.data.count = relatedEntityCount.app.total
const tempEdge = this.generateTempEdge(node, tempNode) const tempEdge = this.generateTempEdge(node, tempNode)
tempNodes.push(tempNode) tempNodes.push(tempNode)
tempEdges.push(tempEdge) tempEdges.push(tempEdge)
@@ -904,23 +966,23 @@ export default {
break break
} }
case 'domain': { case 'domain': {
if (relatedEntityCount.ipCount && !this.hasChildNodeByType(node, 'ip')) { if (relatedEntityCount.ip.total && !this.hasChildNodeByType(node, 'ip')) {
const tempNode = this.generateTempNode('ip', node) const tempNode = this.generateTempNode('ip', node)
tempNode.data.count = relatedEntityCount.ipCount tempNode.data.count = relatedEntityCount.ip.total
const tempEdge = this.generateTempEdge(node, tempNode) const tempEdge = this.generateTempEdge(node, tempNode)
tempNodes.push(tempNode) tempNodes.push(tempNode)
tempEdges.push(tempEdge) tempEdges.push(tempEdge)
} }
if (relatedEntityCount.subDomainCount && !this.hasChildNodeByType(node, 'domain')) { if (relatedEntityCount.subDomain.total && !this.hasChildNodeByType(node, 'domain')) {
const tempNode = this.generateTempNode('domain', node, true) const tempNode = this.generateTempNode('domain', node, true)
tempNode.data.count = relatedEntityCount.subDomainCount tempNode.data.count = relatedEntityCount.subDomain.total
const tempEdge = this.generateTempEdge(node, tempNode) const tempEdge = this.generateTempEdge(node, tempNode)
tempNodes.push(tempNode) tempNodes.push(tempNode)
tempEdges.push(tempEdge) tempEdges.push(tempEdge)
} }
if (relatedEntityCount.appCount && !this.hasChildNodeByType(node, 'app')) { if (relatedEntityCount.app.total && !this.hasChildNodeByType(node, 'app')) {
const tempNode = this.generateTempNode('app', node) const tempNode = this.generateTempNode('app', node)
tempNode.data.count = relatedEntityCount.appCount tempNode.data.count = relatedEntityCount.app.total
const tempEdge = this.generateTempEdge(node, tempNode) const tempEdge = this.generateTempEdge(node, tempNode)
tempNodes.push(tempNode) tempNodes.push(tempNode)
tempEdges.push(tempEdge) tempEdges.push(tempEdge)
@@ -928,16 +990,16 @@ export default {
break break
} }
case 'app': { case 'app': {
if (relatedEntityCount.ipCount && !this.hasChildNodeByType(node, 'ip')) { if (relatedEntityCount.ip.total && !this.hasChildNodeByType(node, 'ip')) {
const tempNode = this.generateTempNode('ip', node) const tempNode = this.generateTempNode('ip', node)
tempNode.data.count = relatedEntityCount.ipCount tempNode.data.count = relatedEntityCount.ip.total
const tempEdge = this.generateTempEdge(node, tempNode) const tempEdge = this.generateTempEdge(node, tempNode)
tempNodes.push(tempNode) tempNodes.push(tempNode)
tempEdges.push(tempEdge) tempEdges.push(tempEdge)
} }
if (relatedEntityCount.domainCount && !this.hasChildNodeByType(node, 'domain')) { if (relatedEntityCount.domain.total && !this.hasChildNodeByType(node, 'domain')) {
const tempNode = this.generateTempNode('domain', node) const tempNode = this.generateTempNode('domain', node)
tempNode.data.count = relatedEntityCount.domainCount tempNode.data.count = relatedEntityCount.domain.total
const tempEdge = this.generateTempEdge(node, tempNode) const tempEdge = this.generateTempEdge(node, tempNode)
tempNodes.push(tempNode) tempNodes.push(tempNode)
tempEdges.push(tempEdge) tempEdges.push(tempEdge)
@@ -1028,10 +1090,9 @@ export default {
}) })
}, },
getNodeLevel (id) { getNodeLevel (id) {
const matrix = this.graph.getShortestPathMatrix(false) const { findShortestPath } = Algorithm
const rootPathMatrix = matrix[0] const info = findShortestPath(this.graphData, this.entity.entityName, id)
const nodeIndex = this.graphData.nodes.findIndex(n => n.id === id) return info.length
return rootPathMatrix[nodeIndex]
}, },
onCloseBlock () { onCloseBlock () {
// todo 关闭右侧graph面板 // todo 关闭右侧graph面板
@@ -1085,16 +1146,42 @@ export default {
<span>${_this.$t('entity.graph.expandedEntityQuantity')}:&nbsp;${node.data.loaded ? node.data.loaded.length : 0}</span> <span>${_this.$t('entity.graph.expandedEntityQuantity')}:&nbsp;${node.data.loaded ? node.data.loaded.length : 0}</span>
</div> </div>
</div>` </div>`
} else { } else if (node.nodeType === 'entity' || node.nodeType === 'root') {
// TODO 逻辑 if (node.data && node.data.tags && node.data.tags.length > 0) {
return `<div class="entity-node-tooltip"> return `<div class="entity-node-tooltip">
<div class="tooltip__header"><span class="tooltip__title">${node.id}</span></div> <div class="tooltip__header">
</div>` <span class="tooltip__title">${node.id}</span>
</div>
<div class="tooltip__content">
<div class="content-header">
<div class="header-icon"></div>
<span>${_this.$t('entity.graph.tags')}</span>
</div>
<div class="content-tag-list">${generateTagHTML(node.data.tags)}</div>
</div>
</div>`
} else {
return `<div style="padding: 0 6px; font-size: 15px; line-height: 15px; color: #111;">${node.id}</div>`
}
} else if (node.nodeType === 'temp') {
return node.label
}
function generateTagHTML (tags) {
let html = ''
if (tags) {
tags.forEach(t => {
html += `<div class="entity-tag entity-tag--level-two-${t.type}">
<span>${t.value}</span>
</div>`
})
}
return html
} }
} }
}) })
}, },
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) {
@@ -1105,8 +1192,11 @@ export default {
this.entity = { this.entity = {
...this.entity, ...this.entity,
relatedEntityCount: this.handleRelatedEntityCount(sourceNode, relatedEntityCount), relatedEntityCount: this.handleRelatedEntityCount(sourceNode, relatedEntityCount),
listData: node.data.loaded 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)
@@ -1117,16 +1207,13 @@ export default {
}, },
async expandDetailList (expandType, currentName) { async expandDetailList (expandType, currentName) {
const currentNode = this.graphData.nodes.find(n => n.id === currentName) const currentNode = this.graphData.nodes.find(n => n.id === currentName)
console.info(expandType, currentName, currentNode)
// 如果存在primary node直接拓展 // 如果存在primary node直接拓展
const _primaryNode = currentNode.data.childNodes ? currentNode.data.childNodes.find(n => n.data.type === expandType) : null const _primaryNode = currentNode.data.childNodes ? currentNode.data.childNodes.find(n => n.data.type === expandType) : null
console.info('primary', _primaryNode)
if (_primaryNode) { if (_primaryNode) {
await this.expandList(currentName, _primaryNode.id) await this.expandList(currentName, _primaryNode.id)
} else { } else {
// 如果是temp node删掉新增primary node再拓展 // 如果是temp node删掉新增primary node再拓展
const tempNode = this.graphData.nodes.find(n => n.nodeType === 'temp' && n.data.sourceName === currentName && n.data.type === expandType) const tempNode = this.graphData.nodes.find(n => n.nodeType === 'temp' && n.data.sourceName === currentName && n.data.type === expandType)
console.info('temp', tempNode)
if (tempNode) { if (tempNode) {
// 先清除此temp node和temp edge // 先清除此temp node和temp edge
this.graphData.nodes = this.graphData.nodes.filter(n => { this.graphData.nodes = this.graphData.nodes.filter(n => {
@@ -1160,7 +1247,9 @@ export default {
const edges = [] const edges = []
const level = this.getNodeLevel(primaryNode.id) const level = this.getNodeLevel(primaryNode.id)
if (level < 9) { if (level < 9) {
this.entity.loading = true
const queryEntityNodes = await this.generateHalfLevelNodes(primaryNode) const queryEntityNodes = await this.generateHalfLevelNodes(primaryNode)
this.entity.loading = false
this.updateRelatedCount(currentNode, [primaryNode]) this.updateRelatedCount(currentNode, [primaryNode])
entityNodes.push(...queryEntityNodes) entityNodes.push(...queryEntityNodes)
edges.push(...this.generateEdges(primaryNode)) edges.push(...this.generateEdges(primaryNode))
@@ -1170,7 +1259,6 @@ export default {
} }
this.addNodes(entityNodes) this.addNodes(entityNodes)
this.addEdges(edges) this.addEdges(edges)
console.info(this.graphData)
this.graph.changeData(this.graphData) this.graph.changeData(this.graphData)
} }
} }
@@ -1360,11 +1448,13 @@ export default {
} }
} }
.entity-node-tooltip { .entity-node-tooltip {
width: 300px;
padding: 5px; padding: 5px;
.tooltip__header { .tooltip__header {
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 20px;
.tooltip__title { .tooltip__title {
font-size: 15px; font-size: 15px;
@@ -1372,6 +1462,60 @@ export default {
color: #111; color: #111;
} }
} }
.tooltip__content {
display: flex;
flex-direction: column;
.content-header {
display: flex;
align-items: center;
.header-icon {
width: 3px !important;
height: 12px !important;
background: #38ACD2;
border-radius: 1px;
margin-right: 6px;
}
}
.content-tag-list {
display: flex;
align-items: flex-start;
flex-wrap: wrap;
.entity-tag {
margin: 10px 0;
padding: 0 8px;
height: 20px;
font-size: 12px;
border: 1px solid;
border-radius: 2px;
$normal-color: #778391;
$normal-light-color: #F7F8F9;
$negative-color: #E26154;
$negative-light-color: #FEF6F5;
$positive-color: #749F4D;
$positive-light-color: #F7FAF5;
&.entity-tag--level-two-normal {
border-color: $normal-color;
color: $normal-color;
background-color: $normal-light-color;
}
&.entity-tag--level-two-negative {
border-color: $negative-color;
color: $negative-color;
background-color: $negative-light-color;
}
&.entity-tag--level-two-positive {
border-color: $positive-color;
color: $positive-color;
background-color: $positive-light-color;
}
}
}
}
} }
} }
} }

View File

@@ -66,6 +66,7 @@
<script> <script>
import { riskLevelMapping } from '@/utils/constants' import { riskLevelMapping } from '@/utils/constants'
import Loading from '@/components/common/Loading' import Loading from '@/components/common/Loading'
import { scrollToTop } from '@/utils/tools'
export default { export default {
name: 'AppList', name: 'AppList',
props: { props: {
@@ -110,6 +111,11 @@ export default {
this.$emit('expandList', this.entity.sourceName, this.entity.nodeId) this.$emit('expandList', this.entity.sourceName, this.entity.nodeId)
} }
} }
},
mounted () {
this.$nextTick(() => {
scrollToTop()
})
} }
} }
</script> </script>

View File

@@ -62,6 +62,7 @@
<script> <script>
import { riskLevelMapping } from '@/utils/constants' import { riskLevelMapping } from '@/utils/constants'
import Loading from '@/components/common/Loading' import Loading from '@/components/common/Loading'
import { scrollToTop } from '@/utils/tools'
export default { export default {
name: 'DomainList', name: 'DomainList',
props: { props: {
@@ -106,6 +107,11 @@ export default {
this.$emit('expandList', this.entity.sourceName, this.entity.nodeId) this.$emit('expandList', this.entity.sourceName, this.entity.nodeId)
} }
} }
},
mounted () {
this.$nextTick(() => {
scrollToTop()
})
} }
} }
</script> </script>

View File

@@ -8,7 +8,7 @@
<div class="entity-graph-type">{{ entityType[entity.type || entity.entityType] }}</div> <div class="entity-graph-type">{{ entityType[entity.type || entity.entityType] }}</div>
<div class="graph-basic-info"> <div class="graph-basic-info">
<div class="graph-basic-info-name__block"> <div class="graph-basic-info-name__block">
<div class="graph-basic-info-name" id="entityName">{{ $_.get(entity, 'detailData.vertex', '') }}</div> <div class="graph-basic-info-name" :title="$_.get(entity, 'detailData.vertex', '')" id="entityName">{{ $_.get(entity, 'detailData.vertex', '') }}</div>
<div class="graph-basic-info-icon" @click="copyEntityName"> <div class="graph-basic-info-icon" @click="copyEntityName">
<i class="cn-icon cn-icon-copy"></i> <i class="cn-icon cn-icon-copy"></i>
</div> </div>
@@ -153,7 +153,7 @@
</div> </div>
<!--标签--> <!--标签-->
<div class="digital-certificate graph-basic-info__block"> <div v-if="entity.tags" 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">
@@ -238,171 +238,13 @@ export default {
entity: { entity: {
deep: true, deep: true,
handler (n) { handler (n) {
const type = n.type || n.entityType this.handleDetailData(n)
switch (type) {
case 'ip': {
this.detailCards = [
{ name: 'asn', label: this.$t('entities.asNumber'), value: _.get(n.detailData, 'detail.asn.asn', '-') },
{
name: 'asOrg',
label: this.$t('entities.asOrg'),
value: _.get(n.detailData, 'detail.asn.organization', '-')
},
{
name: 'isp',
label: this.$t('entities.graph.isp'),
value: _.get(n.detailData, 'detail.location.isp', '-')
},
{ name: 'location', label: this.$t('overall.location'), value: this.location(n.detailData) }
]
this.relationList = [
{
icon: 'cn-icon cn-icon-subdomain',
name: 'domain',
label: this.$t('entity.graph.resolveDomain'),
value: _.get(n.relatedEntityCount, 'domain.current', '0') || 0,
total: _.get(n.relatedEntityCount, 'domain.total', '0') || 0
},
{
icon: 'cn-icon cn-icon-app-name',
name: 'app',
label: this.$t('entities.tab.relatedApp'),
value: _.get(n.relatedEntityCount, 'app.current', '0') || 0,
total: _.get(n.relatedEntityCount, 'app.total', '0') || 0
}
]
break
}
case 'domain': {
const expireDate = _.get(n.detailData, 'detail.whois.expireDate', '')
const createDate = _.get(n.detailData, 'detail.whois.createDate', '')
this.detailCards = [
{
name: 'categoryName',
label: this.$t('entities.category'),
value: _.get(n.detailData, 'detail.category.name', '-')
},
{
name: 'categoryGroup',
label: this.$t('entities.group'),
value: _.get(n.detailData, 'detail.category.group', '-')
},
{
name: 'reputationLevel',
label: this.$t('entities.creditLevel2'),
value: _.get(n.detailData, 'detail.category.reputationLevel', '-')
},
{
name: 'expireDate',
label: this.$t('entities.graph.expirationDate'),
value: expireDate ? dateFormatByAppearance(expireDate) : '-'
},
{
name: 'registrarName',
label: this.$t('entities.registrar'),
value: _.get(n.detailData, 'detail.whois.registrarName', '-')
},
{
name: 'registrantOrg',
label: this.$t('entities.registry'),
value: _.get(n.detailData, 'detail.whois.registrantOrg', '-')
},
{
name: 'registrantCountry',
label: this.$t('entities.registrationCountry'),
value: _.get(n.detailData, 'detail.whois.registrantCountry', '-')
},
{
name: 'createDate',
label: this.$t('entities.registrationDate'),
value: createDate ? dateFormatByAppearance(createDate) : '-'
},
{
name: 'email',
label: this.$t('entities.registryEmail'),
value: _.get(n.detailData, 'detail.whois.email', '-')
}
]
this.relationList = [
{
icon: 'cn-icon cn-icon-resolve-ip',
name: 'ip',
label: this.$t('entities.graph.resolveIp'),
value: _.get(n.relatedEntityCount, 'ip.current', '0') || 0,
total: _.get(n.relatedEntityCount, 'ip.total', '0') || 0
},
{
icon: 'cn-icon cn-icon-subdomain',
name: 'subDomain',
label: this.$t('entities.subdomain'),
value: _.get(n.relatedEntityCount, 'subDomain.current', '0') || 0,
total: _.get(n.relatedEntityCount, 'subDomain.total', '0') || 0
},
{
icon: 'cn-icon cn-icon-app-name',
name: 'app',
label: this.$t('entities.tab.relatedApp'),
value: _.get(n.relatedEntityCount, 'app.current', 0) || 0,
total: _.get(n.relatedEntityCount, 'app.total', 0) || 0
}
]
break
}
case 'app': {
this.detailCards = [
{
name: 'appCategory',
label: this.$t('entities.category'),
value: _.get(n.detailData, 'detail.category.appCategory', '-')
},
{
name: 'appSubcategory',
label: this.$t('entities.subcategory'),
value: _.get(n.detailData, 'detail.category.appSubcategory', '-')
},
{
name: 'appRisk',
label: this.$t('entities.riskLevel'),
value: _.get(n.detailData, 'detail.category.appRisk', '-')
},
{
name: 'appTechnology',
label: this.$t('overall.technology'),
value: _.get(n.detailData, 'detail.category.appTechnology', '-')
},
{
name: 'appLongname',
label: this.$t('overall.appFullName'),
value: _.get(n.detailData, 'detail.category.appLongname', '-')
},
{
name: 'appDescription',
label: this.$t('config.dataSource.description'),
value: _.get(n.detailData, 'detail.category.appDescription', '-')
}
]
this.relationList = [
{
icon: 'cn-icon cn-icon-resolve-ip',
name: 'ip',
label: this.$t('entities.graph.resolveIp'),
value: _.get(n.relatedEntityCount, 'ip.current', '0') || 0,
total: _.get(n.relatedEntityCount, 'ip.total', '0') || 0
},
{
icon: 'cn-icon cn-icon-subdomain',
name: 'domain',
label: this.$t('entity.graph.resolveDomain'),
value: _.get(n.relatedEntityCount, 'domain.current', '0') || 0,
total: _.get(n.relatedEntityCount, 'domain.total', '0') || 0
}
]
}
}
} }
} }
}, },
mounted () {
this.handleDetailData(this.entity)
},
setup (props) { setup (props) {
const detailCards = ref([]) const detailCards = ref([])
const relationList = ref([]) const relationList = ref([])
@@ -480,6 +322,171 @@ export default {
} }
} }
return location || '-' return location || '-'
},
handleDetailData (entity) {
const n = entity
const type = n.type || n.entityType
switch (type) {
case 'ip': {
this.detailCards = [
{ name: 'asn', label: this.$t('entities.asNumber'), value: _.get(n.detailData, 'detail.asn.asn', '-') },
{
name: 'asOrg',
label: this.$t('entities.asOrg'),
value: _.get(n.detailData, 'detail.asn.organization', '-')
},
{
name: 'isp',
label: this.$t('entities.graph.isp'),
value: _.get(n.detailData, 'detail.location.isp', '-')
},
{ name: 'location', label: this.$t('overall.location'), value: this.location(n.detailData) }
]
this.relationList = [
{
icon: 'cn-icon cn-icon-subdomain',
name: 'domain',
label: this.$t('entity.graph.resolveDomain'),
value: _.get(n.relatedEntityCount, 'domain.current', '0') || 0,
total: _.get(n.relatedEntityCount, 'domain.total', '0') || 0
},
{
icon: 'cn-icon cn-icon-app-name',
name: 'app',
label: this.$t('entities.tab.relatedApp'),
value: _.get(n.relatedEntityCount, 'app.current', '0') || 0,
total: _.get(n.relatedEntityCount, 'app.total', '0') || 0
}
]
break
}
case 'domain': {
const expireDate = _.get(n.detailData, 'detail.whois.expireDate', '')
const createDate = _.get(n.detailData, 'detail.whois.createDate', '')
this.detailCards = [
{
name: 'categoryName',
label: this.$t('entities.category'),
value: _.get(n.detailData, 'detail.category.name', '-')
},
{
name: 'categoryGroup',
label: this.$t('entities.group'),
value: _.get(n.detailData, 'detail.category.group', '-')
},
{
name: 'reputationLevel',
label: this.$t('entities.creditLevel2'),
value: _.get(n.detailData, 'detail.category.reputationLevel', '-')
},
{
name: 'expireDate',
label: this.$t('entities.graph.expirationDate'),
value: expireDate ? dateFormatByAppearance(expireDate) : '-'
},
{
name: 'registrarName',
label: this.$t('entities.registrar'),
value: _.get(n.detailData, 'detail.whois.registrarName', '-')
},
{
name: 'registrantOrg',
label: this.$t('entities.registry'),
value: _.get(n.detailData, 'detail.whois.registrantOrg', '-')
},
{
name: 'registrantCountry',
label: this.$t('entities.registrationCountry'),
value: _.get(n.detailData, 'detail.whois.registrantCountry', '-')
},
{
name: 'createDate',
label: this.$t('entities.registrationDate'),
value: createDate ? dateFormatByAppearance(createDate) : '-'
},
{
name: 'email',
label: this.$t('entities.registryEmail'),
value: _.get(n.detailData, 'detail.whois.email', '-')
}
]
this.relationList = [
{
icon: 'cn-icon cn-icon-resolve-ip',
name: 'ip',
label: this.$t('entities.graph.resolveIp'),
value: _.get(n.relatedEntityCount, 'ip.current', '0') || 0,
total: _.get(n.relatedEntityCount, 'ip.total', '0') || 0
},
{
icon: 'cn-icon cn-icon-subdomain',
name: 'subDomain',
label: this.$t('entities.subdomain'),
value: _.get(n.relatedEntityCount, 'subDomain.current', '0') || 0,
total: _.get(n.relatedEntityCount, 'subDomain.total', '0') || 0
},
{
icon: 'cn-icon cn-icon-app-name',
name: 'app',
label: this.$t('entities.tab.relatedApp'),
value: _.get(n.relatedEntityCount, 'app.current', 0) || 0,
total: _.get(n.relatedEntityCount, 'app.total', 0) || 0
}
]
break
}
case 'app': {
this.detailCards = [
{
name: 'appCategory',
label: this.$t('entities.category'),
value: _.get(n.detailData, 'detail.category.appCategory', '-')
},
{
name: 'appSubcategory',
label: this.$t('entities.subcategory'),
value: _.get(n.detailData, 'detail.category.appSubcategory', '-')
},
{
name: 'appRisk',
label: this.$t('entities.riskLevel'),
value: _.get(n.detailData, 'detail.category.appRisk', '-')
},
{
name: 'appTechnology',
label: this.$t('overall.technology'),
value: _.get(n.detailData, 'detail.category.appTechnology', '-')
},
{
name: 'appLongname',
label: this.$t('overall.appFullName'),
value: _.get(n.detailData, 'detail.category.appLongname', '-')
},
{
name: 'appDescription',
label: this.$t('config.dataSource.description'),
value: _.get(n.detailData, 'detail.category.appDescription', '-')
}
]
this.relationList = [
{
icon: 'cn-icon cn-icon-resolve-ip',
name: 'ip',
label: this.$t('entities.graph.resolveIp'),
value: _.get(n.relatedEntityCount, 'ip.current', '0') || 0,
total: _.get(n.relatedEntityCount, 'ip.total', '0') || 0
},
{
icon: 'cn-icon cn-icon-subdomain',
name: 'domain',
label: this.$t('entity.graph.resolveDomain'),
value: _.get(n.relatedEntityCount, 'domain.current', '0') || 0,
total: _.get(n.relatedEntityCount, 'domain.total', '0') || 0
}
]
}
}
} }
} }
} }

View File

@@ -60,6 +60,7 @@
<script> <script>
import Loading from '@/components/common/Loading' import Loading from '@/components/common/Loading'
import { scrollToTop } from '@/utils/tools'
export default { export default {
name: 'IpList', name: 'IpList',
props: { props: {
@@ -90,6 +91,11 @@ export default {
// 继续拓展ip列表传递信息调用接口 // 继续拓展ip列表传递信息调用接口
this.$emit('expandList', this.entity.sourceName, this.entity.nodeId) this.$emit('expandList', this.entity.sourceName, this.entity.nodeId)
} }
},
mounted () {
this.$nextTick(() => {
scrollToTop()
})
} }
} }
</script> </script>