CN-240 feat: 实体详情

This commit is contained in:
chenjinsong
2022-01-03 22:46:22 +08:00
parent e37e967b5d
commit 6d73abf18f
19 changed files with 481 additions and 126 deletions

View File

@@ -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>.cn-chart__body {
.cn-chart__header {
border-bottom: none;
border-bottom: 1px solid $--content-right-background-color;
.header__title {
color: #3976CB;
font-size: 14px;
color: #666;
font-size: 16px;
}
}
}
}

View File

@@ -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';

View File

@@ -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;

View File

@@ -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;
}
}
}
}

View File

@@ -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

View File

@@ -207,7 +207,7 @@ export default {
this.showChangePin = true
},
logout () {
sessionStorage.removeItem(storageKey.token)
localStorage.removeItem(storageKey.token)
get('/logout')
},
refreshLang () {

View File

@@ -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()
}
// 加载权限

View File

@@ -6,6 +6,10 @@ const routes = [
path: '/login',
component: () => import('@/Login')
},
{
path: '/entityDetail',
component: () => import('@/views/entityExplorer/EntityDetail')
},
{
path: '/',
component: () => import('@/components/layout/Home'),

View File

@@ -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')
}
}
}

View File

@@ -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

View File

@@ -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'
}

View File

@@ -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 = '/'
}
}

View File

@@ -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">&nbsp{{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),

View File

@@ -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">
<div class="cn-panel" @scroll="scroll" id="cn-panel">
<template v-for="chart in chartList" :key="chart.id">
<chart
v-for="chart in detailChartList"
:key="chart.id"
:chart="chart"
:time-filter="timeFilter"
: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
}
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
}
}
}

View 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>

View File

@@ -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
}

View File

@@ -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')
}
}
}

View File

@@ -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
}

View File

@@ -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>