summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorchenjinsong <[email protected]>2021-09-15 12:19:13 +0800
committerchenjinsong <[email protected]>2021-09-15 12:19:13 +0800
commit8093f4bf934b03927c1ae6eb8a9c0880edcb4607 (patch)
tree752bdcdec1c096e2a9e23c78a1a60f2816d7fc43
parentb4a03aa7ceb4ef2eedf69b321a243df32b11233e (diff)
feat: entity详情(未完成)、修复一些问题
-rw-r--r--src/components/charts/ChartMap.vue2
-rw-r--r--src/components/charts/panel.scss93
-rw-r--r--src/components/entities/EntityDetail.vue60
-rw-r--r--src/components/entities/EntityList.vue10
-rw-r--r--src/components/layout/Header.vue17
-rw-r--r--src/store/index.js6
-rw-r--r--src/utils/constants.js7
-rw-r--r--src/views/charts/Chart.vue29
-rw-r--r--src/views/charts/Panel.vue116
-rw-r--r--src/views/entities/EntityExplorer.vue23
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>&nbsp;{{$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>&nbsp;{{$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&#45;&#45;pin" :class="false ? 'left-menu&#45;&#45;pin-normal' : 'left-menu&#45;&#45;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">&nbsp{{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&#45;&#45;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: {