Merge branch 'dev-force-graph' into 'dev'

feat: 关系图改版分支合并

See merge request cyber-narrator/cn-ui!83
This commit is contained in:
陈劲松
2024-06-27 01:47:30 +00:00
21 changed files with 3291 additions and 1430 deletions

View File

@@ -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 {
position: fixed;
top: 100px;
@@ -18,13 +15,12 @@ $color-regular: var(--el-text-color-regular);
cursor: pointer;
i {
color: $color-regular;
color: #575757;
font-size: 18px;
}
&.toolbar--unactivated {
cursor: default;
cursor: not-allowed;
i {
opacity: .4;
}
@@ -32,24 +28,137 @@ $color-regular: var(--el-text-color-regular);
}
}
}
.entity-graph {
display: flex;
background-color:'orange';
border: 'solid 5px red';
.entity-graph__chart {
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 {
display: flex;
flex-direction: column;
align-items: center;
background-color: transparent !important;
transition: linear all var(--el-transition-duration-fast);
transition: linear all .2s;
.graph-node__text {
width: 120px;
font-size: 12px;
color: $color-primary;
color: #353636;
}
&.graph-node--root {
@@ -62,38 +171,30 @@ $color-regular: var(--el-text-color-regular);
box-shadow: none;
animation: none;
}
&.graph-node--ip {
border-color: var(--el-color-success-light-5) !important;
box-shadow: 0 0 0 8px rgba(126, 159, 84, 0.14);
border-color: #CBD9BB !important;
box-shadow: 0 0 0 8px rgba(126,159,84,0.14);
i {
color: var(--el-color-success);
color: #7E9F54;
}
}
&.graph-node--domain {
border-color: var(--el-color-primary-light-7) !important;
box-shadow: 0 0 0 8px rgba(56, 172, 210, 0.14);
border-color: #AFDEED !important;
box-shadow: 0 0 0 8px rgba(56,172,210,0.14);
i {
color: $color-business;
color: #38ACD2;
}
}
&.graph-node--app {
border-color: var(--el-color-warning-light-5) !important;
box-shadow: 0 0 0 8px rgba(229, 162, 25, 0.14);
border-color: #F5DAA3 !important;
box-shadow: 0 0 0 8px rgba(229,162,25,0.14);
i {
color: var(--el-color-warning);
color: #E5A219;
}
}
i {
font-size: 36px;
}
.graph-node__text {
padding-top: 30px;
}
@@ -103,21 +204,19 @@ $color-regular: var(--el-text-color-regular);
padding-top: 20px;
width: 66px;
height: 66px;
border: 1px solid #A7B0B9 !important; // 该class并未使用到
border: 1px solid #A7B0B9 !important;
box-shadow: none;
// 覆盖自带的node点击效果
&.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;
border-color: $color-regular !important;
border-color: #778391 !important;
}
i {
font-size: 24px;
color: $color-regular;
color: #778391;
}
.graph-node__text {
padding-top: 24px;
}
@@ -131,22 +230,19 @@ $color-regular: var(--el-text-color-regular);
&.graph-node--ip {
i {
color: var(--el-color-success);
color: #7E9F54;
}
}
&.graph-node--domain {
i {
color: $color-business;
color: #38ACD2;
}
}
&.graph-node--app {
i {
color: var(--el-color-warning);
color: #E5A219;
}
}
i {
font-size: 21px;
}
@@ -159,7 +255,6 @@ $color-regular: var(--el-text-color-regular);
left: unset !important;
right: 0 !important;
}
.entity-graph__detail {
height: calc(100% - 100px) !important;
top: 100px !important;
@@ -170,7 +265,7 @@ $color-regular: var(--el-text-color-regular);
}
.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 {
padding: 5px;
@@ -180,21 +275,19 @@ $color-regular: var(--el-text-color-regular);
align-items: center;
i {
color: $color-regular;
color: #717171;
font-size: 14px;
}
.tooltip__title {
padding-left: 6px;
font-size: 15px;
line-height: 15px;
color: $color-primary;
color: #111;
}
}
.tooltip__content {
padding-top: 10px;
color: $color-primary;
color: #222;
font-size: 12px;
span:first-of-type {
@@ -202,7 +295,6 @@ $color-regular: var(--el-text-color-regular);
}
}
}
.entity-node-tooltip {
width: 300px;
padding: 5px;
@@ -215,7 +307,7 @@ $color-regular: var(--el-text-color-regular);
.tooltip__title {
font-size: 15px;
line-height: 15px;
color: $color-primary;
color: #111;
}
}
@@ -230,12 +322,11 @@ $color-regular: var(--el-text-color-regular);
.header-icon {
width: 3px !important;
height: 12px !important;
background: $color-business;
background: #38ACD2;
border-radius: 1px;
margin-right: 6px;
}
}
.content-tag-list {
display: flex;
align-items: flex-start;
@@ -249,25 +340,22 @@ $color-regular: var(--el-text-color-regular);
border: 1px solid;
border-radius: 2px;
$normal-color: $color-regular;
$normal-light-color: var(--el-fill-color-light);
$negative-color: var(--el-color-danger);
$negative-light-color: var(--el-color-danger-light-9);
$positive-color: var(--el-color-success);
$positive-light-color: var(--el-color-success-light-9);
$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;

View File

@@ -19,7 +19,7 @@ $color-regular: var(--el-text-color-regular);
.graph-list-header-icon {
font-size: 21px;
color: $color-regular;
color: #778391;
margin-right: 9px;
}
}
@@ -50,13 +50,10 @@ $color-regular: var(--el-text-color-regular);
border-radius: 3px;
font-size: 12px;
color: var(--el-color-white);
display: flex;
align-items: center;
justify-content: center;
font-weight: 500;
padding: 14px 10px;
padding: 8px 10px;
cursor: pointer;
border: 1px solid rgba(46, 136, 166, 0.85);
border: 1px solid var(--el-color-business);
text-align: center;
i {
font-size: 16px;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@
<div class="entity-graph-type">{{entityTypeName}}</div>
<div class="graph-basic-info">
<div class="graph-basic-info-name__block">
<div class="graph-basic-info-name" :title="$_.get(node, 'id', '')" id="entityName">{{ $_.get(node, 'id', '') }}</div>
<div class="graph-basic-info-name" :title="$_.get(node, 'realId', '')" id="entityName">{{ $_.get(node, 'realId', '') }}</div>
<div class="graph-basic-info-icon" @click="copyEntityName">
<i class="cn-icon cn-icon-copy"></i>
</div>
@@ -75,7 +75,7 @@
</div>
<!--标签-->
<div v-if="$_.get(node, 'myData.tags', []).length > 0" class="digital-certificate graph-basic-info__block">
<div v-if="$_.get(node, 'data.tags', []).length > 0" class="digital-certificate graph-basic-info__block">
<div class="digital-certificate-header padding-b-10">
<div class="digital-certificate-header__icon graph-header__icon"></div>
<div class="graph-basic-info__block-title">
@@ -85,7 +85,7 @@
<div class="entity-detail graph-basic-info__block-content">
<div class="graph-tag-list">
<div v-for="ic in $_.get(node, 'myData.tags', [])" :key="ic.value">
<div v-for="ic in $_.get(node, 'data.tags', [])" :key="ic.value">
<div class="entity-tag graph-tag-item" :class="`entity-tag--level-two-${ic.type}`" :style="getTagColor(ic.color)">
<span>{{ic.value}}</span>
</div>
@@ -126,13 +126,13 @@ export default {
computed: {
iconClass () {
let className
switch (_.get(this.node, 'myData.entityType', '')) {
switch (_.get(this.node, 'data.entityType', '')) {
case ('ip'): {
className = 'cn-icon cn-icon-ip2'
className = 'cn-icon cn-icon-resolve-ip'
break
}
case ('domain'): {
className = 'cn-icon cn-icon-domain2'
className = 'cn-icon cn-icon-subdomain'
break
}
case ('app'): {
@@ -145,7 +145,7 @@ export default {
return className
},
entityTypeName () {
const type = _.get(this.node, 'myData.entityType', '')
const type = _.get(this.node, 'data.entityType', '')
let entityTypeName = '-'
switch (type) {
case ('ip'): {
@@ -270,59 +270,59 @@ export default {
},
handleDetailData (node) {
const n = node
const type = _.get(n, 'myData.entityType', '')
const type = _.get(n, 'data.entityType', '')
switch (type) {
case 'ip': {
this.detailCards = [
{ name: 'asn', label: this.$t('entities.asNumber'), value: _.get(n, 'myData.basicInfo.asn.asn', '-') },
{ name: 'asn', label: this.$t('entities.asNumber'), value: _.get(n, 'data.basicInfo.asn.asn', '-') },
{
name: 'asOrg',
label: this.$t('entities.asOrg'),
value: _.get(n.myData, 'basicInfo.asn.organization', '-')
value: _.get(n.data, 'basicInfo.asn.organization', '-')
},
{
name: 'isp',
label: this.$t('entities.graph.isp'),
value: _.get(n.myData, 'basicInfo.location.isp', '-')
value: _.get(n.data, 'basicInfo.location.isp', '-')
},
{ name: 'location', label: this.$t('overall.location'), value: this.location(n.myData) }
{ name: 'location', label: this.$t('overall.location'), value: this.location(n.data) }
]
this.relationList = [
{
icon: 'cn-icon cn-icon-subdomain',
name: 'domain',
label: this.$t('entity.graph.resolveDomain'),
value: _.get(n.myData, 'relatedEntity.domain.list', []).length,
total: _.get(n.myData, 'relatedEntity.domain.total', 0) || 0
value: _.get(n.data, 'relatedEntities.domain.list', []).length,
total: _.get(n.data, 'relatedEntities.domain.total', 0) || 0
},
{
icon: 'cn-icon cn-icon-app-name',
name: 'app',
label: this.$t('entities.tab.relatedApp'),
value: _.get(n.myData, 'relatedEntity.app.list', []).length,
total: _.get(n.myData, 'relatedEntity.app.total', '0') || 0
value: _.get(n.data, 'relatedEntities.app.list', []).length,
total: _.get(n.data, 'relatedEntities.app.total', '0') || 0
}
]
break
}
case 'domain': {
const expireDate = _.get(n.myData, 'basicInfo.whois.expireDate', '')
const createDate = _.get(n.myData, 'basicInfo.whois.createDate', '')
const expireDate = _.get(n.data, 'basicInfo.whois.expireDate', '')
const createDate = _.get(n.data, 'basicInfo.whois.createDate', '')
this.detailCards = [
{
name: 'categoryName',
label: this.$t('entities.category'),
value: _.get(n.myData, 'basicInfo.category.categoryName', '-')
value: _.get(n.data, 'basicInfo.category.categoryName', '-')
},
{
name: 'categoryGroup',
label: this.$t('entities.group'),
value: _.get(n.myData, 'basicInfo.category.categoryGroup', '-')
value: _.get(n.data, 'basicInfo.category.categoryGroup', '-')
},
{
name: 'reputationLevel',
label: this.$t('entities.creditLevel2'),
value: _.get(n.myData, 'basicInfo.category.reputationLevel', '-')
value: _.get(n.data, 'basicInfo.category.reputationLevel', '-')
},
{
name: 'expireDate',
@@ -332,17 +332,17 @@ export default {
{
name: 'registrarName',
label: this.$t('entities.registrar'),
value: _.get(n.myData, 'basicInfo.whois.registrarName', '-')
value: _.get(n.data, 'basicInfo.whois.registrarName', '-')
},
{
name: 'registrantOrg',
label: this.$t('entities.registry'),
value: _.get(n.myData, 'basicInfo.whois.registrantOrg', '-')
value: _.get(n.data, 'basicInfo.whois.registrantOrg', '-')
},
{
name: 'registrantCountry',
label: this.$t('entities.registrationCountry'),
value: _.get(n.myData, 'basicInfo.whois.registrantCountry', '-')
value: _.get(n.data, 'basicInfo.whois.registrantCountry', '-')
},
{
name: 'createDate',
@@ -352,7 +352,7 @@ export default {
{
name: 'email',
label: this.$t('entities.registryEmail'),
value: _.get(n.myData, 'basicInfo.whois.email', '-')
value: _.get(n.data, 'basicInfo.whois.email', '-')
}
]
this.relationList = [
@@ -360,22 +360,22 @@ export default {
icon: 'cn-icon cn-icon-resolve-ip',
name: 'ip',
label: this.$t('entities.graph.resolveIp'),
value: _.get(n.myData, 'relatedEntity.ip.list', []).length,
total: _.get(n.myData, 'relatedEntity.ip.total', '0') || 0
value: _.get(n.data, 'relatedEntities.ip.list', []).length,
total: _.get(n.data, 'relatedEntities.ip.total', '0') || 0
},
{
icon: 'cn-icon cn-icon-subdomain',
name: 'domain',
label: this.$t('entities.subdomain'),
value: _.get(n.myData, 'relatedEntity.domain.list', []).length,
total: _.get(n.myData, 'relatedEntity.domain.total', '0') || 0
value: _.get(n.data, 'relatedEntities.domain.list', []).length,
total: _.get(n.data, 'relatedEntities.domain.total', '0') || 0
},
{
icon: 'cn-icon cn-icon-app-name',
name: 'app',
label: this.$t('entities.tab.relatedApp'),
value: _.get(n.myData, 'relatedEntity.app.list', []).length,
total: _.get(n.myData, 'relatedEntity.app.total', 0) || 0
value: _.get(n.data, 'relatedEntities.app.list', []).length,
total: _.get(n.data, 'relatedEntities.app.total', 0) || 0
}
]
break
@@ -385,32 +385,32 @@ export default {
{
name: 'appCategory',
label: this.$t('entities.category'),
value: _.get(n.myData, 'basicInfo.category.appCategory', '-')
value: _.get(n.data, 'basicInfo.category.appCategory', '-')
},
{
name: 'appSubcategory',
label: this.$t('entities.subcategory'),
value: _.get(n.myData, 'basicInfo.category.appSubcategory', '-')
value: _.get(n.data, 'basicInfo.category.appSubcategory', '-')
},
{
name: 'appRisk',
label: this.$t('entities.riskLevel'),
value: this.appRisk(_.get(n.myData, 'basicInfo.category.appRisk', '-'))
value: this.appRisk(_.get(n.data, 'basicInfo.category.appRisk', '-'))
},
{
name: 'appTechnology',
label: this.$t('overall.technology'),
value: _.get(n.myData, 'basicInfo.category.appTechnology', '-')
value: _.get(n.data, 'basicInfo.category.appTechnology', '-')
},
{
name: 'appLongname',
label: this.$t('overall.appFullName'),
value: _.get(n.myData, 'basicInfo.category.appLongname', '-')
value: _.get(n.data, 'basicInfo.category.appLongname', '-')
},
{
name: 'appDescription',
label: this.$t('config.dataSource.description'),
value: _.get(n.myData, 'basicInfo.category.appDescription', '-')
value: _.get(n.data, 'basicInfo.category.appDescription', '-')
}
]
@@ -419,15 +419,15 @@ export default {
icon: 'cn-icon cn-icon-resolve-ip',
name: 'ip',
label: this.$t('entities.tab.relatedIp'),
value: _.get(n.myData, 'relatedEntity.ip.list', []).length,
total: _.get(n.myData, 'relatedEntity.ip.total', '0') || 0
value: _.get(n.data, 'relatedEntities.ip.list', []).length,
total: _.get(n.data, 'relatedEntities.ip.total', '0') || 0
},
{
icon: 'cn-icon cn-icon-subdomain',
name: 'domain',
label: this.$t('entities.graph.relatedDomain'),
value: _.get(n.myData, 'relatedEntity.domain.list', []).length,
total: _.get(n.myData, 'relatedEntity.domain.total', '0') || 0
value: _.get(n.data, 'relatedEntities.domain.list', []).length,
total: _.get(n.data, 'relatedEntities.domain.total', '0') || 0
}
]
}

View File

@@ -4,11 +4,11 @@
<div class="graph-list-header">
<div>
<div class="graph-list-header-title">
<i class="cn-icon cn-icon-resolve-ip graph-list-header-icon"></i>
<i :class="`${iconClass} graph-list-header-icon`"></i>
<span>{{ title }}</span>
</div>
<div class="graph-list-header-number">
{{ $t('entity.graph.associatedCount') }}:&nbsp;<span>{{$_.get(node, 'sourceNode.myData.relatedEntity.' + $_.get(node, 'myData.entityType') + '.total', '-')}}</span>
{{ $t('entity.graph.associatedCount') }}:&nbsp;<span>{{$_.get(node, 'sourceNode.data.relatedEntities.' + $_.get(node, 'data.entityType') + '.total', '-')}}</span>
</div>
</div>
@@ -16,10 +16,10 @@
</div>
<div class="graph-list-expand-btn-block">
<div class="graph-list-expand-btn graph-list-expand-btn__display" :class="{ 'graph-list-expand-btn--disabled': expandBtnDisable }" @click="expandList">
<span class="graph-list-expand-btn" :class="{ 'graph-list-expand-btn--disabled': expandBtnDisable }" @click="expandList">
<i class="cn-icon cn-icon-expand-continue graph-expand-continue"></i>
{{ $t('entity.graph.continueToExpand') }}
</div>
</span>
</div>
<div>
@@ -28,14 +28,14 @@
<div class="digital-certificate-header__icon graph-header__icon"></div>
<div class="graph-list-content-header ">
{{ $t('entity.graph.expandedEntityCount') }}:&nbsp
<span>{{$_.get(node, 'sourceNode.myData.relatedEntity.' + $_.get(node, 'myData.entityType') + '.list', []).length}}</span>
<span>{{$_.get(node, 'sourceNode.data.relatedEntities.' + $_.get(node, 'data.entityType') + '.list', []).length}}</span>
</div>
</div>
<div class="graph-list-content">
<div v-for="(item, index) in $_.get(node, 'sourceNode.myData.relatedEntity.' + $_.get(node, 'myData.entityType') + '.list', [])" :key="index">
<div v-for="(item, index) in $_.get(node, 'sourceNode.data.relatedEntities.' + $_.get(node, 'data.entityType') + '.list', [])" :key="index">
<div class="graph-list-item-ip"><span @click="expandDetail">{{ item.vertex }}</span></div>
<template v-if="$_.get(node, 'myData.entityType', '') === 'ip'">
<template v-if="$_.get(node, 'data.entityType', '') === 'ip'">
<div class="graph-list-item-block">
<div class="graph-list-item padding-b-4">
<div class="graph-list-item-label">{{ $t('overall.location') }}:</div>
@@ -54,7 +54,7 @@
</div>
</div>
</template>
<template v-else-if="$_.get(node, 'myData.entityType', '') === 'domain'">
<template v-else-if="$_.get(node, 'data.entityType', '') === 'domain'">
<div class="graph-list-item-block">
<div class="graph-list-item__app">
<div class="graph-list-item-label__app">{{$t('entities.category')}}:</div>
@@ -74,7 +74,7 @@
</div>
</div>
</template>
<template v-else-if="$_.get(node, 'myData.entityType', '') === 'app'">
<template v-else-if="$_.get(node, 'data.entityType', '') === 'app'">
<div class="graph-list-item-block">
<div class="graph-list-item__app">
<div class="graph-list-item-label__app">APP ID:</div>
@@ -98,7 +98,7 @@
</div>
</div>
</template>
<div class="graph-list-dividing-line" v-if="index !== $_.get(node, 'sourceNode.myData.relatedEntity.' + $_.get(node, 'myData.entityType') + '.list', []).length - 1"></div>
<div class="graph-list-dividing-line" v-if="index !== $_.get(node, 'sourceNode.data.relatedEntities.' + $_.get(node, 'data.entityType') + '.list', []).length - 1"></div>
</div>
</div>
</div>
@@ -132,8 +132,8 @@ export default {
},
computed: {
expandBtnDisable () {
const loaded = _.get(this.node, 'sourceNode.myData.relatedEntity.' + _.get(this.node, 'myData.entityType') + '.list', []).length
const total = _.get(this.node, 'sourceNode.myData.relatedEntity.' + _.get(this.node, 'myData.entityType') + '.total', 0)
const loaded = _.get(this.node, 'sourceNode.data.relatedEntities.' + _.get(this.node, 'data.entityType') + '.list', []).length
const total = _.get(this.node, 'sourceNode.data.relatedEntities.' + _.get(this.node, 'data.entityType') + '.total', 0)
return !(loaded < total && total > 10 && (loaded && loaded < 50))
},
appRisk () {
@@ -147,9 +147,9 @@ export default {
title () {
let title = ''
if (this.node) {
const entityType = _.get(this.node, 'myData.entityType', '')
const entityType = _.get(this.node, 'data.entityType', '')
if (entityType) {
const sourceType = _.get(this.node, 'sourceNode.myData.entityType', '')
const sourceType = _.get(this.node, 'sourceNode.data.entityType', '')
switch (entityType) {
case 'ip': {
if (sourceType === 'domain') {
@@ -177,6 +177,26 @@ export default {
}
}
return title
},
iconClass () {
let className
switch (_.get(this.node, 'data.entityType', '')) {
case ('ip'): {
className = 'cn-icon cn-icon-resolve-ip'
break
}
case ('domain'): {
className = 'cn-icon cn-icon-subdomain'
break
}
case ('app'): {
className = 'cn-icon cn-icon-app2'
break
}
default:
break
}
return className
}
},
methods: {

View File

@@ -1,52 +0,0 @@
import G6 from '@antv/g6'
export default class Edge {
constructor (sourceNode, targetNode, type) {
this.id = sourceNode.id + '__' + targetNode.id
this.source = sourceNode.id
this.target = targetNode.id
this.isTemp = type === 'temp'
this.style = type === 'temp' ? tempStyles.style : normalStyles.style
this.stateStyles = type === 'temp' ? tempStyles.stateStyles : normalStyles.stateStyles
}
}
const normalStyles = {
style: {
stroke: '#BEBEBE',
endArrow: {
path: G6.Arrow.triangle(5, 5),
fill: '#BEBEBE'
}
},
stateStyles: {
mySelected: {
stroke: '#778391',
endArrow: {
path: G6.Arrow.triangle(5, 5),
fill: '#778391'
}
}
}
}
const tempStyles = {
style: {
endArrow: {
path: G6.Arrow.triangle(5, 5),
fill: '#DDD'
},
stroke: '#DDD',
lineDash: [3, 2]
},
stateStyles: {
mySelected: {
stroke: '#DDD',
endArrow: {
path: G6.Arrow.triangle(5, 5),
fill: '#DDD'
},
lineDash: [3, 2]
}
}
}

View File

@@ -0,0 +1,29 @@
import { nodeType } from './node'
export default class Link {
constructor (sourceNode, targetNode, type) {
this.source = sourceNode.id
this.target = targetNode.id
this.type = type || linkType.normal
this.strength = strengthHandler(sourceNode, targetNode)
}
}
function strengthHandler (sourceNode, targetNode) {
if (sourceNode.type === nodeType.rootNode) {
return 0.5
} else {
return 1
}
}
export const linkType = {
normal: 'normal',
temp: 'temp'
}
export const linkDistance = {
root: 180,
entityToList: 120,
normal: 90
}

View File

@@ -1,132 +1,126 @@
import _ from 'lodash'
import i18n from '@/i18n'
import axios from 'axios'
import { api } from '@/utils/api'
import { entityDefaultColor, intentColor } from '@/utils/constants'
import { entityDefaultColor, entityType } from '@/utils/constants'
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 {
/*
* type: 对应nodeType
* cfg: { entityType, entityName, x, y, fx, fy }
* cfg: { entityType, entityName }
* */
constructor (type, id, cfg, sourceNode) {
constructor (type, id, data, sourceNode, img) {
this.id = id + data.entityType
this.realId = id
this.type = type
this.id = id
this.x = _.get(cfg, 'x', null)
this.y = _.get(cfg, 'y', null)
this.fx = _.get(cfg, 'fx', null)
this.fy = _.get(cfg, 'fy', null)
this.myData = {
entityType: cfg.entityType,
entityName: cfg.entityName
this.vx = 0
this.vy = 0
this.x = data.x || null
this.y = data.y || null
this.fx = data.fx || (sourceNode ? null : 0) // 设置为0即可固定中心节点。0为中心节点1为中心节点的子节点2为第三级节点
this.fy = data.fy || (sourceNode ? null : 0) // 设置为0即可固定中心节点。0为中心节点1为中心节点的子节点2为第三级节点
this.preDragX = null
this.preDragY = null
this.img = img
this.sourceNode = sourceNode
this.isSubdomain = sourceNode ? sourceNode.data.entityType === entityType.domain && data.entityType === entityType.domain : false
this.data = {
entityType: data.entityType,
entityName: data.entityName
}
if (sourceNode) {
this.sourceNode = sourceNode
}
this.label = this.generateLabel()
}
this.label = generateLabel(type, id, data, sourceNode)
this.name = builtTooltip(this)
generateLabel () {
switch (this.type) {
// val 值决定鼠标触发 hover 的半径
switch (type) {
case nodeType.rootNode:
case nodeType.entityNode: {
return this.id
}
case nodeType.listNode: {
return `${this.getLabelText()}(${_.get(this.sourceNode.myData, 'relatedEntity.' + this.myData.entityType + '.total', 0)})`
}
case nodeType.tempNode: {
return this.getLabelText()
}
}
return ''
}
getLabelText () {
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 ''
}
isSubdomain () {
if (this.sourceNode) {
return this.sourceNode.myData.entityType === 'domain' && this.myData.entityType === 'domain'
} else {
return false
this.val = 19
break
case nodeType.listNode:
this.val = 16
break
case nodeType.entityNode:
case nodeType.tempNode:
this.val = 10
break
}
}
// 查询basicInfo、tags、关联实体数量
// listNode和entityNode专用查询实体信息
async queryDetailData () {
const entityType = this.myData.entityType
const entityName = this.myData.entityName
const entityType = this.data.entityType
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 = []
formatTags(tags, entityType, _tags)
if (_.isArray(tags.tags)) {
_tags = _.concat(_tags, tags.tags.map(tag => ({ value: tag.name, color: intentColor[tag.intent] || entityDefaultColor })))
if (_.isArray(tags.userDefinedTags)) {
_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.queryRelatedEntityCount(entityType, entityName)
this.myData.relatedEntity = {
ip: { total: relatedEntityTotalCount.ipCount, list: [] },
domain: { total: this.myData.entityType === 'domain' ? relatedEntityTotalCount.subDomainCount : relatedEntityTotalCount.domainCount, list: [] },
app: { total: relatedEntityTotalCount.appCount, list: [] }
const relatedEntityTotalCount = await queryRelatedEntityCount(entityType, entityName)
this.data.relatedEntities = {
ip: { total: relatedEntityTotalCount.ipCount, pageNo: 0, list: [] }, //
domain: { total: this.data.entityType === 'domain' ? relatedEntityTotalCount.subDomainCount : relatedEntityTotalCount.domainCount, pageNo: 0, list: [] }, // pageNo: 0,
app: { total: relatedEntityTotalCount.appCount, pageNo: 0, list: [] }// pageNo: 0,
}
}
async queryEntityBasicInfo (entityType, entityName) {
const response = await axios.get(`${api.entity.entityGraph.basicInfo}/${entityType}?resource=${entityName}`).catch(e => {
console.error(e)
throw e
})
if (response.data && response.status === 200) {
return response.data.data
} else {
console.error(response)
throw response
}
}
async queryTags (entityType, entityName) {
const response = await axios.get(`${api.entity.entityGraph.tags}/${entityType}?resource=${entityName}`).catch(e => {
console.error(e)
throw e
})
if (response.data && response.status === 200) {
return response.data.data
} else {
console.error(response)
throw response
}
}
async queryRelatedEntityCount (entityType, entityName) {
const response = await axios.get(`${api.entity.entityGraph.relatedEntityCount}/${entityType}?resource=${entityName}`).catch(e => {
getNeighbors (gData) {
const { links } = gData
const neighboringNodes = []
const neighboringTargetNodes = []
const neighboringSourceNodes = []
const neighboringTargetLinks = []
const neighboringSourceLinks = []
const neighboringLinks = links.filter(l => {
if (l.target.id === this.id || l.source.id === this.id) {
neighboringNodes.push(l.target.id === this.id ? l.source : l.target)
if (l.target.id === this.id) {
neighboringSourceNodes.push(l.source)
neighboringSourceLinks.push(l)
} else {
neighboringTargetNodes.push(l.target)
neighboringTargetLinks.push(l)
}
return true
}
return false
})
return {
nodes: neighboringNodes,
targetNodes: neighboringTargetNodes,
sourceNodes: neighboringSourceNodes,
links: neighboringLinks,
targetLinks: neighboringTargetLinks,
sourceLinks: neighboringSourceLinks
}
}
// 获取唯一sourcelistNode和tempNode专用因为entityNode的source可能有多个rootNode无source
getSourceNode (gData) {
const links = gData.links.filter(l => l.target.id === this.id)
const nodes = gData.nodes.filter(n => links.some(l => l.source.id === n.id))
return nodes.length > 0 ? nodes[0] : null
}
// listNode和entityNode专用查询关联实体列表
async queryRelatedEntities (targetEntityType) {
let _targetEntityType = targetEntityType
if (this.data.entityType === entityType.domain && targetEntityType === entityType.domain) {
_targetEntityType = 'subdomain'
}
const url = `${api.entity.entityGraph[`${this.data.entityType}Related${_.upperFirst(_targetEntityType)}`]}?resource=${this.data.entityName}&pageSize=10&pageNo=${this.data.relatedEntities[targetEntityType].pageNo + 1}`
const response = await axios.get(url).catch(e => {
console.error(e)
throw e
})
if (response.data && response.status === 200) {
this.data.relatedEntities[targetEntityType].pageNo += 1
return response.data.data
} else {
console.error(response)
@@ -134,29 +128,10 @@ export default class Node {
}
}
}
export const nodeType = {
rootNode: 'rootNode',
listNode: 'listNode',
entityNode: 'entityNode',
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.relatedEntity[targetEntityType].list.length
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
}
}

View File

@@ -0,0 +1,162 @@
import _ from 'lodash'
import i18n from '@/i18n'
import axios from 'axios'
import { api } from '@/utils/api'
import { entityDefaultColor } from '@/utils/constants'
import { formatTags } from '@/utils/tools'
export default class Node {
/*
* type: 对应nodeType
* cfg: { entityType, entityName, x, y, fx, fy }
* */
constructor (type, id, cfg, sourceNode) {
this.type = type
this.id = id
this.x = _.get(cfg, 'x', null)
this.y = _.get(cfg, 'y', null)
this.fx = _.get(cfg, 'fx', null)
this.fy = _.get(cfg, 'fy', null)
this.myData = {
entityType: cfg.entityType,
entityName: cfg.entityName
}
if (sourceNode) {
this.sourceNode = sourceNode
}
this.label = this.generateLabel()
}
generateLabel () {
switch (this.type) {
case nodeType.rootNode:
case nodeType.entityNode: {
return this.id
}
case nodeType.listNode: {
return `${this.getLabelText()}(${_.get(this.sourceNode.myData, 'relatedEntity.' + this.myData.entityType + '.total', 0)})`
}
case nodeType.tempNode: {
return this.getLabelText()
}
}
return ''
}
getLabelText () {
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 ''
}
isSubdomain () {
if (this.sourceNode) {
return this.sourceNode.myData.entityType === 'domain' && this.myData.entityType === 'domain'
} else {
return false
}
}
// 查询basicInfo、tags、关联实体数量
async queryDetailData () {
const entityType = this.myData.entityType
const entityName = this.myData.entityName
this.myData.basicInfo = await this.queryEntityBasicInfo(entityType, entityName)
const tags = await this.queryTags(entityType, entityName)
let _tags = []
formatTags(tags, entityType, _tags)
if (_.isArray(tags.userDefinedTags)) {
_tags = _.concat(_tags, tags.userDefinedTags.map(tag => ({ value: tag.tagValue, color: tag.knowledgeBase ? tag.knowledgeBase.color : entityDefaultColor })))
}
this.myData.tags = _tags
const relatedEntityTotalCount = await this.queryRelatedEntityCount(entityType, entityName)
this.myData.relatedEntity = {
ip: { total: relatedEntityTotalCount.ipCount, list: [] },
domain: { total: this.myData.entityType === 'domain' ? relatedEntityTotalCount.subDomainCount : relatedEntityTotalCount.domainCount, list: [] },
app: { total: relatedEntityTotalCount.appCount, list: [] }
}
}
async queryEntityBasicInfo (entityType, entityName) {
const response = await axios.get(`${api.entity.entityGraph.basicInfo}/${entityType}?resource=${entityName}`).catch(e => {
console.error(e)
throw e
})
if (response.data && response.status === 200) {
return response.data.data
} else {
console.error(response)
throw response
}
}
async queryTags (entityType, entityName) {
const response = await axios.get(`${api.entity.entityGraph.tags}/${entityType}?resource=${entityName}`).catch(e => {
console.error(e)
throw e
})
if (response.data && response.status === 200) {
return response.data.data
} else {
console.error(response)
throw response
}
}
async queryRelatedEntityCount (entityType, entityName) {
const response = await axios.get(`${api.entity.entityGraph.relatedEntityCount}/${entityType}?resource=${entityName}`).catch(e => {
console.error(e)
throw e
})
if (response.data && response.status === 200) {
return response.data.data
} else {
console.error(response)
throw response
}
}
}
export const nodeType = {
rootNode: 'rootNode',
listNode: 'listNode',
entityNode: 'entityNode',
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.relatedEntity[targetEntityType].list.length
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
}
}

View File

@@ -0,0 +1,151 @@
import _ from 'lodash'
import { nodeType } from '@/views/entityExplorer/entityGraph/node'
import i18n from '@/i18n'
import { entityType } from '@/utils/constants'
import axios from 'axios'
import { api } from '@/utils/api'
export function generateLabel (type, id, data, sourceNode) {
switch (type) {
case nodeType.rootNode:
case nodeType.entityNode: {
return id
}
case nodeType.listNode: {
return `${getLabelText(data, sourceNode)}(${_.get(sourceNode.data, 'relatedEntities.' + data.entityType + '.total', 0)})`
}
case nodeType.tempNode: {
return getLabelText(data, sourceNode)
}
}
return ''
}
export function builtTooltip (node) {
if (node && node.type !== nodeType.tempNode) {
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')}:&nbsp;${total}</span>
<span>${i18n.global.t('entity.graph.expandedEntityCount')}:&nbsp;${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.realId}</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.realId}</div>`
}
}
}
}
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) {
if (data.entityType === entityType.ip) {
if (sourceNode.data.entityType === entityType.app) {
return i18n.global.t('entities.tab.relatedIp')
} else if (sourceNode.data.entityType === entityType.domain) {
return i18n.global.t('entities.graph.resolveIp')
}
} else if (data.entityType === entityType.domain) {
if (sourceNode.data.entityType === entityType.ip) {
return i18n.global.t('entities.graph.resolvedDomain')
} else if (sourceNode.data.entityType === entityType.app) {
return i18n.global.t('entities.graph.relatedDomain')
} else if (sourceNode.data.entityType === entityType.domain) {
return i18n.global.t('entities.subdomain')
}
} else if (data.entityType === entityType.app) {
return i18n.global.t('entities.tab.relatedApp')
}
return ''
}
export async function queryEntityBasicInfo (entityType, entityName) {
const response = await axios.get(`${api.entity.entityGraph.basicInfo}/${entityType}?resource=${entityName}`).catch(e => {
console.error(e)
throw e
})
if (response.data && response.status === 200) {
return response.data.data
} else {
console.error(response)
throw response
}
}
export async function queryTags (entityType, entityName) {
const response = await axios.get(`${api.entity.entityGraph.tags}/${entityType}?resource=${entityName}`).catch(e => {
console.error(e)
throw e
})
if (response.data && response.status === 200) {
return response.data.data
} else {
console.error(response)
throw response
}
}
export async function queryRelatedEntityCount (entityType, entityName) {
const response = await axios.get(`${api.entity.entityGraph.relatedEntityCount}/${entityType}?resource=${entityName}`).catch(e => {
console.error(e)
throw e
})
if (response.data && response.status === 200) {
return response.data.data
} else {
console.error(response)
throw response
}
}

View File

@@ -0,0 +1,7 @@
export default {
nodes: [
{
}
]
}