Merge branch 'dev-force-graph' into 'dev'
feat: 关系图改版分支合并 See merge request cyber-narrator/cn-ui!83
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 {
|
||||
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;
|
||||
|
||||
@@ -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
1146
src/views/entityExplorer/EntityGraph2.vue
Normal file
1146
src/views/entityExplorer/EntityGraph2.vue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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') }}: <span>{{$_.get(node, 'sourceNode.myData.relatedEntity.' + $_.get(node, 'myData.entityType') + '.total', '-')}}</span>
|
||||
{{ $t('entity.graph.associatedCount') }}: <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') }}: 
|
||||
<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: {
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/views/entityExplorer/entityGraph/link.js
Normal file
29
src/views/entityExplorer/entityGraph/link.js
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
// 获取唯一source,listNode和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
|
||||
}
|
||||
}
|
||||
|
||||
162
src/views/entityExplorer/entityGraph/node2.js
Normal file
162
src/views/entityExplorer/entityGraph/node2.js
Normal 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
|
||||
}
|
||||
}
|
||||
151
src/views/entityExplorer/entityGraph/utils.js
Normal file
151
src/views/entityExplorer/entityGraph/utils.js
Normal 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')}: ${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.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
|
||||
}
|
||||
}
|
||||
7
src/views/entityExplorer/testData2.js
Normal file
7
src/views/entityExplorer/testData2.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export default {
|
||||
nodes: [
|
||||
{
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user