diff --git a/src/assets/css/components/components/charts/panel.scss b/src/assets/css/components/components/charts/panel.scss index 544e6c28..7adea929 100644 --- a/src/assets/css/components/components/charts/panel.scss +++ b/src/assets/css/components/components/charts/panel.scss @@ -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; + } } } } diff --git a/src/assets/css/components/index.scss b/src/assets/css/components/index.scss index e65ea519..ce73b892 100644 --- a/src/assets/css/components/index.scss +++ b/src/assets/css/components/index.scss @@ -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'; diff --git a/src/assets/css/components/views/charts/chart.scss b/src/assets/css/components/views/charts/chart.scss index e7d37015..0ab4d78e 100644 --- a/src/assets/css/components/views/charts/chart.scss +++ b/src/assets/css/components/views/charts/chart.scss @@ -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; diff --git a/src/assets/css/components/views/entityExplorer/entityDetail.scss b/src/assets/css/components/views/entityExplorer/entityDetail.scss new file mode 100644 index 00000000..f3f0a667 --- /dev/null +++ b/src/assets/css/components/views/entityExplorer/entityDetail.scss @@ -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; + } + } + } +} diff --git a/src/components/charts/chart-options.js b/src/components/charts/chart-options.js index ea43e87b..2506a6d2 100644 --- a/src/components/charts/chart-options.js +++ b/src/components/charts/chart-options.js @@ -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 diff --git a/src/components/layout/Header.vue b/src/components/layout/Header.vue index 37f453f9..79e59463 100644 --- a/src/components/layout/Header.vue +++ b/src/components/layout/Header.vue @@ -207,7 +207,7 @@ export default { this.showChangePin = true }, logout () { - sessionStorage.removeItem(storageKey.token) + localStorage.removeItem(storageKey.token) get('/logout') }, refreshLang () { diff --git a/src/permission.js b/src/permission.js index 5713f3c3..2954bf6d 100644 --- a/src/permission.js +++ b/src/permission.js @@ -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() } // 加载权限 diff --git a/src/router/index.js b/src/router/index.js index 0131c056..30bc4aba 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -6,6 +6,10 @@ const routes = [ path: '/login', component: () => import('@/Login') }, + { + path: '/entityDetail', + component: () => import('@/views/entityExplorer/EntityDetail') + }, { path: '/', component: () => import('@/components/layout/Home'), diff --git a/src/store/modules/user.js b/src/store/modules/user.js index eb24739b..d17c7416 100644 --- a/src/store/modules/user.js +++ b/src/store/modules/user.js @@ -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') } } } diff --git a/src/utils/api.js b/src/utils/api.js index d2c678d6..3901a9f3 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -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 diff --git a/src/utils/constants.js b/src/utils/constants.js index e7f59c50..aec809f8 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -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' } diff --git a/src/utils/http.js b/src/utils/http.js index 44bfccd0..00d8d45a 100644 --- a/src/utils/http.js +++ b/src/utils/http.js @@ -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 = '/' } } diff --git a/src/views/charts/Chart.vue b/src/views/charts/Chart.vue index 5e16b165..ed71e8a9 100644 --- a/src/views/charts/Chart.vue +++ b/src/views/charts/Chart.vue @@ -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" >  {{item.name}} @@ -367,7 +367,50 @@
+
+ + +
+
+ + +
+ {{chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name}} +
+ + +
+
+
+
+
@@ -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 `
@@ -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), diff --git a/src/views/charts/Panel.vue b/src/views/charts/Panel.vue index 96f7b5a4..410b4793 100644 --- a/src/views/charts/Panel.vue +++ b/src/views/charts/Panel.vue @@ -1,6 +1,8 @@