diff options
| author | chenjinsong <[email protected]> | 2021-09-15 12:19:13 +0800 |
|---|---|---|
| committer | chenjinsong <[email protected]> | 2021-09-15 12:19:13 +0800 |
| commit | 8093f4bf934b03927c1ae6eb8a9c0880edcb4607 (patch) | |
| tree | 752bdcdec1c096e2a9e23c78a1a60f2816d7fc43 | |
| parent | b4a03aa7ceb4ef2eedf69b321a243df32b11233e (diff) | |
feat: entity详情(未完成)、修复一些问题
| -rw-r--r-- | src/components/charts/ChartMap.vue | 2 | ||||
| -rw-r--r-- | src/components/charts/panel.scss | 93 | ||||
| -rw-r--r-- | src/components/entities/EntityDetail.vue | 60 | ||||
| -rw-r--r-- | src/components/entities/EntityList.vue | 10 | ||||
| -rw-r--r-- | src/components/layout/Header.vue | 17 | ||||
| -rw-r--r-- | src/store/index.js | 6 | ||||
| -rw-r--r-- | src/utils/constants.js | 7 | ||||
| -rw-r--r-- | src/views/charts/Chart.vue | 29 | ||||
| -rw-r--r-- | src/views/charts/Panel.vue | 116 | ||||
| -rw-r--r-- | src/views/entities/EntityExplorer.vue | 23 |
10 files changed, 239 insertions, 124 deletions
diff --git a/src/components/charts/ChartMap.vue b/src/components/charts/ChartMap.vue index a09d27d2..4a597711 100644 --- a/src/components/charts/ChartMap.vue +++ b/src/components/charts/ChartMap.vue @@ -2,7 +2,7 @@ <div class="cn-chart cn-chart__map"> <div class="cn-chart__header chart-header-position" > <slot name="chartErrorInfo"></slot> - <div class="header__title chart-title-width" > + <div class="header__title" > <slot name="title"></slot> </div> <div class="header__operations"> diff --git a/src/components/charts/panel.scss b/src/components/charts/panel.scss index eb2472d2..4fba703a 100644 --- a/src/components/charts/panel.scss +++ b/src/components/charts/panel.scss @@ -23,11 +23,8 @@ max-width: 280px !important; } -.chart-title-width{ - width:100%; -} .chart-header-position{ - position:relative; + position: relative; } .chart-error-popper.el-popper.is-light { @@ -105,6 +102,7 @@ position: absolute; right: 20px; top: 10px; + z-index: 1; display: flex; } @@ -133,14 +131,14 @@ flex-shrink: 0; padding: 10px 20px 10px 18px; height: 50px; - font-size: 16px; - color: #333333; - font-weight: bold; + .cn-chart__title { + font-size: 16px; + color: #333333; + font-weight: bold; + } .header__operations { - .cn-icon-more-light { - color: #999; - } + color: #999; } } .cn-chart__body { @@ -517,6 +515,77 @@ border-bottom: none; } } + +.cn-entity-detail { + height: 100%; + width: 100%; + overflow: hidden; + .entity-detail__header { + display: flex; + justify-content: end; + height: 70px; + padding-right: 20px; + background-color: #F7F9FB; + & > .el-tabs > .el-tabs__header { // header背景色 + margin: 0; + & > .el-tabs__nav-wrap > .el-tabs__nav-scroll > .el-tabs__nav { + & > .el-tabs__item.is-active { // 激活的tab上边框和背景色 + background-color: white; + border-top: 2px solid #0091ff; + } + & > .el-tabs__active-bar { + display: none; + } + & > div:last-of-type { + padding-right: 20px; + } + & > div:nth-of-type(2) { + padding-left: 20px; + } + } + } + & > .el-tabs > .el-tabs__header > .el-tabs__nav-wrap::after { // 去掉tabs下方边框 + height: 0 !important; + } + &>.el-tabs { // 底部对齐 + display: flex; + align-items: end; + } + } + .entity-detail__body { + height: calc(100% - 90px); + 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; + } + .cn-panel { + padding: 20px; + grid-gap: 20px; + } + } +} +.el-overlay { + overflow: hidden !important; +} +.entity-detail__dialog { + height: 90vh; + overflow: hidden; + .el-dialog__header { + display: none; + } + .el-dialog__body { + height: 100%; + padding: 0; + .panel__time { + display: none; + } + } +} .option-popper { .el-select-dropdown__item { height: 24px; @@ -524,6 +593,10 @@ font-size: 12px; } } +.header__operation-btn { + margin-left: 12px; + cursor: pointer; +} //.cn-chart-select{ // display: flex; // align-items: center; diff --git a/src/components/entities/EntityDetail.vue b/src/components/entities/EntityDetail.vue new file mode 100644 index 00000000..d1a9ebce --- /dev/null +++ b/src/components/entities/EntityDetail.vue @@ -0,0 +1,60 @@ +<template> + <el-dialog + v-model="show" + :top="top" + :modal="modal" + :custom-class="customClass" + :show-close="showClose" + :width="width" + destroy-on-close + > + <cn-panel :entity="entity" :is-entity-detail="true"></cn-panel> + </el-dialog> +</template> + +<script> +import Panel from '@/views/charts/Panel' +export default { + name: 'EntityDetail', + components: { + 'cn-panel': Panel + }, + props: { + showDetail: Boolean, + top: { + type: String, + default: '5vh' + }, + modal: { + type: Boolean, + default: true + }, + customClass: { + type: String, + default: 'entity-detail__dialog' + }, + showClose: { + type: Boolean, + default: true + }, + width: { // 高度需要通过customClass自行定义 + type: [String, Number], + default: '90vw' + }, + entity: Object // 实体,{ type: 'ip|domain|app', name: name, icon: icon-class } + }, + data () { + return { + show: false + } + }, + watch: { + showDetail (n, o) { + this.show = n + }, + show (n, o) { + this.$emit('update:show-detail', n) + } + } +} +</script> diff --git a/src/components/entities/EntityList.vue b/src/components/entities/EntityList.vue index c1d96a86..fa69f8af 100644 --- a/src/components/entities/EntityList.vue +++ b/src/components/entities/EntityList.vue @@ -35,7 +35,7 @@ <div class="body__row-value" :title="d.asn">{{d.asn || '-'}}</div> </div> <div class="body__detail" - @click="entityDetail({ip: d.ip}, [{key: 'clientIP', label: $t('overall.clientIp')}, {key: 'serverIP', label: $t('overall.serverIp')}])">{{$t('overall.detail')}}></div> + @click="entityDetail({name: d.ip, type: 4})">{{$t('overall.detail')}}></div> </template> <template v-else-if="from === 'domain'"> <div class="body__row"> @@ -50,7 +50,7 @@ <span class="body__row-label"><i class="cn-icon cn-icon-risk"></i> {{$t('entities.credit')}}:</span> <div class="body__row-value" :title="d.reputationScore">{{d.reputationScore || '-'}}</div> </div> - <div class="body__detail" @click="entityDetail({domain: d.domainName})">{{$t('overall.detail')}}></div> + <div class="body__detail" @click="entityDetail({name: d.domainName, type: 5})">{{$t('overall.detail')}}></div> </template> <template v-else-if="from === 'app'"> <div class="body__row"> @@ -65,7 +65,7 @@ <span class="body__row-label"><i class="cn-icon cn-icon-risk"></i> {{$t('entities.subcategory')}}:</span> <div class="body__row-value" :title="d.appSubategory">{{d.appSubategory || '-'}}</div> </div> - <div class="body__detail" @click="entityDetail({appId: d.appId})">{{$t('overall.detail')}}></div> + <div class="body__detail" @click="entityDetail({name: d.appId, type: 6})">{{$t('overall.detail')}}></div> </template> </div> </div> @@ -162,8 +162,8 @@ export default { wraps.scrollTop = 0 }) }, - entityDetail (params, tabs = []) { - this.$emit('showDetail', params, tabs) + entityDetail (params) { + this.$emit('showDetail', { ...params, icon: this.iconClass }) } } } diff --git a/src/components/layout/Header.vue b/src/components/layout/Header.vue index 41387605..72c015de 100644 --- a/src/components/layout/Header.vue +++ b/src/components/layout/Header.vue @@ -1,6 +1,5 @@ <template> <div class="cn-header"> -<!-- <div class="left-menu--pin" :class="false ? 'left-menu--pin-normal' : 'left-menu--pin-reverse'" @click="shrink"><i :class="{'icon-reverse': false}" class="el-icon-s-fold"></i></div>--> <!--导航面包屑--> <div class="header__left"> <span @click="shrink" class="shrink-button" :class="{'shrink-button--collapse': isShrink}"><i class="cn-icon cn-icon-expand"></i></span> @@ -47,7 +46,6 @@ </template> </el-dropdown> </div> -<!-- <change-password :cur-user="username" :show-dialog="showChangePin" @click="showPinDialog" @dialogClosed="dialogClosed"></change-password>--> <el-dialog v-model="showChangePin" width="30%" :before-close="handleClose"> @@ -84,6 +82,7 @@ import { useRoute } from 'vue-router' import { get, put } from '@/utils/http' import { entityType, storageKey } from '@/utils/constants' + export default { name: 'Header', data () { @@ -151,11 +150,7 @@ export default { computed: { breadcrumb () { const breadcrumb = this.breadcrumbMap.find(b => this.path === b.path) - const breadcrumbArray = breadcrumb ? [breadcrumb.parentName, breadcrumb.name] : [] - if (breadcrumbArray.length > 0 && breadcrumb.childName) { - breadcrumbArray.push(breadcrumb.childName) - } - return breadcrumbArray + return breadcrumb ? [breadcrumb.parentName, breadcrumb.name] : [] }, path () { const { path } = useRoute() @@ -167,9 +162,6 @@ export default { isShrink () { return this.$store.getters.getIsShrink }, - entityName () { - return this.$store.getters.entityName - }, storeFrom () { return this.$store.getters.from } @@ -182,10 +174,6 @@ export default { if (this.from !== n) { this.from = n } - }, - entityName (n) { - const breadcrumb = this.breadcrumbMap.find(b => b.path === '/entityExplorer') - breadcrumb.childName = n } }, mounted () { @@ -203,7 +191,6 @@ export default { changeLocal (lang) { if (lang !== localStorage.getItem('cn-language')) { localStorage.setItem('cn-language', lang) - // this.$i18n.locale = lang window.location.reload() } }, diff --git a/src/store/index.js b/src/store/index.js index 9570b0d2..aa844b9e 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -11,8 +11,7 @@ const store = createStore({ i18n: false, showEntityTypeSelector: false, // 在entity explore页面时,控制header显示实体类型选择框 - from: '', // entity type - entityName: '' // entity名称,用于header顶部面包屑展示 + from: '' // entity type } }, getters: { @@ -44,9 +43,6 @@ const store = createStore({ }, showEntityTypeSelector (state, show) { state.showEntityTypeSelector = show - }, - setEntityName (state, entityName) { - state.entityName = entityName } } }) diff --git a/src/utils/constants.js b/src/utils/constants.js index 1563f7f2..3e63a9f4 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -33,15 +33,14 @@ export const fromRoute = { galaxyProxy: 'galaxyProxy' } -/* panel类别和路由之间的映射 */ +/* panel类别和名称之间的映射 */ export const panelTypeAndRouteMapping = { trafficSummary: 1, networkAppPerformance: 2, dnsServiceInsights: 3, - ipEntityDetail: 4, // 此为clientIP + ipEntityDetail: 4, domainEntityDetail: 5, - appEntityDetail: 6, - serverIpEntityDetail: 7 + appEntityDetail: 6 } export const position = { diff --git a/src/views/charts/Chart.vue b/src/views/charts/Chart.vue index 2b5fea17..b590a36e 100644 --- a/src/views/charts/Chart.vue +++ b/src/views/charts/Chart.vue @@ -39,8 +39,12 @@ </template> <template #title>{{chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name}}</template> <template #operations> -<!-- <i class="cn-icon cn-icon-more-light"></i>--> -<!-- <i class="cn-icon cn-icon-refresh" @click="loadMap"></i>--> + <el-popover trigger="hover" placement="top" :content="chartInfo.remark" v-if="chartInfo.remark"> + <template #reference> + <span class="header__operation-btn"><i class="cn-icon el-icon-info"></i></span> + </template> + </el-popover> + <span class="header__operation-btn" @click="refresh"><i class="cn-icon cn-icon-refresh"></i></span> </template> <template #default> <div class="chart-drawing" :id="`chart${chartInfo.id}`"></div> @@ -78,7 +82,12 @@ <el-option v-for="item in chartPieTableTopOptions" :key="item.value" :value="item.value"> {{item.name}}</el-option> </el-select> </div> -<!-- <i class="cn-icon cn-icon-more-light margin-l-10"></i>--> + <el-popover trigger="hover" placement="top" :content="chartInfo.remark" v-if="chartInfo.remark"> + <template #reference> + <span class="header__operation-btn"><i class="cn-icon el-icon-info"></i></span> + </template> + </el-popover> + <span class="header__operation-btn" @click="refresh"><i class="cn-icon cn-icon-refresh"></i></span> </template> <template #default> <div class="chart-drawing" :id="`chart${chartInfo.id}`"></div> @@ -135,9 +144,12 @@ </template> <template #title>{{chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name}}</template> <template #operations> -<!-- <div class="header__operation header__operation--table"> - <span class="option__button"><i class="cn-icon cn-icon-download"></i></span> - </div>--> + <el-popover trigger="hover" placement="top" :content="chartInfo.remark" v-if="chartInfo.remark"> + <template #reference> + <span class="header__operation-btn"><i class="cn-icon el-icon-info"></i></span> + </template> + </el-popover> + <span class="header__operation-btn" @click="refresh"><i class="cn-icon cn-icon-refresh"></i></span> <div class="header__operation header__operation--table"> <el-select size="mini" @@ -578,7 +590,7 @@ export default { polygonTemplate.nonScalingStroke = true polygonTemplate.strokeWidth = 0.5 } - } else if (response.code != 200) { + } else if (response.code !== 200) { this.isError = true this.noData = true this.errorInfo = response.msg || response.message || 'Unknown' @@ -593,6 +605,9 @@ export default { getTargetPageData (pageNum, pageSize, tableData) { return this.$_.slice(tableData, (pageNum - 1) * pageSize, pageNum * pageSize) }, + refresh () { + this.initChart() + }, getTableTitle (data) { if (data.length > 0) { const dataColumns = Object.keys(data[0]) // 返回数据的字段 diff --git a/src/views/charts/Panel.vue b/src/views/charts/Panel.vue index 90984910..c01e9483 100644 --- a/src/views/charts/Panel.vue +++ b/src/views/charts/Panel.vue @@ -1,15 +1,6 @@ <template> - <div style="padding: 10px 0 20px 20px;"> - <div v-if="typeName" class="entity-detail-tool"> - <div> - <span @click="goBack" style="cursor: pointer;"><i class="cn-icon cn-icon-arrow-left-circle"></i></span> - <span style="padding-left: 15px; color: #333;">{{tabTitle}}</span> - </div> - <el-radio-group v-model="tab" size="mini" @change="changeTab"> - <el-radio-button v-for="tabTmp in tabs" :key="tabTmp.key" :label="tabTmp.key" >{{tabTmp.label}}</el-radio-button> - </el-radio-group> - </div> - <div class="cn-panel" id="cn-panel" :style="{height: typeName ? 'calc(100% - 80px)' : ''}"> + <div style="padding: 10px 0 20px 20px;" v-if="!isEntityDetail"> + <div class="cn-panel" id="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,6 +26,30 @@ </grid-layout>--> </div> </div> + <div class="cn-entity-detail" id="cn-entity-detail" v-else> + <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> + <div class="entity-detail__header"> + <el-tabs v-model="currentTab"> + <el-tab-pane + v-for="(tab, index) in detailTabs" + :label="tab.i18n ? $t(tab.i18n) : tab.name" + :name="`${tab.id}`" + :key="tab.id" + :ref="`chart-${tab.id}`" + @tab-click="changeTab(index)" + > + </el-tab-pane> + </el-tabs> + </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"></chart> + </div> + </div> + </div> </template> <script> @@ -46,14 +61,12 @@ import { getNowTime } from '@/utils/date-util' import Chart from './Chart' import DateTimeRange from '@/components/common/TimeRange/DateTimeRange' import TimeRefresh from '@/components/common/TimeRange/TimeRefresh' -import _ from 'lodash' export default { name: 'Panel', props: { - typeName: String, entity: Object, - tabs: Array + isEntityDetail: Boolean }, components: { Chart, @@ -62,42 +75,18 @@ export default { }, data () { return { - chartList: [] - } - }, - computed: { - tabTitle () { - let title - switch (this.typeName) { - case 'ipEntityDetail': { // client IP - title = this.$t('entities.ipDetail') - break - } - case 'serverIpEntityDetail': { - title = this.$t('entities.ipDetail') - break - } - case 'domainEntityDetail': { - title = this.$t('entities.domainDetail') - break - } - case 'appEntityDetail': { - title = this.$t('entities.appDetail') - break - } - default: break - } - return title + chartList: [], // 普通panel的chart + // entity详情的chart + detailTabs: [], + detailChartList: [], + currentTab: '' } }, async mounted () { await this.init() + this.currentTab = this.detailTabs[0].id }, setup (props, ctx) { - let tab = '' - if (!_.isEmpty(props.tabs)) { - tab = ref(props.tabs[0].key) - } // data const dateRangeValue = 60 const { startTime, endTime } = getNowTime(dateRangeValue) @@ -105,42 +94,44 @@ export default { const panel = ref({}) let panelType = 1 // 取得panel的type const { params } = useRoute() - panelType = props.typeName ? panelTypeAndRouteMapping[props.typeName] : panelTypeAndRouteMapping[params.typeName] + panelType = props.entity ? props.entity.type : panelTypeAndRouteMapping[params.typeName] return { panelType, panel, timeFilter, - api, - tab + api } }, methods: { - goBack () { - this.$emit('goBack') - }, - async changeTab (label) { - let routePath - if (label == 'clientIP') { - routePath = 'ipEntityDetail' - } else if (label == 'serverIP') { - routePath = 'serverIpEntityDetail' - } - this.panelType = panelTypeAndRouteMapping[routePath] - await this.init() - }, async init () { const panels = await getPanelList({ type: this.panelType }) if (panels && panels.length > 0) { this.panel = panels[0] } if (this.panel.id) { - this.chartList = (await getChartList({ panelId: this.panel.id, pageSize: -1 })).map(chart => { + const allCharts = (await getChartList({ panelId: this.panel.id, pageSize: -1 })).map(chart => { chart.i = chart.id 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 + } } }, + changeTab (i) { + this.currentTab = this.detailTabs[i].id + this.detailChartList = this.detailTabs[i].children + this.init() + }, recursionParamsConvert (chart) { chart.params = chart.params ? JSON.parse(chart.params) : null if (!this.$_.isEmpty(chart.children)) { @@ -180,9 +171,6 @@ export default { this.$refs[`chart-${chart.id}`] && this.$refs[`chart-${chart.id}`].reloadChart() }) } - }, - beforeUnmount () { - this.$store.commit('setEntityName', '') } } </script> diff --git a/src/views/entities/EntityExplorer.vue b/src/views/entities/EntityExplorer.vue index f888b031..535479e0 100644 --- a/src/views/entities/EntityExplorer.vue +++ b/src/views/entities/EntityExplorer.vue @@ -1,5 +1,5 @@ <template> - <div class="outer-box" v-show="!showDetail"> + <div class="outer-box"> <div class="cn-entities"> <el-input v-model="searchContentTemp" @@ -30,7 +30,11 @@ ></entity-list> </div> </div> - <cn-panel v-if="showDetail" :entity="currentEntity" :type-name="typeName" :tabs="panelTabs" @goBack="showDetail = false"></cn-panel> + <entity-detail + v-model:show-detail="showDetail" + top="5vh" + :show-close="false" + :entity="currentEntity"></entity-detail> </template> <script> @@ -38,8 +42,8 @@ import { entityType, entityFilterType } from '@/utils/constants' import LeftFilter from '@/components/entities/LeftFilter' import EntityList from '@/components/entities/EntityList' import { getEntityFilter, getEntityList, getEntityCount } from '@/utils/api' -import Panel from '@/views/charts/Panel' import { doubleQuotationToSingle } from '@/utils/tools' +import EntityDetail from '@/components/entities/EntityDetail' export default { name: 'EntityExplorer', data () { @@ -67,15 +71,13 @@ export default { total: 0 }, showDetail: false, - typeName: '', - currentEntity: {}, - panelTabs: [] + currentEntity: {} } }, components: { + EntityDetail, LeftFilter, - EntityList, - 'cn-panel': Panel + EntityList }, methods: { enter () { @@ -315,10 +317,8 @@ export default { }, entityDetail (entity, tabs) { const entityName = entity.domain || entity.ip || entity.appId - this.$store.commit('setEntityName', entityName) this.typeName = `${this.from.toLowerCase()}EntityDetail` this.currentEntity = entity - this.panelTabs = tabs this.showDetail = true } }, @@ -446,9 +446,6 @@ export default { this.search() } } - }, - showDetail (n) { - this.$store.commit('showEntityTypeSelector', !n) } }, computed: { |
