feat: 实体关系图优化
This commit is contained in:
@@ -1,6 +1,3 @@
|
|||||||
$color-business: var(--el-color-business);
|
|
||||||
$color-primary: var(--el-text-color-primary);
|
|
||||||
$color-regular: var(--el-text-color-regular);
|
|
||||||
.graph-toolbar {
|
.graph-toolbar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 100px;
|
top: 100px;
|
||||||
@@ -18,13 +15,12 @@ $color-regular: var(--el-text-color-regular);
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
i {
|
i {
|
||||||
color: $color-regular;
|
color: #575757;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.toolbar--unactivated {
|
&.toolbar--unactivated {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
|
||||||
i {
|
i {
|
||||||
opacity: .4;
|
opacity: .4;
|
||||||
}
|
}
|
||||||
@@ -32,24 +28,137 @@ $color-regular: var(--el-text-color-regular);
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.entity-graph {
|
.entity-graph {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
background-color:'orange';
|
||||||
|
border: 'solid 5px red';
|
||||||
.entity-graph__chart {
|
.entity-graph__chart {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height:100%;
|
||||||
|
overflow: hidden;
|
||||||
|
.force-graph-container .graph-tooltip {
|
||||||
|
padding: 8px 10px;
|
||||||
|
//border-radius: 10px;
|
||||||
|
background-color: white;
|
||||||
|
box-shadow: -1px 1px 10px -1px rgba(205,205,205,0.85);
|
||||||
|
|
||||||
|
.primary-node-tooltip {
|
||||||
|
padding: 5px;
|
||||||
|
|
||||||
|
.tooltip__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: #717171;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.tooltip__title {
|
||||||
|
padding-left: 6px;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 15px;
|
||||||
|
color: #111;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tooltip__content {
|
||||||
|
padding-top: 10px;
|
||||||
|
color: #222;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
span:first-of-type {
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.entity-node-tooltip {
|
||||||
|
width: 300px;
|
||||||
|
padding: 5px;
|
||||||
|
|
||||||
|
.tooltip__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.tooltip__title {
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 15px;
|
||||||
|
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 9px 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.force-graph-container {
|
||||||
|
//height:100% !important;
|
||||||
|
canvas {
|
||||||
|
//height:100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.graph-node {
|
.graph-node {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
transition: linear all var(--el-transition-duration-fast);
|
transition: linear all .2s;
|
||||||
|
|
||||||
.graph-node__text {
|
.graph-node__text {
|
||||||
width: 120px;
|
width: 120px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: $color-primary;
|
color: #353636;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.graph-node--root {
|
&.graph-node--root {
|
||||||
@@ -62,38 +171,30 @@ $color-regular: var(--el-text-color-regular);
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
animation: none;
|
animation: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.graph-node--ip {
|
&.graph-node--ip {
|
||||||
border-color: var(--el-color-success-light-5) !important;
|
border-color: #CBD9BB !important;
|
||||||
box-shadow: 0 0 0 8px rgba(126, 159, 84, 0.14);
|
box-shadow: 0 0 0 8px rgba(126,159,84,0.14);
|
||||||
|
|
||||||
i {
|
i {
|
||||||
color: var(--el-color-success);
|
color: #7E9F54;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.graph-node--domain {
|
&.graph-node--domain {
|
||||||
border-color: var(--el-color-primary-light-7) !important;
|
border-color: #AFDEED !important;
|
||||||
box-shadow: 0 0 0 8px rgba(56, 172, 210, 0.14);
|
box-shadow: 0 0 0 8px rgba(56,172,210,0.14);
|
||||||
|
|
||||||
i {
|
i {
|
||||||
color: $color-business;
|
color: #38ACD2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.graph-node--app {
|
&.graph-node--app {
|
||||||
border-color: var(--el-color-warning-light-5) !important;
|
border-color: #F5DAA3 !important;
|
||||||
box-shadow: 0 0 0 8px rgba(229, 162, 25, 0.14);
|
box-shadow: 0 0 0 8px rgba(229,162,25,0.14);
|
||||||
|
|
||||||
i {
|
i {
|
||||||
color: var(--el-color-warning);
|
color: #E5A219;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
i {
|
i {
|
||||||
font-size: 36px;
|
font-size: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.graph-node__text {
|
.graph-node__text {
|
||||||
padding-top: 30px;
|
padding-top: 30px;
|
||||||
}
|
}
|
||||||
@@ -103,21 +204,19 @@ $color-regular: var(--el-text-color-regular);
|
|||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
width: 66px;
|
width: 66px;
|
||||||
height: 66px;
|
height: 66px;
|
||||||
border: 1px solid #A7B0B9 !important; // 该class并未使用到
|
border: 1px solid #A7B0B9 !important;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
|
||||||
// 覆盖自带的node点击效果
|
// 覆盖自带的node点击效果
|
||||||
&.rel-node-checked {
|
&.rel-node-checked {
|
||||||
box-shadow: 0 0 0 5px rgba(151, 151, 151, 0.21);
|
box-shadow: 0 0 0 5px rgba(151,151,151,0.21);
|
||||||
animation: none;
|
animation: none;
|
||||||
border-color: $color-regular !important;
|
border-color: #778391 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
i {
|
i {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
color: $color-regular;
|
color: #778391;
|
||||||
}
|
}
|
||||||
|
|
||||||
.graph-node__text {
|
.graph-node__text {
|
||||||
padding-top: 24px;
|
padding-top: 24px;
|
||||||
}
|
}
|
||||||
@@ -131,22 +230,19 @@ $color-regular: var(--el-text-color-regular);
|
|||||||
|
|
||||||
&.graph-node--ip {
|
&.graph-node--ip {
|
||||||
i {
|
i {
|
||||||
color: var(--el-color-success);
|
color: #7E9F54;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.graph-node--domain {
|
&.graph-node--domain {
|
||||||
i {
|
i {
|
||||||
color: $color-business;
|
color: #38ACD2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.graph-node--app {
|
&.graph-node--app {
|
||||||
i {
|
i {
|
||||||
color: var(--el-color-warning);
|
color: #E5A219;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
i {
|
i {
|
||||||
font-size: 21px;
|
font-size: 21px;
|
||||||
}
|
}
|
||||||
@@ -159,7 +255,6 @@ $color-regular: var(--el-text-color-regular);
|
|||||||
left: unset !important;
|
left: unset !important;
|
||||||
right: 0 !important;
|
right: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.entity-graph__detail {
|
.entity-graph__detail {
|
||||||
height: calc(100% - 100px) !important;
|
height: calc(100% - 100px) !important;
|
||||||
top: 100px !important;
|
top: 100px !important;
|
||||||
@@ -170,7 +265,7 @@ $color-regular: var(--el-text-color-regular);
|
|||||||
}
|
}
|
||||||
|
|
||||||
.g6-component-tooltip {
|
.g6-component-tooltip {
|
||||||
box-shadow: -1px 1px 10px -1px rgba(205, 205, 205, 0.85);
|
box-shadow: -1px 1px 10px -1px rgba(205,205,205,0.85);
|
||||||
|
|
||||||
.primary-node-tooltip {
|
.primary-node-tooltip {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
@@ -180,21 +275,19 @@ $color-regular: var(--el-text-color-regular);
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
i {
|
i {
|
||||||
color: $color-regular;
|
color: #717171;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip__title {
|
.tooltip__title {
|
||||||
padding-left: 6px;
|
padding-left: 6px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 15px;
|
line-height: 15px;
|
||||||
color: $color-primary;
|
color: #111;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip__content {
|
.tooltip__content {
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
color: $color-primary;
|
color: #222;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|
||||||
span:first-of-type {
|
span:first-of-type {
|
||||||
@@ -202,7 +295,6 @@ $color-regular: var(--el-text-color-regular);
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.entity-node-tooltip {
|
.entity-node-tooltip {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
@@ -215,7 +307,7 @@ $color-regular: var(--el-text-color-regular);
|
|||||||
.tooltip__title {
|
.tooltip__title {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 15px;
|
line-height: 15px;
|
||||||
color: $color-primary;
|
color: #111;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,12 +322,11 @@ $color-regular: var(--el-text-color-regular);
|
|||||||
.header-icon {
|
.header-icon {
|
||||||
width: 3px !important;
|
width: 3px !important;
|
||||||
height: 12px !important;
|
height: 12px !important;
|
||||||
background: $color-business;
|
background: #38ACD2;
|
||||||
border-radius: 1px;
|
border-radius: 1px;
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-tag-list {
|
.content-tag-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@@ -249,25 +340,22 @@ $color-regular: var(--el-text-color-regular);
|
|||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
|
||||||
$normal-color: $color-regular;
|
$normal-color: #778391;
|
||||||
$normal-light-color: var(--el-fill-color-light);
|
$normal-light-color: #F7F8F9;
|
||||||
$negative-color: var(--el-color-danger);
|
$negative-color: #E26154;
|
||||||
$negative-light-color: var(--el-color-danger-light-9);
|
$negative-light-color: #FEF6F5;
|
||||||
$positive-color: var(--el-color-success);
|
$positive-color: #749F4D;
|
||||||
$positive-light-color: var(--el-color-success-light-9);
|
$positive-light-color: #F7FAF5;
|
||||||
|
|
||||||
&.entity-tag--level-two-normal {
|
&.entity-tag--level-two-normal {
|
||||||
border-color: $normal-color;
|
border-color: $normal-color;
|
||||||
color: $normal-color;
|
color: $normal-color;
|
||||||
background-color: $normal-light-color;
|
background-color: $normal-light-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.entity-tag--level-two-negative {
|
&.entity-tag--level-two-negative {
|
||||||
border-color: $negative-color;
|
border-color: $negative-color;
|
||||||
color: $negative-color;
|
color: $negative-color;
|
||||||
background-color: $negative-light-color;
|
background-color: $negative-light-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.entity-tag--level-two-positive {
|
&.entity-tag--level-two-positive {
|
||||||
border-color: $positive-color;
|
border-color: $positive-color;
|
||||||
color: $positive-color;
|
color: $positive-color;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,21 @@
|
|||||||
export default class Link {
|
export default class Edge {
|
||||||
constructor (sourceNode, targetNode, type) {
|
constructor (sourceNode, targetNode, type = linkType.normal, distance, level = 1) {
|
||||||
this.id = sourceNode.id + '__' + targetNode.id
|
|
||||||
this.source = sourceNode.id
|
this.source = sourceNode.id
|
||||||
this.target = targetNode.id
|
this.target = targetNode.id
|
||||||
this.isTemp = type === 'temp'
|
this.type = type
|
||||||
|
this.distance = distance || 20// 连接中心节点的线的distance为60,其他节点为20
|
||||||
|
this.strength = level === 1 ? 0.5 : (level === 2 ? 1 : 1)// level2:设置为0.5-1
|
||||||
|
this.color = '#c8c8cc'// 未点击线所连接的节点时线的颜色
|
||||||
|
this.clickColor = '#7b7b99'// 点击此线所连接的节点后,线的颜色
|
||||||
|
// this.width = 1//未点击线所连接的节点时线的宽度
|
||||||
|
// this.arrowColor = '#c8c8cc'
|
||||||
|
// this.clickArrowColor = '#7b7b99'
|
||||||
|
// this.clickWidth = 1//点击节点后,此节点所连接的线的宽度
|
||||||
|
this.level = level// 1:source为中心节点的线,是直线; 2:source为listNode节点的线,是直线; 3:target为临时节点的线,是虚线
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const linkType = {
|
||||||
|
normal: 'normal',
|
||||||
|
temp: 'temp'
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,141 +1,85 @@
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import i18n from '@/i18n'
|
import { entityDefaultColor, entityType } from '@/utils/constants'
|
||||||
import axios from 'axios'
|
|
||||||
import { api } from '@/utils/api'
|
|
||||||
import { entityDefaultColor, intentColor } from '@/utils/constants'
|
|
||||||
import { formatTags } from '@/utils/tools'
|
import { formatTags } from '@/utils/tools'
|
||||||
|
import { generateLabel, builtTooltip, queryEntityBasicInfo, queryTags, queryRelatedEntityCount } from '@/views/entityExplorer/entityGraph/utils'
|
||||||
|
import { api } from '@/utils/api'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
export default class Node {
|
export default class Node {
|
||||||
/*
|
/*
|
||||||
* type: 对应nodeType
|
* type: 对应nodeType
|
||||||
* cfg: { entityType, entityName, x, y, fx, fy }
|
* cfg: { entityType, entityName }
|
||||||
* */
|
* */
|
||||||
constructor (type, id, cfg, sourceNode) {
|
constructor (type, id, data, sourceNode, strength, img, level) {
|
||||||
this.type = type
|
|
||||||
this.id = id
|
this.id = id
|
||||||
this.x = _.get(cfg, 'x', null)
|
this.type = type
|
||||||
this.y = _.get(cfg, 'y', null)
|
this.vx = 0
|
||||||
this.fx = _.get(cfg, 'fx', null)
|
this.vy = 0
|
||||||
this.fy = _.get(cfg, 'fy', null)
|
this.fx = level === 0 ? 0 : null// 设置为0,即可固定中心节点。0为中心节点,1为中心节点的子节点,2为第三级节点
|
||||||
this.myData = {
|
this.fy = level === 0 ? 0 : null// 设置为0,即可固定中心节点。0为中心节点,1为中心节点的子节点,2为第三级节点
|
||||||
entityType: cfg.entityType,
|
this.level = level
|
||||||
entityName: cfg.entityName
|
|
||||||
}
|
|
||||||
if (sourceNode) {
|
|
||||||
this.sourceNode = sourceNode
|
|
||||||
}
|
|
||||||
this.label = this.generateLabel()
|
|
||||||
|
|
||||||
const img = new Image()
|
|
||||||
img.src = this.getIconUrl(cfg.entityType, type === nodeType.rootNode)
|
|
||||||
this.img = img
|
this.img = img
|
||||||
}
|
this.strength = strength || -10
|
||||||
|
this.sourceNode = sourceNode
|
||||||
generateLabel () {
|
this.isSubdomain = sourceNode ? sourceNode.data.entityType === entityType.domain && data.entityType === entityType.domain : false
|
||||||
switch (this.type) {
|
this.data = {
|
||||||
case nodeType.rootNode:
|
entityType: data.entityType,
|
||||||
case nodeType.entityNode: {
|
entityName: data.entityName
|
||||||
return this.id
|
// img:data.img
|
||||||
}
|
|
||||||
case nodeType.listNode: {
|
|
||||||
return `${this.getLabelText()}(${_.get(this.sourceNode.myData, 'relatedEntities.' + this.myData.entityType + '.total', 0)})`
|
|
||||||
}
|
|
||||||
case nodeType.tempNode: {
|
|
||||||
return this.getLabelText()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return ''
|
this.label = generateLabel(type, id, data, sourceNode)
|
||||||
|
this.name = builtTooltip(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
getLabelText () {
|
// listNode和entityNode专用,查询实体信息
|
||||||
if (this.myData.entityType === 'ip') {
|
|
||||||
if (this.sourceNode.myData.entityType === 'app') {
|
|
||||||
return i18n.global.t('entities.tab.relatedIp')
|
|
||||||
} else if (this.sourceNode.myData.entityType === 'domain') {
|
|
||||||
return i18n.global.t('entities.graph.resolveIp')
|
|
||||||
}
|
|
||||||
} else if (this.myData.entityType === 'domain') {
|
|
||||||
if (this.sourceNode.myData.entityType === 'ip') {
|
|
||||||
return i18n.global.t('entities.graph.resolvedDomain')
|
|
||||||
} else if (this.sourceNode.myData.entityType === 'app') {
|
|
||||||
return i18n.global.t('entities.graph.relatedDomain')
|
|
||||||
} else if (this.sourceNode.myData.entityType === 'domain') {
|
|
||||||
return i18n.global.t('entities.subdomain')
|
|
||||||
}
|
|
||||||
} else if (this.myData.entityType === 'app') {
|
|
||||||
return i18n.global.t('entities.tab.relatedApp')
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
getIconUrl (entityType, colored) {
|
|
||||||
const suffix = colored ? '-colored' : ''
|
|
||||||
return require(`@/assets/img/entity-symbol2/${entityType}${suffix}.svg`)
|
|
||||||
}
|
|
||||||
|
|
||||||
isSubdomain () {
|
|
||||||
if (this.sourceNode) {
|
|
||||||
return this.sourceNode.myData.entityType === 'domain' && this.myData.entityType === 'domain'
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询basicInfo、tags、关联实体数量
|
|
||||||
async queryDetailData () {
|
async queryDetailData () {
|
||||||
const entityType = this.myData.entityType
|
const entityType = this.data.entityType
|
||||||
const entityName = this.myData.entityName
|
const entityName = this.data.entityName
|
||||||
|
|
||||||
this.myData.basicInfo = await this.queryEntityBasicInfo(entityType, entityName)
|
this.data.basicInfo = await queryEntityBasicInfo(entityType, entityName)
|
||||||
|
|
||||||
const tags = await this.queryTags(entityType, entityName)
|
const tags = await queryTags(entityType, entityName)
|
||||||
let _tags = []
|
let _tags = []
|
||||||
formatTags(tags, entityType, _tags)
|
formatTags(tags, entityType, _tags)
|
||||||
if (_.isArray(tags.tags)) {
|
if (_.isArray(tags.userDefinedTags)) {
|
||||||
_tags = _.concat(_tags, tags.tags.map(tag => ({ value: tag.name, color: intentColor[tag.intent] || entityDefaultColor })))
|
_tags = _.concat(_tags, tags.userDefinedTags.map(tag => ({ value: tag.tagValue, color: tag.knowledgeBase ? tag.knowledgeBase.color : entityDefaultColor })))
|
||||||
}
|
}
|
||||||
this.myData.tags = _tags
|
this.data.tags = _tags
|
||||||
|
|
||||||
const relatedEntityTotalCount = await this.queryRelatedEntitiesCount(entityType, entityName)
|
const relatedEntityTotalCount = await queryRelatedEntityCount(entityType, entityName)
|
||||||
this.myData.relatedEntities = {
|
this.data.relatedEntities = {
|
||||||
ip: { total: relatedEntityTotalCount.ipCount, loadedCount: 0 },
|
ip: { total: relatedEntityTotalCount.ipCount, pageNo: 0, list: [] }, //
|
||||||
domain: { total: this.myData.entityType === 'domain' ? relatedEntityTotalCount.subDomainCount : relatedEntityTotalCount.domainCount, loadedCount: 0 },
|
domain: { total: this.data.entityType === 'domain' ? relatedEntityTotalCount.subDomainCount : relatedEntityTotalCount.domainCount, pageNo: 0, list: [] }, // pageNo: 0,
|
||||||
app: { total: relatedEntityTotalCount.appCount, loadedCount: 0 }
|
app: { total: relatedEntityTotalCount.appCount, pageNo: 0, list: [] }// pageNo: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async queryEntityBasicInfo (entityType, entityName) {
|
getNeighbors (gData) {
|
||||||
const response = await axios.get(`${api.entity.entityGraph.basicInfo}/${entityType}?resource=${entityName}`).catch(e => {
|
const links = gData.links.filter(l => l.source.id === this.id || l.target.id === this.id)
|
||||||
console.error(e)
|
const nodes = gData.nodes.filter(n => links.some(l => l.source.id === n.id || l.target.id === n.id))
|
||||||
throw e
|
return { links, nodes }
|
||||||
})
|
}
|
||||||
if (response.data && response.status === 200) {
|
|
||||||
return response.data.data
|
// 获取唯一source,listNode和tempNode专用,因为entityNode的source可能有多个,rootNode无source
|
||||||
} else {
|
getSourceNode (gData) {
|
||||||
console.error(response)
|
const links = gData.links.filter(l => l.target.id === this.id)
|
||||||
throw response
|
const nodes = gData.nodes.filter(n => links.some(l => l.source.id === n.id))
|
||||||
}
|
return nodes.length > 0 ? nodes[0] : null
|
||||||
}
|
}
|
||||||
|
|
||||||
async queryTags (entityType, entityName) {
|
// listNode和entityNode专用,查询关联实体列表
|
||||||
const response = await axios.get(`${api.entity.entityGraph.tags}/${entityType}?resource=${entityName}`).catch(e => {
|
async queryRelatedEntities (targetEntityType) {
|
||||||
console.error(e)
|
let _targetEntityType = targetEntityType
|
||||||
throw e
|
if (this.data.entityType === entityType.domain && targetEntityType === entityType.domain) {
|
||||||
})
|
_targetEntityType = 'subdomain'
|
||||||
if (response.data && response.status === 200) {
|
}
|
||||||
return response.data.data
|
const url = `${api.entity.entityGraph[`${this.data.entityType}Related${_.upperFirst(_targetEntityType)}`]}?resource=${this.data.entityName}&pageSize=10&pageNo=${this.data.relatedEntities[targetEntityType].pageNo + 1}`
|
||||||
} else {
|
const response = await axios.get(url).catch(e => {
|
||||||
console.error(response)
|
|
||||||
throw response
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async queryRelatedEntitiesCount (entityType, entityName) {
|
|
||||||
const response = await axios.get(`${api.entity.entityGraph.relatedEntityCount}/${entityType}?resource=${entityName}`).catch(e => {
|
|
||||||
console.error(e)
|
console.error(e)
|
||||||
throw e
|
throw e
|
||||||
})
|
})
|
||||||
if (response.data && response.status === 200) {
|
if (response.data && response.status === 200) {
|
||||||
|
this.data.relatedEntities[targetEntityType].pageNo += 1
|
||||||
return response.data.data
|
return response.data.data
|
||||||
} else {
|
} else {
|
||||||
console.error(response)
|
console.error(response)
|
||||||
@@ -143,29 +87,10 @@ export default class Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const nodeType = {
|
export const nodeType = {
|
||||||
rootNode: 'rootNode',
|
rootNode: 'rootNode',
|
||||||
listNode: 'listNode',
|
listNode: 'listNode',
|
||||||
entityNode: 'en-tityNode',
|
entityNode: 'entityNode',
|
||||||
tempNode: 'tempNode'
|
tempNode: 'tempNode'
|
||||||
}
|
}
|
||||||
export async function queryRelatedEntity (node, targetEntityType) {
|
|
||||||
let _targetEntityType = targetEntityType
|
|
||||||
if (node.myData.entityType === 'domain' && targetEntityType === 'domain') {
|
|
||||||
_targetEntityType = 'subdomain'
|
|
||||||
}
|
|
||||||
let url = `${api.entity.entityGraph[`${node.myData.entityType}Related${_.upperFirst(_targetEntityType)}`]}?resource=${node.myData.entityName}&pageSize=10`
|
|
||||||
const current = node.myData.relatedEntities[targetEntityType].loadedCount
|
|
||||||
const pageNo = parseInt(current / 10) + 1
|
|
||||||
url += `&pageNo=${pageNo}`
|
|
||||||
const response = await axios.get(url).catch(e => {
|
|
||||||
console.error(e)
|
|
||||||
throw e
|
|
||||||
})
|
|
||||||
if (response.data && response.status === 200) {
|
|
||||||
return response.data.data
|
|
||||||
} else {
|
|
||||||
console.error(response)
|
|
||||||
throw response
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -21,6 +21,78 @@ export function generateLabel (type, id, data, sourceNode) {
|
|||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function builtTooltip (node) {
|
||||||
|
if (node) {
|
||||||
|
if (node.type === nodeType.listNode) {
|
||||||
|
let iconClass = ''
|
||||||
|
let title = ''
|
||||||
|
let total = 0
|
||||||
|
let loadedCount = 0
|
||||||
|
switch (node.data.entityType) {
|
||||||
|
case 'ip': {
|
||||||
|
iconClass = 'cn-icon cn-icon-resolve-ip'
|
||||||
|
title = i18n.global.t('entities.graph.resolveIp')
|
||||||
|
total = _.get(node.sourceNode.data, 'relatedEntities.ip.total', 0)
|
||||||
|
loadedCount = _.get(node.sourceNode.data, 'relatedEntities.ip.list', []).length
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'domain': {
|
||||||
|
iconClass = 'cn-icon cn-icon-subdomain'
|
||||||
|
title = node.isSubdomain ? i18n.global.t('entities.subdomain') : i18n.global.t('entity.graph.resolveDomain')
|
||||||
|
total = _.get(node.sourceNode.data, 'relatedEntities.domain.total', 0)
|
||||||
|
loadedCount = _.get(node.sourceNode.data, 'relatedEntities.domain.list', []).length
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'app': {
|
||||||
|
iconClass = 'cn-icon cn-icon-app-name'
|
||||||
|
title = i18n.global.t('entities.tab.relatedApp')
|
||||||
|
total = _.get(node.sourceNode.data, 'relatedEntities.app.total', 0)
|
||||||
|
loadedCount = _.get(node.sourceNode.data, 'relatedEntities.app.list', []).length
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return `<div class="primary-node-tooltip">
|
||||||
|
<div class="tooltip__header"><i class="${iconClass}"></i><span class="tooltip__title">${title}</span></div>
|
||||||
|
<div class="tooltip__content">
|
||||||
|
<span>${i18n.global.t('entity.graph.associatedCount')}: ${total}</span>
|
||||||
|
<span>${i18n.global.t('entity.graph.expandedEntityCount')}: ${loadedCount}</span>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
} else if (node.type === nodeType.entityNode || node.type === nodeType.rootNode) {
|
||||||
|
if (node.data && node.data.tags && node.data.tags.length > 0) {
|
||||||
|
return `<div class="entity-node-tooltip">
|
||||||
|
<div class="tooltip__header">
|
||||||
|
<span class="tooltip__title">${node.id}</span>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip__content">
|
||||||
|
<div class="content-header">
|
||||||
|
<div class="header-icon"></div>
|
||||||
|
<span>${i18n.global.t('entity.graph.tags')}</span>
|
||||||
|
</div>
|
||||||
|
<div class="content-tag-list">${this.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.type === nodeType.tempNode) {
|
||||||
|
return `<div style="padding: 0 6px; font-size: 15px; line-height: 15px; color: #111;">${node.id}</div>`
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
function getLabelText (data, sourceNode) {
|
function getLabelText (data, sourceNode) {
|
||||||
if (data.entityType === entityType.ip) {
|
if (data.entityType === entityType.ip) {
|
||||||
if (sourceNode.data.entityType === entityType.app) {
|
if (sourceNode.data.entityType === entityType.app) {
|
||||||
|
|||||||
Reference in New Issue
Block a user