CN-240 feat: 实体详情
This commit is contained in:
@@ -89,14 +89,17 @@
|
||||
.cn-panel-crypto {
|
||||
grid-template-columns: repeat(36, 1fr) !important;
|
||||
}
|
||||
|
||||
.cn-panel, .cn-panel>.cn-chart__tabs>.el-tabs__content>.el-tab-pane, .cn-chart__group .cn-chart__body {
|
||||
.cn-chart:not(.cn-chart__group):not(.cn-chart__block) {
|
||||
&>.cn-chart__body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.cn-panel, .cn-panel>.cn-chart__tabs>.el-tabs__content>.el-tab-pane, .cn-chart__group .cn-chart__body, .cn-chart__block .cn-chart__body {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(30, 1fr);
|
||||
grid-auto-flow: row;
|
||||
grid-gap: 10px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
padding-right: 20px;
|
||||
position: relative;
|
||||
@@ -107,6 +110,10 @@
|
||||
top: 10px;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
|
||||
&>div {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&>.cn-chart {
|
||||
@@ -126,7 +133,7 @@
|
||||
&>.cn-chart__whois>.cn-chart__body {
|
||||
overflow: auto;
|
||||
}
|
||||
&>.cn-chart__echarts, &>.cn-chart__table, &>.cn-chart__map, &>.cn-chart__group, &>.cn-chart__whois, &>.cn-chart__dns-record, &>.cn-chart__app-basic {
|
||||
&>.cn-chart__echarts, &>.cn-chart__table, &>.cn-chart__map, &>.cn-chart__group, &>.cn-chart__block, &>.cn-chart__whois, &>.cn-chart__dns-record, &>.cn-chart__app-basic {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.cn-chart__header {
|
||||
@@ -167,12 +174,40 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
&>.cn-chart__group {
|
||||
&>.cn-chart__block {
|
||||
&>.cn-chart__header {
|
||||
height: 60px;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
&>.cn-chart__body {
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(30, 1fr);
|
||||
grid-auto-flow: row;
|
||||
grid-gap: 10px;
|
||||
padding: 0 20px;
|
||||
&>.cn-chart {
|
||||
border: 1px solid #E7EAED;
|
||||
}
|
||||
/* detail页面block下的五连图的标题样式改变 */
|
||||
.cn-chart__group .cn-chart__echarts {
|
||||
.cn-chart__header {
|
||||
border-bottom: none !important;
|
||||
|
||||
.header__title {
|
||||
font-size: 14px !important;
|
||||
color: #3976CB !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.cn-chart__group {
|
||||
.cn-chart__header {
|
||||
border-bottom: 1px solid $--content-right-background-color;
|
||||
}
|
||||
&>.cn-chart__body {
|
||||
display: grid !important;
|
||||
grid-gap: 10px;
|
||||
padding: 0 20px;
|
||||
.cn-chart {
|
||||
border: none;
|
||||
@@ -518,22 +553,29 @@
|
||||
}
|
||||
}
|
||||
@media only screen and (min-width : 10px) {
|
||||
.cn-panel, .cn-panel>.cn-chart__tabs>.el-tabs__content>.el-tab-pane, .cn-chart__group .cn-chart__body {
|
||||
.cn-panel, .cn-panel>.cn-chart__tabs>.el-tabs__content>.el-tab-pane,
|
||||
.cn-chart__body {
|
||||
grid-auto-rows: 25px;
|
||||
}
|
||||
}
|
||||
@media only screen and (min-width : 1224px) {
|
||||
.cn-panel, .cn-panel>.cn-chart__tabs>.el-tabs__content>.el-tab-pane, .cn-chart__group .cn-chart__body {
|
||||
.cn-panel,
|
||||
.cn-panel>.cn-chart__tabs>.el-tabs__content>.el-tab-pane,
|
||||
.cn-chart__body {
|
||||
grid-auto-rows: 30px;
|
||||
}
|
||||
}
|
||||
@media only screen and (min-width : 1824px) {
|
||||
.cn-panel, .cn-panel>.cn-chart__tabs>.el-tabs__content>.el-tab-pane, .cn-chart__group .cn-chart__body {
|
||||
.cn-panel,
|
||||
.cn-panel>.cn-chart__tabs>.el-tabs__content>.el-tab-pane,
|
||||
.cn-chart__body {
|
||||
grid-auto-rows: 40px;
|
||||
}
|
||||
}
|
||||
@media only screen and (min-width : 2424px) {
|
||||
.cn-panel, .cn-panel>.cn-chart__tabs>.el-tabs__content>.el-tab-pane, .cn-chart__group .cn-chart__body {
|
||||
.cn-panel,
|
||||
.cn-panel>.cn-chart__tabs>.el-tabs__content>.el-tab-pane,
|
||||
.cn-chart__body {
|
||||
grid-auto-rows: 55px;
|
||||
}
|
||||
}
|
||||
@@ -627,32 +669,36 @@
|
||||
}
|
||||
}
|
||||
.entity-detail__body {
|
||||
height: calc(100% - 70px);
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
&>div {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(30, 1fr);
|
||||
grid-auto-flow: row;
|
||||
grid-auto-rows: var(--chart-height-unit);
|
||||
grid-gap: 10px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.cn-panel {
|
||||
padding: 20px;
|
||||
grid-gap: 10px;
|
||||
|
||||
&>.cn-chart>.cn-chart__header {
|
||||
border-bottom: 1px solid $--content-right-background-color;
|
||||
.header__title {
|
||||
color: #333;
|
||||
.header__title>span {
|
||||
color: #1890FF;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
.cn-chart__header {
|
||||
border-bottom: none;
|
||||
.header__title {
|
||||
color: #3976CB;
|
||||
font-size: 14px;
|
||||
&>.cn-chart>.cn-chart__body {
|
||||
.cn-chart__header {
|
||||
border-bottom: 1px solid $--content-right-background-color;
|
||||
.header__title {
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
@import './views/entityExplorer/entityExplorer';
|
||||
@import './views/entityExplorer/search/explorerSearch';
|
||||
@import './views/entityExplorer/entityFilter';
|
||||
@import './views/entityExplorer/entityDetail';
|
||||
@import './views/entityExplorer/entityList/entityList';
|
||||
@import './views/entityExplorer/entityList/card';
|
||||
@import './views/entityExplorer/entityList/row';
|
||||
|
||||
@@ -102,6 +102,11 @@
|
||||
.domain-detail-list__row {
|
||||
display: table-row;
|
||||
|
||||
&:last-of-type {
|
||||
.domain-detail-list__label, .domain-detail-list__content {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
.domain-detail-list__label {
|
||||
display: table-cell;
|
||||
padding: 13px 30px;
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
.entity-detail.cn-home {
|
||||
flex-direction: column;
|
||||
|
||||
.entity-detail__header {
|
||||
flex: 0 0 80px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.cn-entity__name {
|
||||
font-size: 20px;
|
||||
color: #333333;
|
||||
font-weight: bold;
|
||||
}
|
||||
.cn-entity__icon {
|
||||
margin-left: 26px;
|
||||
margin-right: 10px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
border-radius: 50%;
|
||||
background-color: #F3F7FA;
|
||||
|
||||
i {
|
||||
font-size: 22px;
|
||||
color: #4E84B4;
|
||||
}
|
||||
}
|
||||
}
|
||||
&>.entity-detail__body {
|
||||
width: 100%;
|
||||
height: calc(100% - 52px);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.entity-detail__menu {
|
||||
flex: 0 0 240px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 23px;
|
||||
border-top: 1px solid $--content-right-background-color;
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 7px 0 7px 30px;
|
||||
font-size: 14px;
|
||||
color: #666666;
|
||||
|
||||
span {
|
||||
padding-left: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.menu-item--active {
|
||||
color: #1890FF;
|
||||
|
||||
span {
|
||||
border-left: 2px solid #1890FF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.entity-detail__content {
|
||||
height: calc(100% - 28px);
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
background-color: $--content-right-background-color;
|
||||
|
||||
&>.cn-entity-detail .entity-detail__body>.cn-panel {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -708,6 +708,10 @@ export function isCryptocurrencyEventList (type) {
|
||||
export function isGroup (type) {
|
||||
return type === 94
|
||||
}
|
||||
/* 实体详情块 */
|
||||
export function isBlock (type) {
|
||||
return type === 95
|
||||
}
|
||||
export function getOption (type) {
|
||||
const mapping = typeOptionMappings.find(m => m.value === type)
|
||||
return mapping && mapping.option ? _.cloneDeep(mapping.option) : null
|
||||
|
||||
@@ -207,7 +207,7 @@ export default {
|
||||
this.showChangePin = true
|
||||
},
|
||||
logout () {
|
||||
sessionStorage.removeItem(storageKey.token)
|
||||
localStorage.removeItem(storageKey.token)
|
||||
get('/logout')
|
||||
},
|
||||
refreshLang () {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { storageKey } from '@/utils/constants'
|
||||
import { loadI18n } from '@/i18n'
|
||||
|
||||
const loginWhiteList = ['/login', '/'] // 免登陆白名单
|
||||
const permissionWhiteList = [...loginWhiteList] // 权限白名单
|
||||
const permissionWhiteList = [...loginWhiteList, '/entityDetail'] // 权限白名单
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
// await test(to, from)
|
||||
@@ -19,9 +19,9 @@ router.beforeEach(async (to, from, next) => {
|
||||
const config = await getConfigJson()
|
||||
axios.defaults.baseURL = config.baseUrl
|
||||
}
|
||||
if (sessionStorage.getItem(storageKey.token)) {
|
||||
if (localStorage.getItem(storageKey.token)) {
|
||||
// 加载i18n
|
||||
if (!sessionStorage.getItem(storageKey.i18n)) {
|
||||
if (!localStorage.getItem(storageKey.i18n)) {
|
||||
await loadI18n()
|
||||
}
|
||||
// 加载权限
|
||||
|
||||
@@ -6,6 +6,10 @@ const routes = [
|
||||
path: '/login',
|
||||
component: () => import('@/Login')
|
||||
},
|
||||
{
|
||||
path: '/entityDetail',
|
||||
component: () => import('@/views/entityExplorer/EntityDetail')
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
component: () => import('@/components/layout/Home'),
|
||||
|
||||
@@ -44,7 +44,7 @@ const user = {
|
||||
actions: {
|
||||
loginSuccess (store, res) {
|
||||
window.$dayJs.tz.setDefault(res.data.timezone)
|
||||
sessionStorage.setItem('cn-token', res.data.token)
|
||||
localStorage.setItem('cn-token', res.data.token)
|
||||
localStorage.setItem('cn-sys-name', res.data.systemName)
|
||||
if (res.systemLogo) {
|
||||
localStorage.setItem('cn-sys-logo', res.data.systemLogo)
|
||||
@@ -72,9 +72,9 @@ const user = {
|
||||
})
|
||||
},
|
||||
logoutSuccess (store, res) {
|
||||
sessionStorage.removeItem('cn-username')
|
||||
localStorage.removeItem('cn-username')
|
||||
sessionStorage.removeItem('cn-token')
|
||||
localStorage.removeItem('cn-username')
|
||||
localStorage.removeItem('cn-token')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ export async function getConfigJson () {
|
||||
|
||||
export async function getPermission () {
|
||||
const request = new Promise(resolve => {
|
||||
post(api.permission, { token: sessionStorage.getItem('cn-token') }).then(response => {
|
||||
post(api.permission, { token: localStorage.getItem('cn-token') }).then(response => {
|
||||
resolve({
|
||||
menuList: sortByOrderNum(response.data.menus),
|
||||
buttonList: response.data.buttons,
|
||||
@@ -116,7 +116,7 @@ export async function getPermission () {
|
||||
export async function getI18n () {
|
||||
const dictData = await getDictList()
|
||||
const langs = dictData.map(d => d.value).join(',')
|
||||
sessionStorage.setItem(storageKey.languages, langs)
|
||||
localStorage.setItem(storageKey.languages, langs)
|
||||
const request = new Promise(resolve => {
|
||||
get(api.i18n, { l: langs }).then(response => {
|
||||
response.data.cn = response.data.zh
|
||||
|
||||
@@ -84,34 +84,34 @@ export const entityFilterType = {
|
||||
],
|
||||
domain: [
|
||||
{
|
||||
column: 'category_group_distinct_count',
|
||||
column: 'categoryGroupDistinctCount',
|
||||
labelI18nCode: 'entities.domainDetail.categoryGroup',
|
||||
icon: 'cn-icon cn-icon-category'
|
||||
},
|
||||
{
|
||||
column: 'category_distinct_count',
|
||||
column: 'categoryDistinctCount',
|
||||
labelI18nCode: 'entities.category',
|
||||
icon: 'cn-icon cn-icon-sub-category'
|
||||
},
|
||||
{
|
||||
column: 'category_group_distinct_count',
|
||||
column: 'categoryGroupDistinctCount',
|
||||
labelI18nCode: 'entities.reputationLevel',
|
||||
icon: 'cn-icon cn-icon-credit'
|
||||
}
|
||||
],
|
||||
app: [
|
||||
{
|
||||
column: 'category_distinct_count',
|
||||
column: 'categoryDistinctCount',
|
||||
labelI18nCode: 'entities.category',
|
||||
icon: 'cn-icon cn-icon-category'
|
||||
},
|
||||
{
|
||||
column: 'subcategory_distinct_count',
|
||||
column: 'subcategoryDistinctCount',
|
||||
labelI18nCode: 'entities.subcategory',
|
||||
icon: 'cn-icon cn-icon-sub-category'
|
||||
},
|
||||
{
|
||||
column: 'risk_distinct_count',
|
||||
column: 'riskDistinctCount',
|
||||
labelI18nCode: 'entities.risk',
|
||||
icon: 'cn-icon cn-icon-risk'
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import axios from 'axios'
|
||||
import { storageKey } from '@/utils/constants'
|
||||
|
||||
axios.interceptors.request.use(config => {
|
||||
const token = sessionStorage.getItem('cn-token')
|
||||
const token = localStorage.getItem('cn-token')
|
||||
if (token) {
|
||||
config.headers.Authorization = token // 请求头token
|
||||
}
|
||||
@@ -41,11 +41,11 @@ axios.interceptors.request.use(
|
||||
axios.interceptors.response.use(
|
||||
response => {
|
||||
if (licenceErrorCode.indexOf(response.data.code) !== -1) {
|
||||
sessionStorage.removeItem(storageKey.token)
|
||||
localStorage.removeItem(storageKey.token)
|
||||
window.location.href = '/'
|
||||
} else if (response.status === 200) {
|
||||
if (accountErrorCode.indexOf(response.data.code) !== -1) {
|
||||
sessionStorage.removeItem(storageKey.token)
|
||||
localStorage.removeItem(storageKey.token)
|
||||
window.location.href = '/'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
v-model="orderPieTable"
|
||||
class="option__select select-column"
|
||||
placeholder=""
|
||||
popper-class="option-popper"
|
||||
popper-class="option-popper is-light"
|
||||
@change="orderPieTableChange"
|
||||
>
|
||||
<el-option v-for="item in chartPieTableTopOptions" :key="item.value" :value="item.value"> {{item.name}}</el-option>
|
||||
@@ -367,7 +367,50 @@
|
||||
</div>
|
||||
<div class="cn-chart__body">
|
||||
<template v-for="chart in chartInfo.children" :key="chart.id">
|
||||
<chart :chart="chart" :time-filter="timeFilter" :ref="`chart-${chart.id}`" :entity="entity" :parent-data="groupData"></chart>
|
||||
<chart
|
||||
:chart="chart"
|
||||
:time-filter="timeFilter"
|
||||
:ref="`chart-${chart.id}`"
|
||||
:entity="entity"
|
||||
:parent-data="groupData"
|
||||
:from-block="fromBlock"
|
||||
@getChartCurrentTimeRange="getChartCurrentTimeRange"
|
||||
></chart>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- block -->
|
||||
<div
|
||||
v-else-if="isBlock"
|
||||
class="cn-chart cn-chart__block"
|
||||
:style="computePosition"
|
||||
:id="chartInfo.params && chartInfo.params.anchorPoint"
|
||||
>
|
||||
<div class="cn-chart__header">
|
||||
<chart-error
|
||||
:isError="isError"
|
||||
:errorInfo="errorInfo"
|
||||
>
|
||||
</chart-error>
|
||||
<div class="header__title">
|
||||
<span :title="chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name">{{chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name}}</span>
|
||||
<div style="top: 18px;" class="panel__time" v-if="chartInfo.params && chartInfo.params.showTimeTool">
|
||||
<DateTimeRange class="date-time-range" :start-time="chartTimeFilter.startTime" :end-time="chartTimeFilter.endTime" ref="dateTimeRange" @change="reload"/>
|
||||
<TimeRefresh class="date-time-range" @change="timeRefreshChange" :end-time="chartTimeFilter.endTime"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cn-chart__body">
|
||||
<template v-for="chart in chartInfo.children" :key="chart.id">
|
||||
<chart
|
||||
:chart="chart"
|
||||
:time-filter="chartTimeFilter"
|
||||
:ref="`chart-${chart.id}`"
|
||||
:entity="entity"
|
||||
:parent-data="groupData"
|
||||
:from-block="true"
|
||||
@getChartCurrentTimeRange="getChartCurrentTimeRange"
|
||||
></chart>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -564,7 +607,7 @@
|
||||
import * as echarts from 'echarts'
|
||||
import * as am4Core from '@amcharts/amcharts4/core'
|
||||
import * as am4Maps from '@amcharts/amcharts4/maps'
|
||||
import { shallowRef } from 'vue'
|
||||
import { ref, shallowRef } from 'vue'
|
||||
import { tableTitleMapping, legendMapping } from '@/components/charts/chart-table-title'
|
||||
import {
|
||||
isEcharts,
|
||||
@@ -597,7 +640,7 @@ import {
|
||||
isAppBasicInfo,
|
||||
isAppRelatedDomain,
|
||||
getChartColor, chartBarColor, timeVerticalFormatter, timeHorizontalFormatter,
|
||||
categoryHorizontalFormatter, categoryVerticalFormatter, getCharBartColor
|
||||
categoryHorizontalFormatter, categoryVerticalFormatter, getCharBartColor, isBlock
|
||||
} from '@/components/charts/chart-options'
|
||||
import ChartError from '@/components/charts/ChartError'
|
||||
import EchartsFrame from '@/components/charts/EchartsFrame'
|
||||
@@ -613,11 +656,14 @@ import { chartTableDefaultPageSize, chartTableTopOptions, chartActiveIpTableOrde
|
||||
import { get, post } from '@/utils/http'
|
||||
import { replaceUrlPlaceholder, getCapitalGeo, getGeoData, lineToSpace } from '@/utils/tools'
|
||||
import { HeatLegend } from '@/components/amcharts/heatLegend'
|
||||
import DateTimeRange from '@/components/common/TimeRange/DateTimeRange'
|
||||
import TimeRefresh from '@/components/common/TimeRange/TimeRefresh'
|
||||
|
||||
import * as L from 'leaflet'
|
||||
import 'leaflet/dist/leaflet.css'
|
||||
import icon from 'leaflet/dist/images/marker-icon.png'
|
||||
import iconShadow from 'leaflet/dist/images/marker-shadow.png'
|
||||
import { getNowTime } from '@/utils/date-util'
|
||||
|
||||
export default {
|
||||
name: 'Chart',
|
||||
@@ -625,6 +671,7 @@ export default {
|
||||
chart: Object, // 图表对象,包括id、name、type等数据
|
||||
timeFilter: Object,
|
||||
parentData: Object,
|
||||
fromBlock: Boolean,
|
||||
entity: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
@@ -639,7 +686,9 @@ export default {
|
||||
PieTable,
|
||||
StatisticsLegend,
|
||||
ChartMap,
|
||||
ChartError
|
||||
ChartError,
|
||||
DateTimeRange,
|
||||
TimeRefresh
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -651,6 +700,7 @@ export default {
|
||||
tableData: [], // table的所有数据
|
||||
currentPageData: [] // table当前页的数据
|
||||
},
|
||||
|
||||
activeIpTable: {
|
||||
orderBy: 'machine',
|
||||
tableData: [
|
||||
@@ -719,9 +769,13 @@ export default {
|
||||
methods: {
|
||||
initChart () {
|
||||
this.loading = true
|
||||
this.queryTimeRange = this.standaloneTimeRange.use
|
||||
? { startTime: parseInt(this.standaloneTimeRange.startTime / 1000), endTime: parseInt(this.standaloneTimeRange.endTime / 1000) }
|
||||
: { startTime: parseInt(this.timeFilter.startTime / 1000), endTime: parseInt(this.timeFilter.endTime / 1000) }
|
||||
if (this.standaloneTimeRange.use) {
|
||||
this.queryTimeRange = { startTime: parseInt(this.standaloneTimeRange.startTime / 1000), endTime: parseInt(this.standaloneTimeRange.endTime / 1000) }
|
||||
} else if (this.timeFilter) {
|
||||
this.queryTimeRange = { startTime: parseInt(this.timeFilter.startTime / 1000), endTime: parseInt(this.timeFilter.endTime / 1000) }
|
||||
} else {
|
||||
this.queryTimeRange = { startTime: parseInt(this.chartTimeFilter.startTime / 1000), endTime: parseInt(this.chartTimeFilter.endTime / 1000) }
|
||||
}
|
||||
try {
|
||||
const chartParams = this.chartInfo.params
|
||||
if (this.isMap) {
|
||||
@@ -972,6 +1026,18 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
timeRefreshChange () {
|
||||
if (!this.$refs.dateTimeRange.isCustom) {
|
||||
const value = this.chartTimeFilter.dateRangeValue
|
||||
this.$refs.dateTimeRange.quickChange(value)
|
||||
}
|
||||
},
|
||||
reload (s, e, v) {
|
||||
this.dateTimeRangeChange(s, e, v)
|
||||
},
|
||||
dateTimeRangeChange (s, e, v) {
|
||||
this.chartTimeFilter = { startTime: s, endTime: e, dateRangeValue: v }
|
||||
},
|
||||
generateTooltipHTML () {
|
||||
return `
|
||||
<div class="map-tooltip" style="padding-bottom: 10px;">
|
||||
@@ -1233,7 +1299,8 @@ export default {
|
||||
return this.$_.slice(tableData, (pageNum - 1) * pageSize, pageNum * pageSize)
|
||||
},
|
||||
refresh () {
|
||||
this.$emit('getCurrentTimeRange', ({ startTime, endTime }) => {
|
||||
const eventName = this.fromBlock ? 'getChartCurrentTimeRange' : 'getCurrentTimeRange'
|
||||
this.$emit(eventName, ({ startTime, endTime }) => {
|
||||
this.standaloneTimeRange.use = true
|
||||
this.standaloneTimeRange.startTime = startTime
|
||||
this.standaloneTimeRange.endTime = endTime
|
||||
@@ -1252,6 +1319,21 @@ export default {
|
||||
callback({ startTime, endTime })
|
||||
})
|
||||
},
|
||||
// 获取最新时间
|
||||
getChartCurrentTimeRange (callback) {
|
||||
console.info(this.isGroup)
|
||||
if (this.isGroup) {
|
||||
this.$emit('getChartCurrentTimeRange', ({ startTime, endTime }) => {
|
||||
console.info(startTime, endTime)
|
||||
callback({ startTime, endTime })
|
||||
})
|
||||
} else {
|
||||
const myEndTime = window.$dayJs.tz().valueOf()
|
||||
const myStartTime = myEndTime - this.chartTimeFilter.dateRangeValue * 60 * 1000
|
||||
console.info(myStartTime, myEndTime)
|
||||
callback({ startTime: myStartTime, endTime: myEndTime })
|
||||
}
|
||||
},
|
||||
getDataKey (r) {
|
||||
let key = ''
|
||||
let labelText = ''
|
||||
@@ -1337,6 +1419,8 @@ export default {
|
||||
this.chartOption.legend.show = false
|
||||
}
|
||||
const queryParams = { ...this.queryTimeRange, ...this.entity }
|
||||
console.info(chartParams.url)
|
||||
console.info(queryParams)
|
||||
get(replaceUrlPlaceholder(chartParams.url, queryParams)).then(response => {
|
||||
if (response.code === 200) {
|
||||
if (this.$_.isEmpty(response.data.result)) {
|
||||
@@ -2464,8 +2548,14 @@ export default {
|
||||
setup (props) {
|
||||
const chartInfo = JSON.parse(JSON.stringify(props.chart))
|
||||
chartInfo.category = getTypeCategory(props.chart.type)
|
||||
|
||||
const dateRangeValue = 60
|
||||
const { startTime, endTime } = getNowTime(dateRangeValue)
|
||||
// entity详情内的chart时间工具不是公共的,需要单独定义
|
||||
const chartTimeFilter = ref({ startTime, endTime, dateRangeValue })
|
||||
return {
|
||||
chartInfo,
|
||||
chartTimeFilter,
|
||||
layoutConstant,
|
||||
chartTableTopOptions,
|
||||
chartActiveIpTableOrderOptions,
|
||||
@@ -2486,6 +2576,7 @@ export default {
|
||||
isMapBlock: isMapBlock(props.chart.type),
|
||||
isTabs: isTabs(props.chart.type),
|
||||
isGroup: isGroup(props.chart.type),
|
||||
isBlock: isBlock(props.chart.type),
|
||||
isSankey: isSankey(props.chart.type),
|
||||
isIpBasicInfo: isIpBasicInfo(props.chart.type),
|
||||
isIpHostedDomain: isIpHostedDomain(props.chart.type),
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<div style="padding: 10px 0 20px 20px;" v-if="!isEntityDetail">
|
||||
<div id="cn-panel" :class="(isCryptocurrency)?'cn-panel cn-panel-crypto':'cn-panel'">
|
||||
<div style="padding: 10px 0 20px 20px; overflow: auto" v-if="!isEntityDetail">
|
||||
<div id="cn-panel"
|
||||
:class="(isCryptocurrency)?'cn-panel cn-panel-crypto':'cn-panel'"
|
||||
>
|
||||
<div class="panel__time">
|
||||
<DateTimeRange class="date-time-range" :start-time="timeFilter.startTime" :end-time="timeFilter.endTime" ref="dateTimeRange" @change="reload"/>
|
||||
<TimeRefresh class="date-time-range" @change="timeRefreshChange" :end-time="timeFilter.endTime"/>
|
||||
@@ -35,43 +37,16 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="cn-entity-detail" id="cn-entity-detail" v-else>
|
||||
<div class="entity-detail__header">
|
||||
<div class="detail-header__title">
|
||||
<span class="title__icon-circle">
|
||||
<i class="cn-icon" :class="{'cn-icon-ip': entity.ip, 'cn-icon-domain': entity.domain, 'cn-icon-app': entity.app}"></i>
|
||||
</span>
|
||||
<div class="title__name" :title="entity.ip || entity.domain || entity.app || '-'">{{entity.ip || entity.domain || entity.app || '-'}}</div></div>
|
||||
<div class="detail-header__operation">
|
||||
<div class="panel__time">
|
||||
<DateTimeRange class="date-time-range" :start-time="timeFilter.startTime" :end-time="timeFilter.endTime" ref="dateTimeRange" @change="reload"/>
|
||||
<TimeRefresh class="date-time-range" @change="timeRefreshChange" :end-time="timeFilter.endTime"/>
|
||||
</div>
|
||||
<el-tabs
|
||||
v-model="currentTab"
|
||||
@tab-click="changeTab"
|
||||
>
|
||||
<el-tab-pane
|
||||
v-for="tab in detailTabs"
|
||||
:label="tab.i18n ? $t(tab.i18n) : tab.name"
|
||||
:name="`${tab.id}`"
|
||||
:key="tab.id"
|
||||
:ref="`chart-${tab.id}`"
|
||||
>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entity-detail__body">
|
||||
<div class="cn-panel">
|
||||
<chart
|
||||
v-for="chart in detailChartList"
|
||||
:key="chart.id"
|
||||
:chart="chart"
|
||||
:time-filter="timeFilter"
|
||||
:ref="`chart-${chart.id}`"
|
||||
:entity="entity"
|
||||
@getCurrentTimeRange="getCurrentTimeRange"
|
||||
></chart>
|
||||
<div class="cn-panel" @scroll="scroll" id="cn-panel">
|
||||
<template v-for="chart in chartList" :key="chart.id">
|
||||
<chart
|
||||
:chart="chart"
|
||||
:ref="`chart-${chart.id}`"
|
||||
:entity="entity"
|
||||
@getCurrentTimeRange="getCurrentTimeRange"
|
||||
></chart>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -91,7 +66,8 @@ export default {
|
||||
name: 'Panel',
|
||||
props: {
|
||||
entity: Object,
|
||||
isEntityDetail: Boolean
|
||||
isEntityDetail: Boolean,
|
||||
typeName: String
|
||||
},
|
||||
components: {
|
||||
Chart,
|
||||
@@ -109,7 +85,6 @@ export default {
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
const path = this.$route.path
|
||||
this.isCryptocurrency = this.$route.path.indexOf('cryptocurrency') > -1
|
||||
await this.init()
|
||||
if (!this.$_.isEmpty(this.detailTabs)) {
|
||||
@@ -144,23 +119,19 @@ export default {
|
||||
this.recursionParamsConvert(chart)
|
||||
return chart
|
||||
})
|
||||
if (this.isEntityDetail) {
|
||||
if (!this.$_.isEmpty(allCharts)) {
|
||||
const rootChart = allCharts[0]
|
||||
this.detailTabs = rootChart.children
|
||||
if (!this.$_.isEmpty(this.detailTabs)) {
|
||||
this.detailChartList = this.detailTabs[0].children
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.chartList = allCharts
|
||||
}
|
||||
this.chartList = allCharts
|
||||
setTimeout(() => {
|
||||
this.$emit('chartLoaded', allCharts)
|
||||
})
|
||||
}
|
||||
},
|
||||
changeTab ({ index }) {
|
||||
this.currentTab = this.detailTabs[index].id + ''
|
||||
this.detailChartList = this.detailTabs[index].children
|
||||
},
|
||||
scroll (e) {
|
||||
this.$emit('scroll', { top: e.target.scrollTop })
|
||||
},
|
||||
recursionParamsConvert (chart) {
|
||||
chart.params = chart.params ? JSON.parse(chart.params) : null
|
||||
if (!this.$_.isEmpty(chart.children)) {
|
||||
@@ -179,22 +150,9 @@ export default {
|
||||
const value = this.timeFilter.dateRangeValue
|
||||
this.$refs.dateTimeRange.quickChange(value)
|
||||
}
|
||||
/* if (!this.$refs.dateTimeRange) {
|
||||
this.reloadCharts()
|
||||
return
|
||||
}
|
||||
if (this.$refs.dateTimeRange.isCustom) {
|
||||
this.reloadCharts()
|
||||
} else {
|
||||
const value = this.timeFilter.dateRangeValue
|
||||
this.$refs.dateTimeRange.quickChange(value)
|
||||
} */
|
||||
},
|
||||
reload (s, e, v) {
|
||||
this.dateTimeRangeChange(s, e, v)
|
||||
/* this.$nextTick(() => {
|
||||
this.reloadCharts()
|
||||
}) */
|
||||
},
|
||||
// methods
|
||||
dateTimeRangeChange (s, e, v) {
|
||||
@@ -204,6 +162,9 @@ export default {
|
||||
this.chartList.forEach(chart => {
|
||||
this.$refs[`chart-${chart.id}`] && this.$refs[`chart-${chart.id}`].reloadChart()
|
||||
})
|
||||
},
|
||||
jumpToTop (top) {
|
||||
document.querySelector('#cn-panel').scrollTop = top
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
140
src/views/entityExplorer/EntityDetail.vue
Normal file
140
src/views/entityExplorer/EntityDetail.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<div class="cn-home entity-detail">
|
||||
<div class="entity-detail__header">
|
||||
<div class="cn-entity__icon"><i :class="iconClass"></i></div>
|
||||
<div class="cn-entity__name">{{entityData.name}}</div>
|
||||
</div>
|
||||
<main class="cn-body entity-detail__body">
|
||||
<div class="entity-detail__menu">
|
||||
<template v-for="anchor in anchorPoints" :key="anchor.id">
|
||||
<div class="menu-item" :class="{'menu-item--active': menuIsActive(anchor)}" @click="jumpToAnchor(anchor)">
|
||||
<span>{{anchor.label}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="entity-detail__content">
|
||||
<cn-panel
|
||||
ref="cnPanel"
|
||||
:entity="entityData"
|
||||
:is-entity-detail="true"
|
||||
@chartLoaded="chartLoaded"
|
||||
@scroll="scroll"
|
||||
></cn-panel>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useRoute } from 'vue-router'
|
||||
import Panel from '@/views/charts/Panel'
|
||||
export default {
|
||||
name: 'EntityDetail',
|
||||
components: {
|
||||
cnPanel: Panel
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
anchorPoints: [], // { id, label, top, height }
|
||||
top: 0
|
||||
}
|
||||
},
|
||||
setup (props, ctx) {
|
||||
const { query } = useRoute()
|
||||
let panelType
|
||||
const entityData = {}
|
||||
switch (query.entityType) {
|
||||
case 'ip': {
|
||||
panelType = 4
|
||||
entityData.ip = query.name
|
||||
break
|
||||
}
|
||||
case 'domain': {
|
||||
panelType = 5
|
||||
entityData.domain = query.name
|
||||
break
|
||||
}
|
||||
case 'app': {
|
||||
panelType = 6
|
||||
entityData.appName = query.name
|
||||
break
|
||||
}
|
||||
default: {
|
||||
panelType = 4
|
||||
break
|
||||
}
|
||||
}
|
||||
entityData.type = panelType
|
||||
return {
|
||||
entityData
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
chartLoaded (chartList) {
|
||||
this.anchorPoints = []
|
||||
let anchorPoints = []
|
||||
chartList.forEach(chart => {
|
||||
if (chart.params.anchorPoint) {
|
||||
const dom = document.querySelector(`#${chart.params.anchorPoint}`)
|
||||
anchorPoints.push({
|
||||
id: chart.params.anchorPoint,
|
||||
label: chart.i18n ? this.$t(chart.i18n) : chart.name,
|
||||
top: dom.offsetTop/* ,
|
||||
height: document.querySelector(`#${chart.params.anchorPoint}}`).scrollHeight */
|
||||
})
|
||||
}
|
||||
})
|
||||
// 从小到大排序
|
||||
anchorPoints = anchorPoints.sort((a, b) => {
|
||||
return a.top - b.top
|
||||
})
|
||||
if (!this.$_.isEmpty(anchorPoints)) {
|
||||
anchorPoints[0].top = 0
|
||||
}
|
||||
this.anchorPoints = anchorPoints
|
||||
},
|
||||
scroll ({ top }) {
|
||||
this.top = top || 0
|
||||
},
|
||||
jumpToAnchor (anchor) {
|
||||
this.top = anchor.top
|
||||
this.$refs.cnPanel.jumpToTop(anchor.top)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iconClass () {
|
||||
let className
|
||||
switch (this.entityData.entityType) {
|
||||
case ('ip'): {
|
||||
className = 'cn-icon cn-icon-ip'
|
||||
break
|
||||
}
|
||||
case ('domain'): {
|
||||
className = 'cn-icon cn-icon-domain'
|
||||
break
|
||||
}
|
||||
case ('app'): {
|
||||
className = 'cn-icon cn-icon-app'
|
||||
break
|
||||
}
|
||||
default: break
|
||||
}
|
||||
return className
|
||||
},
|
||||
menuIsActive () {
|
||||
return function (anchor) {
|
||||
return this.currentAnchor ? this.currentAnchor.id === anchor.id : false
|
||||
}
|
||||
},
|
||||
currentAnchor () {
|
||||
let currentAnchor = null
|
||||
this.anchorPoints.forEach(anchor => {
|
||||
if (anchor.top <= this.top) {
|
||||
currentAnchor = anchor
|
||||
}
|
||||
})
|
||||
return currentAnchor
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -150,28 +150,28 @@ export default {
|
||||
data: [
|
||||
{
|
||||
label: this.$t('overall.country'),
|
||||
column: 'country_distinct_count',
|
||||
topColumn: 'ip_location_country', // top弹框查询字段
|
||||
column: 'countryDistinctCount',
|
||||
topColumn: 'ipLocationCountry', // top弹框查询字段
|
||||
icon: entityFilterType.ip[0].icon,
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: this.$t('overall.province'),
|
||||
column: 'province_distinct_count',
|
||||
topColumn: 'ip_location_province', // top弹框查询字段
|
||||
column: 'provinceDistinctCount',
|
||||
topColumn: 'ipLocationProvince', // top弹框查询字段
|
||||
icon: entityFilterType.ip[1].icon,
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: this.$t('overall.city'),
|
||||
column: 'city_distinct_count',
|
||||
column: 'cityDistinctCount',
|
||||
topColumn: 'ip_location_city', // top弹框查询字段
|
||||
icon: entityFilterType.ip[2].icon,
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: this.$t('entities.asn'),
|
||||
column: 'asn_distinct_count',
|
||||
column: 'asnDistinctCount',
|
||||
topColumn: 'ip_asn', // top弹框查询字段
|
||||
icon: entityFilterType.ip[3].icon,
|
||||
value: 0
|
||||
@@ -185,21 +185,21 @@ export default {
|
||||
data: [
|
||||
{
|
||||
label: this.$t('entities.category'),
|
||||
column: 'category_distinct_count',
|
||||
column: 'categoryDistinctCount',
|
||||
topColumn: 'app_category', // top弹框查询字段
|
||||
icon: entityFilterType.app[0].icon,
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: this.$t('entities.subcategory'),
|
||||
column: 'subcategory_distinct_count',
|
||||
column: 'subcategoryDistinctCount',
|
||||
topColumn: 'app_subcategory', // top弹框查询字段
|
||||
icon: entityFilterType.app[1].icon,
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: this.$t('entities.risk'),
|
||||
column: 'risk_distinct_count',
|
||||
column: 'riskDistinctCount',
|
||||
topColumn: 'app_risk', // top弹框查询字段
|
||||
icon: entityFilterType.app[2].icon,
|
||||
value: 0
|
||||
@@ -213,22 +213,22 @@ export default {
|
||||
data: [
|
||||
{
|
||||
label: this.$t('entities.domainDetail.categoryGroup'),
|
||||
column: 'category_group_distinct_count',
|
||||
column: 'categoryGroupDistinctCount',
|
||||
topColumn: 'domain_category_group', // top弹框查询字段
|
||||
icon: entityFilterType.domain[0].icon,
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: this.$t('entities.category'),
|
||||
column: 'category_distinct_count',
|
||||
column: 'categoryDistinctCount',
|
||||
topColumn: 'domain_category', // top弹框查询字段
|
||||
icon: entityFilterType.domain[1].icon,
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: this.$t('entities.reputationLevel'),
|
||||
column: 'reputation_level_distinct_count',
|
||||
topColumn: 'domain_reputation_level', // top弹框查询字段
|
||||
column: 'reputationLevelDistinctCount',
|
||||
topColumn: 'domainReputationLevel', // top弹框查询字段
|
||||
icon: entityFilterType.domain[2].icon,
|
||||
value: 0
|
||||
}
|
||||
|
||||
@@ -84,7 +84,11 @@
|
||||
<span>{{entityData.securityCount || '-'}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="show-detail" :style="{visibility: !isCollapse ? 'visible' : 'hidden'}">{{$t('overall.detail')}}>></div>
|
||||
<div
|
||||
class="show-detail"
|
||||
:style="{visibility: !isCollapse ? 'visible' : 'hidden'}"
|
||||
@click="showDetail"
|
||||
>{{$t('overall.detail')}}>></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -127,6 +131,16 @@ export default {
|
||||
/* 设为折叠状态 */
|
||||
collapse () {
|
||||
this.isCollapse = true
|
||||
},
|
||||
showDetail () {
|
||||
const { href } = this.$router.resolve({
|
||||
path: '/entityDetail',
|
||||
query: {
|
||||
entityType: this.entityData.entityType,
|
||||
name: this.entityData.ipAddr || this.entityData.domainName || this.entityData.appName
|
||||
}
|
||||
})
|
||||
window.open(href, '_blank')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export default {
|
||||
let name
|
||||
switch (this.entityData.entityType) {
|
||||
case ('ip'): {
|
||||
name = this.entity.ip
|
||||
name = this.entity.ipAddr
|
||||
break
|
||||
}
|
||||
case ('domain'): {
|
||||
@@ -85,7 +85,7 @@ export default {
|
||||
startTime: Math.floor(now.getTime() / 1000 - 3600),
|
||||
endTime: Math.floor(now.getTime() / 1000)
|
||||
},
|
||||
ip: this.entityData.ip
|
||||
ip: this.entityData.ipAddr
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ import dataListMixin from '@/mixins/dataList'
|
||||
import rolesTable from '@/components/table/settings/RoleTable'
|
||||
import roleBox from '@/components/rightBox/settings/RoleBox'
|
||||
import { api } from '@/utils/api'
|
||||
import {get} from "@/utils/http";
|
||||
|
||||
export default {
|
||||
name: 'roles',
|
||||
@@ -69,6 +70,16 @@ export default {
|
||||
name: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
edit (u) {
|
||||
get(`${this.url}`, { ids: u.id }).then(response => {
|
||||
if (response.code === 200) {
|
||||
this.object = response.data
|
||||
this.rightBox.show = true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user