Merge branch 'dev' of https://git.mesalab.cn/cyber-narrator/cn-ui into dev
Conflicts: src/components/charts/StatisticsLegend.vue
This commit is contained in:
@@ -1,13 +1,21 @@
|
||||
.nz-chart-tooltip > div > div:nth-of-type(1) > div:nth-of-type(2) > div > div:nth-of-type(1){
|
||||
.nz-chart-tooltip .cn-chart-tooltip-box{
|
||||
display: flex;
|
||||
span {
|
||||
float: none;
|
||||
}
|
||||
> span:nth-of-type(2){
|
||||
.cn-chart-tooltip-content{
|
||||
flex: 1;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
text-overflow:ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size:14px;
|
||||
color:#666;
|
||||
font-weight:400;
|
||||
margin-left:2px ;
|
||||
}
|
||||
.cn-chart-tooltip-value{
|
||||
float:right;
|
||||
margin-left:20px;
|
||||
font-size:14px;
|
||||
color:#666;
|
||||
font-weight:900;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,3 +54,9 @@ th *:first-letter,
|
||||
.header__operations *:first-letter {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.outer-box {
|
||||
padding: 10px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
height: 44px;
|
||||
background-color: white;
|
||||
border: 1px solid #E6EAED;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.top-tool-right {
|
||||
@@ -134,7 +134,7 @@
|
||||
color: #666666;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
|
||||
|
||||
line-height: 40px;
|
||||
|
||||
&.sub-list-tab--active {
|
||||
@@ -156,7 +156,7 @@
|
||||
background-color: #e6eaed;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
|
||||
|
||||
user-select: none;
|
||||
color: #5f6368;
|
||||
cursor: ns-resize;
|
||||
@@ -188,7 +188,7 @@
|
||||
position: relative;
|
||||
padding: 0 20px;
|
||||
width: 100%;
|
||||
|
||||
|
||||
flex: auto;
|
||||
height: calc(100% - 58px);
|
||||
|
||||
@@ -356,7 +356,7 @@
|
||||
left: 270px !important;
|
||||
margin-top: -3px !important;
|
||||
box-shadow: none;
|
||||
|
||||
|
||||
border-radius: 0;
|
||||
border-color: #c7c7c7;
|
||||
|
||||
@@ -391,3 +391,6 @@
|
||||
.margin-r-20{
|
||||
margin-right: 20px;
|
||||
}
|
||||
.margin-l-10{
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
<template>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
ref="table"
|
||||
class="pie-table"
|
||||
:data="tableData"
|
||||
:data="pieTableData"
|
||||
style="width: 100%;border: 1px solid #E7EAED"
|
||||
:row-key="getRowKey"
|
||||
@expand-change="currentChange"
|
||||
current-row-key="domain"
|
||||
:expand-row-keys="expandRowKeys"
|
||||
:size="'mini'"
|
||||
:height="'100%'">
|
||||
<el-table-column type="expand" :min-width="'5%'">
|
||||
<template #default="props">
|
||||
<template #default="props" style="height: auto">
|
||||
<el-table
|
||||
class="expand-table"
|
||||
:data=" props.row.children"
|
||||
:data="childrenTableData"
|
||||
style="width: 100%;"
|
||||
:show-header="false"
|
||||
:size="'mini'"
|
||||
@@ -19,11 +24,21 @@
|
||||
min-width="5%">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-for="(item, index) in tableTitles"
|
||||
v-for="(item, index) in tableTitlesOther"
|
||||
:key="index"
|
||||
:min-width="item.width"
|
||||
:label="item.label"
|
||||
:prop="item.prop">
|
||||
:prop="item.prop"
|
||||
#default="{row}">
|
||||
<span v-if="item.prop === 'nameColumn'">
|
||||
{{nameColumn === 'fqdnCategoryName' ? row['fqdnCategoryName'] : row['reputationLevel'] }}
|
||||
</span>
|
||||
<span v-else-if="item.prop === 'bytes' || item.prop === 'packets' || item.prop === 'sessions'" >
|
||||
{{shortFormatter(row[item.prop])}}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{row[item.prop]}}
|
||||
</span>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
@@ -33,59 +48,163 @@
|
||||
:key="index"
|
||||
:min-width="item.width"
|
||||
:label="item.label"
|
||||
:prop="item.prop">
|
||||
:prop="item.prop"
|
||||
#default="{row}">
|
||||
<span v-if="item.prop === 'nameColumn'">
|
||||
{{nameColumn === 'fqdnCategoryName' ? row['categoryName'] : row['reputationLevel'] }}
|
||||
</span>
|
||||
<span v-else-if="item.prop === 'bytes' || item.prop === 'packets' || item.prop === 'sessions'" >
|
||||
{{shortFormatter(row[item.prop])}}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{row[item.prop]}}
|
||||
</span>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { shortFormatter } from '@/components/charts/chartFormatter'
|
||||
import { get } from '@/utils/http'
|
||||
import { replaceUrlPlaceholder } from '@/utils/tools'
|
||||
export default {
|
||||
name: 'PieTable',
|
||||
props: {
|
||||
tableData: Array
|
||||
tableData: Array,
|
||||
chartInfo: Object,
|
||||
order: String,
|
||||
startTime: {
|
||||
type: Number
|
||||
},
|
||||
endTime: {
|
||||
type: Number
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
tableData: {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
handler (n) {
|
||||
this.pieTableData = JSON.parse((JSON.stringify(n)))
|
||||
this.pieTableData.forEach(item => {
|
||||
item.children = []
|
||||
})
|
||||
}
|
||||
},
|
||||
pieTableData: {
|
||||
handler (n) {
|
||||
}
|
||||
},
|
||||
chartInfo: {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
handler (n) {
|
||||
if (n && n.params) {
|
||||
this.nameColumn = JSON.parse(n.params).nameColumn
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
nameColumn: '',
|
||||
pieTableData: [],
|
||||
childrenTableData: [],
|
||||
expandRowKeys: [],
|
||||
tableTitles: [
|
||||
{
|
||||
label: this.$t('chart.pieTable.domain'),
|
||||
prop: 'domain',
|
||||
width: '20%'
|
||||
},
|
||||
{
|
||||
label: this.$t('chart.pieTable.nameColumn'),
|
||||
prop: 'nameColumn',
|
||||
width: '22%'
|
||||
},
|
||||
{
|
||||
label: this.$t('chart.pieTable.sessions'),
|
||||
prop: 'sessions',
|
||||
width: '25%'
|
||||
width: '18%'
|
||||
},
|
||||
{
|
||||
label: this.$t('chart.pieTable.packets'),
|
||||
prop: 'packets',
|
||||
width: '25%'
|
||||
width: '18%'
|
||||
},
|
||||
{
|
||||
label: this.$t('chart.pieTable.bytes'),
|
||||
prop: 'bytes',
|
||||
width: '25%'
|
||||
width: '18%'
|
||||
}
|
||||
],
|
||||
tableTitlesOther: [
|
||||
{
|
||||
label: this.$t('chart.pieTable.serverIp'),
|
||||
prop: 'serverIp',
|
||||
width: '20%'
|
||||
},
|
||||
{
|
||||
label: this.$t('chart.pieTable.nameColumn'),
|
||||
prop: 'nameColumn',
|
||||
width: '22%'
|
||||
},
|
||||
{
|
||||
label: this.$t('chart.pieTable.sessions'),
|
||||
prop: 'sessions',
|
||||
width: '18%'
|
||||
},
|
||||
{
|
||||
label: this.$t('chart.pieTable.packets'),
|
||||
prop: 'packets',
|
||||
width: '18%'
|
||||
},
|
||||
{
|
||||
label: this.$t('chart.pieTable.bytes'),
|
||||
prop: 'bytes',
|
||||
width: '18%'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
shortFormatter: shortFormatter,
|
||||
// rowChange (echartParams) {
|
||||
// const nameColumnKey = this.nameColumn === 'fqdnCategoryName' ? 'categoryName' : 'reputationLevel'
|
||||
// const row = this.pieTableData.find(item => echartParams.name === item[nameColumnKey])
|
||||
// this.toggleRowExpansion(row)
|
||||
// },
|
||||
// toggleRowExpansion (row) {
|
||||
// this.$refs.table.toggleRowExpansion(row)
|
||||
// },
|
||||
currentChange (row, expandedRows) {
|
||||
if (this.expandRowKeys[0] && (row.domain === this.expandRowKeys[0])) {
|
||||
this.expandRowKeys = []
|
||||
} else {
|
||||
this.expandRowKeys = [row.domain]
|
||||
}
|
||||
|
||||
// this.$refs.table.toggleRowExpansion(row)
|
||||
const url = JSON.parse(this.chartInfo.params).urlChildrenTable
|
||||
const queryParams = { startTime: parseInt(this.startTime / 1000), endTime: parseInt(this.endTime / 1000), order: this.order, domain: row.domain }
|
||||
get(replaceUrlPlaceholder(url, queryParams)).then(response2 => {
|
||||
if (response2.code === 200) {
|
||||
this.childrenTableData = response2.data.result
|
||||
}
|
||||
})
|
||||
},
|
||||
getRowKey (row) {
|
||||
return row.domain
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/deep/ .el-table__expanded-cell[class*=cell]{
|
||||
padding: 0;
|
||||
}
|
||||
<style scoped lang="scss">
|
||||
/deep/ .el-table__expanded-cell[class*=cell]{
|
||||
padding: 0;
|
||||
}
|
||||
.expand-table /deep/ .el-table__body .el-table__row:last-of-type td{
|
||||
border: none;
|
||||
}
|
||||
@@ -98,5 +217,8 @@ export default {
|
||||
.expand-table{
|
||||
font-weight: 400;
|
||||
color: #606266;
|
||||
/deep/ .el-table__body-wrapper{
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -97,7 +97,6 @@ export default {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
handler (n) {
|
||||
console.info(n)
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -198,4 +197,4 @@ color: deepskyblue;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @description chart option和一些工具
|
||||
*/
|
||||
import { format } from 'echarts'
|
||||
import { shortFormatter } from './chartFormatter'
|
||||
import _ from 'lodash'
|
||||
export const chartColor = ['#5370C6', '#90CC74', '#FAC858', '#EE6666',
|
||||
'#73BFDE', '#3BA172', '#FC8452', '#9960B4',
|
||||
@@ -20,9 +21,7 @@ const line = {
|
||||
width: '20px',
|
||||
overflow: 'truncate'
|
||||
},
|
||||
// formatter: () =>{
|
||||
// return '1'
|
||||
// }
|
||||
formatter: axiosFormatter,
|
||||
className: 'nz-chart-tooltip',
|
||||
extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important'
|
||||
},
|
||||
@@ -30,7 +29,11 @@ const line = {
|
||||
type: 'time'
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: shortFormatter
|
||||
}
|
||||
|
||||
},
|
||||
animation: false,
|
||||
grid: {
|
||||
@@ -74,9 +77,7 @@ const lineWithStatistics = {
|
||||
width: '20px',
|
||||
overflow: 'truncate'
|
||||
},
|
||||
// formatter: () =>{
|
||||
// return '1'
|
||||
// }
|
||||
formatter: axiosFormatter,
|
||||
className: 'nz-chart-tooltip',
|
||||
extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important'
|
||||
},
|
||||
@@ -85,7 +86,11 @@ const lineWithStatistics = {
|
||||
},
|
||||
animation: false,
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: shortFormatter
|
||||
}
|
||||
|
||||
},
|
||||
color: chartColor,
|
||||
grid: {
|
||||
@@ -117,9 +122,7 @@ const lineStack = {
|
||||
width: '20px',
|
||||
overflow: 'truncate'
|
||||
},
|
||||
// formatter: () =>{
|
||||
// return '1'
|
||||
// }
|
||||
formatter: axiosFormatter,
|
||||
className: 'nz-chart-tooltip',
|
||||
extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important'
|
||||
},
|
||||
@@ -128,7 +131,11 @@ const lineStack = {
|
||||
},
|
||||
color: chartColor,
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: shortFormatter
|
||||
}
|
||||
|
||||
},
|
||||
grid: {
|
||||
left: 55,
|
||||
@@ -181,11 +188,12 @@ const pieWithTable = {
|
||||
itemHeight: 10, // 设置高度
|
||||
itemGap: 20,
|
||||
selectedMode: false,
|
||||
formatter: tooLongFormatter
|
||||
// formatter: tooLongFormatter
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
selectedMode: 'single',
|
||||
radius: ['42%', '70%'],
|
||||
center: ['30%', '50%'],
|
||||
data: [],
|
||||
@@ -293,6 +301,26 @@ export function getLayout (type) {
|
||||
function tooLongFormatter (name) {
|
||||
return format.truncateText(name, 110, '12')
|
||||
}
|
||||
// function axiosFormatter(params) {
|
||||
//
|
||||
// }
|
||||
function axiosFormatter (params) {
|
||||
let str = '<div>'
|
||||
const sum = 0
|
||||
params.forEach((item, i) => {
|
||||
const tData = item.data[0]
|
||||
if (i === 0) {
|
||||
str += '<div style="margin-bottom: 5px">'
|
||||
str += window.$dayJs.tz(tData).format('YYYY-MM-DD HH:mm:ss')
|
||||
str += '</div>'
|
||||
}
|
||||
str += '<div class="cn-chart-tooltip-box">'
|
||||
str += item.marker
|
||||
str += `<span class="cn-chart-tooltip-content">
|
||||
${item.seriesName}
|
||||
</span>`
|
||||
str += `<span class="cn-chart-tooltip-value">
|
||||
${shortFormatter(item.data[1])}
|
||||
</span>`
|
||||
str += '</div>'
|
||||
})
|
||||
str += '</div>'
|
||||
return str
|
||||
}
|
||||
|
||||
21
src/components/charts/chartFormatter.js
Normal file
21
src/components/charts/chartFormatter.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const shortList = [' ', 'K', 'Mil', 'Bil', 'Til', 'Quadrillion', 'Quintillion']
|
||||
function asciiCompute (num, ascii, units, dot = 2) {
|
||||
if (!num && num !== 0 && num !== '0') {
|
||||
return ''
|
||||
}
|
||||
num = Number(num)
|
||||
let carry = 0
|
||||
if (num > 1) {
|
||||
const log = Math.log(num) / Math.log(ascii)
|
||||
carry = parseInt(log)
|
||||
num = num / Math.pow(ascii, carry)
|
||||
}
|
||||
if (Number.isInteger(num)) {
|
||||
return num + ' ' + units[carry]
|
||||
} else {
|
||||
return num.toFixed(dot) + ' ' + units[carry]
|
||||
}
|
||||
}
|
||||
export function shortFormatter (value, index, dot = 2) {
|
||||
return asciiCompute(value, 1000, shortList, dot)
|
||||
}
|
||||
@@ -252,6 +252,110 @@
|
||||
flex: auto;
|
||||
overflow-y: auto;
|
||||
|
||||
.el-table {
|
||||
padding: 0 10px;
|
||||
|
||||
&:before {
|
||||
height: 0;
|
||||
}
|
||||
thead {
|
||||
color: #333;
|
||||
}
|
||||
th.is-leaf, td {
|
||||
border-bottom: none;
|
||||
}
|
||||
th {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
td {
|
||||
padding: 4px 0;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&>.cn-chart__echarts {
|
||||
.cn-chart__header {
|
||||
border-bottom: 1px solid $--content-right-background-color;
|
||||
.header__operations {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
|
||||
.header__operation.header__operation--table {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 22px;
|
||||
margin-left: 10px;
|
||||
color: $--color-primary;
|
||||
border: 1px solid $--color-primary;
|
||||
border-radius: $--border-radius-primary;
|
||||
|
||||
.option__button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
padding: 0 5px;
|
||||
cursor: pointer;
|
||||
background-color: white;
|
||||
transition: all linear .2s;
|
||||
}
|
||||
.option__button:hover {
|
||||
background-color: #EFF2F5;
|
||||
}
|
||||
.option__button.icon-group-item:first-of-type:not(:last-of-type) {
|
||||
padding: 0 5px 0 0;
|
||||
}
|
||||
.option__button.icon-group-item:last-of-type:not(:first-of-type) {
|
||||
padding: 0 0 0 5px;
|
||||
}
|
||||
.option__select {
|
||||
.el-input__inner {
|
||||
width: 120px;
|
||||
padding-right: 20px;
|
||||
border: none;
|
||||
height: 100%;
|
||||
line-height: 20px;
|
||||
color: $--color-primary;
|
||||
}
|
||||
.el-input__prefix > div {
|
||||
font-weight: normal;
|
||||
line-height: 19px;
|
||||
color: $--color-primary;
|
||||
}
|
||||
.el-input__suffix {
|
||||
display: flex;
|
||||
.el-input__suffix-inner {
|
||||
line-height: 14px;
|
||||
.el-select__caret {
|
||||
line-height: 14px;
|
||||
width: 16px;
|
||||
color: $--color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.option__select.select-column {
|
||||
.el-input__inner {
|
||||
width: 120px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
}
|
||||
.icon-group-divide {
|
||||
height: 14px;
|
||||
width: 1px;
|
||||
background-color: $--color-primary;
|
||||
}
|
||||
i {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.cn-chart__body {
|
||||
flex: auto;
|
||||
overflow-y: auto;
|
||||
|
||||
.el-table {
|
||||
padding: 0 10px;
|
||||
|
||||
@@ -282,3 +386,12 @@
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
//.cn-chart-select{
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// height: 22px;
|
||||
// margin-left: 10px;
|
||||
// color: #0091ff;
|
||||
// border: 1px solid #0091ff;
|
||||
// border-radius: 2px;
|
||||
//}
|
||||
|
||||
@@ -231,7 +231,6 @@ export default {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
handler (n, o) {
|
||||
// console.log(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
72
src/components/entities/LeftFilter.vue
Normal file
72
src/components/entities/LeftFilter.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<div class="entity-left-filter">
|
||||
<div class="filter__header">{{$t('entities.filter')}}</div>
|
||||
<div class="filter__body">
|
||||
<el-collapse v-model="active">
|
||||
<el-collapse-item
|
||||
v-for="(f, i) in filterData"
|
||||
:key="i"
|
||||
:title="f.title"
|
||||
:name="`${i}`"
|
||||
class="filter__collapse"
|
||||
>
|
||||
<el-tree
|
||||
:data="f.data"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<span>{{data[f.key]}}-{{data.count}}</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'LeftFilter',
|
||||
props: {
|
||||
filterData: Array
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
active: ['1']
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
console.info(this.filterData)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.entity-left-filter {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid $--right-box-border-color;
|
||||
|
||||
.filter__header {
|
||||
background-color: #FAFAFA;
|
||||
padding-left: 8px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
}
|
||||
.filter__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.filter__collapse {
|
||||
max-height: 50%;
|
||||
|
||||
.el-collapse-item__header {
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
10
src/components/entities/entities.scss
Normal file
10
src/components/entities/entities.scss
Normal file
@@ -0,0 +1,10 @@
|
||||
.cn-entities {
|
||||
display: grid;
|
||||
grid-template-rows: 32px auto;
|
||||
grid-template-columns: 250px auto;
|
||||
grid-gap: 10px 20px;
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
*/
|
||||
import { get, post } from '@/utils/http'
|
||||
import { sortByOrderNum } from '@/permission'
|
||||
import { storageKey, iso36112 } from '@/utils/constants'
|
||||
|
||||
export const api = {
|
||||
// 系统相关
|
||||
@@ -14,7 +13,10 @@ export const api = {
|
||||
dict: '/sys/dict',
|
||||
// 业务
|
||||
panel: '/visual/panel',
|
||||
chart: '/visual/chart'
|
||||
chart: '/visual/chart',
|
||||
entityIpFilter: '/interface/entity/ip/filter',
|
||||
entityDomainFilter: '/interface/entity/domain/filter',
|
||||
entityAppFilter: '/interface/entity/app/filter'
|
||||
}
|
||||
/* panel */
|
||||
export async function getPanelList (params) {
|
||||
@@ -30,6 +32,18 @@ export async function getChartList (params) {
|
||||
export async function getChart (id) {
|
||||
return await getData(`${api.chart}/${id}`)
|
||||
}
|
||||
/* ip类型entity过滤器数据 */
|
||||
export async function getEntityIpFilterList (params) {
|
||||
return await getData(api.entityIpFilter, params, true)
|
||||
}
|
||||
/* domain类型entity过滤器数据 */
|
||||
export async function getEntityDomainFilterList (params) {
|
||||
return await getData(api.entityDomainFilter, params, true)
|
||||
}
|
||||
/* app类型entity过滤器数据 */
|
||||
export async function getEntityAppFilterList (params) {
|
||||
return await getData(api.entityAppFilter, params, true)
|
||||
}
|
||||
/* 字典 */
|
||||
export async function getDictList (params) {
|
||||
return await getData(api.dict, params, true)
|
||||
@@ -39,7 +53,7 @@ export async function getData (url, params = {}, isQueryList) {
|
||||
const request = new Promise(resolve => {
|
||||
get(url, params).then(response => {
|
||||
if (response.code === 200) {
|
||||
resolve(isQueryList ? response.data.list : response.data)
|
||||
resolve(isQueryList ? response.data.list || response.data.result : response.data || response.data.result)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -44,5 +44,12 @@ export const position = {
|
||||
}
|
||||
}
|
||||
|
||||
export const entityType = {
|
||||
ip: 'IP',
|
||||
domain: 'Domain',
|
||||
app: 'APP'
|
||||
}
|
||||
|
||||
export const chartTableDefaultPageSize = 10 // table类型图表默认每页数据量
|
||||
export const chartTableTopOptions = [10, 100] // table类型图表的TOP-N选项
|
||||
export const chartPieTableTopOptions = [{ name: 'Sessions', value: 'sessions' }, { name: 'Packets', value: 'packets' }, { name: 'Bytes', value: 'bytes' }] // table类型图表的TOP-N选项
|
||||
|
||||
@@ -433,7 +433,7 @@ export function getCapitalGeo (countryId) {
|
||||
return data[countryId]
|
||||
}
|
||||
|
||||
export function JSONParse (data) {
|
||||
function JSONParse (data) {
|
||||
const firstParse = JSON.parse(data)
|
||||
if (typeof firstParse === 'string') {
|
||||
return JSON.parse(firstParse)
|
||||
|
||||
@@ -44,21 +44,22 @@
|
||||
>
|
||||
<template #title v-if="layout.indexOf(layoutConstant.HEADER) > -1">
|
||||
{{chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name}}
|
||||
<span v-if="isEchartsWithTable">
|
||||
</template>
|
||||
<template #operations v-if="layout.indexOf(layoutConstant.HEADER) > -1">
|
||||
<div class="header__operation header__operation--table">
|
||||
<el-select
|
||||
size="mini"
|
||||
v-model="table.limit"
|
||||
v-model="orderPieTable"
|
||||
class="option__select select-topn"
|
||||
placeholder=""
|
||||
popper-class="option-popper"
|
||||
@change="orderPieTableChange"
|
||||
>
|
||||
<el-option v-for="item in chartTableTopOptions" :key="item" :value="item">TOP {{item}}</el-option>
|
||||
<el-option v-for="item in chartPieTableTopOptions" :key="item.value" :value="item.value"> {{item.name}}</el-option>
|
||||
<template #prefix>TOP </template>
|
||||
</el-select>
|
||||
</span>
|
||||
</template>
|
||||
<template #operations v-if="layout.indexOf(layoutConstant.HEADER) > -1">
|
||||
<i class="cn-icon cn-icon-more-light"></i>
|
||||
</div>
|
||||
<i class="cn-icon cn-icon-more-light margin-l-10"></i>
|
||||
</template>
|
||||
<template #default>
|
||||
<div class="chart-drawing" :id="`chart${chartInfo.id}`"></div>
|
||||
@@ -66,7 +67,7 @@
|
||||
<template #footer v-if="layout.indexOf(layoutConstant.FOOTER) > -1" :class="{}">
|
||||
<!-- 带Table的饼图,展示Table -->
|
||||
<template v-if="isEchartsWithTable">
|
||||
<pie-table :tableData="pieTableData" ref="pieTable"/>
|
||||
<pie-table :tableData="pieTableData" ref="pieTable" :chartInfo="chartInfo" :start-time="startTime" :end-time="endTime" :order="orderPieTable"/>
|
||||
</template>
|
||||
<template v-else-if="isEchartsWithStatistics">
|
||||
<statistics-legend :data="statisticsData"></statistics-legend>
|
||||
@@ -102,6 +103,7 @@
|
||||
class="option__select select-topn"
|
||||
placeholder=""
|
||||
popper-class="option-popper"
|
||||
@change="tabelLimtChange"
|
||||
>
|
||||
<el-option v-for="item in chartTableTopOptions" :key="item" :value="item">TOP {{item}}</el-option>
|
||||
<template #prefix>TOP </template>
|
||||
@@ -114,6 +116,7 @@
|
||||
class="option__select select-column"
|
||||
:placeholder="$t('common.field')"
|
||||
popper-class="option-popper"
|
||||
@change="tabelLimtChange"
|
||||
>
|
||||
<el-option v-for="item in table.tableColumns" :key="item" :value="item">{{item}}</el-option>
|
||||
</el-select>
|
||||
@@ -140,7 +143,6 @@
|
||||
import * as echarts from 'echarts'
|
||||
import * as am4Core from '@amcharts/amcharts4/core'
|
||||
import * as am4Maps from '@amcharts/amcharts4/maps'
|
||||
import am4GeoDataWorldLow from '@amcharts/amcharts4-geodata/worldChinaLow'
|
||||
import { shallowRef } from 'vue'
|
||||
|
||||
import {
|
||||
@@ -166,9 +168,9 @@ import ChartMap from '@/components/charts/ChartMap'
|
||||
import PieTable from '@/components/charts/PieTable'
|
||||
import StatisticsLegend from '@/components/charts/StatisticsLegend'
|
||||
import ChartTablePagination from '@/components/charts/ChartTablePagination'
|
||||
import { chartTableDefaultPageSize, chartTableTopOptions } from '@/utils/constants'
|
||||
import { chartTableDefaultPageSize, chartTableTopOptions, storageKey, chartPieTableTopOptions } from '@/utils/constants'
|
||||
import { get } from '@/utils/http'
|
||||
import { replaceUrlPlaceholder, getCapitalGeo } from '@/utils/tools'
|
||||
import { replaceUrlPlaceholder, getCapitalGeo, getGeoData } from '@/utils/tools'
|
||||
|
||||
export default {
|
||||
name: 'Chart',
|
||||
@@ -195,7 +197,7 @@ export default {
|
||||
table: {
|
||||
pageSize: chartTableDefaultPageSize,
|
||||
limit: chartTableTopOptions[0], // top-n
|
||||
orderBy: '',
|
||||
orderBy: 'sessions',
|
||||
tableColumns: [], // table字段
|
||||
tableData: [], // table的所有数据
|
||||
currentPageData: [] // table当前页的数据
|
||||
@@ -206,11 +208,14 @@ export default {
|
||||
icon: ''
|
||||
},
|
||||
activeTab: '',
|
||||
statisticsData: []
|
||||
statisticsData: [],
|
||||
orderPieTable: chartPieTableTopOptions[0].value,
|
||||
selectPieChartName: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initChart () {
|
||||
const self = this
|
||||
const chartParams = this.chartInfo.params ? JSON.parse(this.chartInfo.params) : null // 图表参数
|
||||
if (this.isMap) {
|
||||
this.myChart = this.initMap(`chart${this.chartInfo.id}`)
|
||||
@@ -307,41 +312,7 @@ export default {
|
||||
this.myChart = echarts.init(dom)
|
||||
if (chartParams) {
|
||||
if (this.isEchartsWithTable) {
|
||||
const queryParams = { startTime: parseInt(this.startTime / 1000), endTime: parseInt(this.endTime / 1000), limit: 10 } // 统计数据的查询参数
|
||||
const tableQueryParams = { startTime: parseInt(this.startTime / 1000), endTime: this.endTime, limit: 10 } // 统计数据的查询参数
|
||||
get(replaceUrlPlaceholder(chartParams.url, queryParams)).then(response => {
|
||||
if (response.code === 200) {
|
||||
const data = response.data.result
|
||||
this.chartOption.series[0].data = data.map(d => {
|
||||
return {
|
||||
data: d,
|
||||
name: d[chartParams.nameColumn],
|
||||
value: parseInt(d[chartParams.valueColumn])
|
||||
}
|
||||
})
|
||||
if (this.chartOption.series[0].data && this.chartOption.series[0].data.length > 10) { // pieWithTable 图例超过10个改为滚动显示
|
||||
this.chartOption.legend.type = 'scroll'
|
||||
}
|
||||
this.myChart.setOption(this.chartOption)
|
||||
this.$nextTick(() => {
|
||||
this.myChart.resize()
|
||||
})
|
||||
tableQueryParams[chartParams.nameColumn] = data[0][chartParams.nameColumn]
|
||||
get(replaceUrlPlaceholder(chartParams.urlTable, tableQueryParams)).then(response2 => {
|
||||
if (response2.code === 200) {
|
||||
this.pieTableData = response2.data.result
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
this.myChart.on('click', function (echartParams) {
|
||||
get(replaceUrlPlaceholder(chartParams.urlTable, tableQueryParams)).then(response => {
|
||||
if (response.code === 200) {
|
||||
this.pieTableData = response.data.result
|
||||
}
|
||||
})
|
||||
})
|
||||
this.chartWithPieTableInit(chartParams)
|
||||
} else if (this.isEchartsWithStatistics) {
|
||||
const queryParams = { startTime: parseInt(this.startTime / 1000), endTime: parseInt(this.endTime / 1000) }
|
||||
get(replaceUrlPlaceholder(chartParams.url, queryParams)).then(response => {
|
||||
@@ -387,21 +358,7 @@ export default {
|
||||
}
|
||||
} else if (this.isTable) {
|
||||
if (chartParams) {
|
||||
const queryParams = { startTime: parseInt(this.startTime / 1000), endTime: parseInt(this.endTime / 1000), limit: 100, order: 'sessions' }
|
||||
get(replaceUrlPlaceholder(chartParams.url, queryParams)).then(response => {
|
||||
if (response.code === 200) {
|
||||
const tableColumns = new Set()
|
||||
response.data.result.forEach(d => {
|
||||
Object.keys(d).forEach(k => {
|
||||
tableColumns.add(k)
|
||||
})
|
||||
})
|
||||
this.table.tableColumns = tableColumns
|
||||
this.table.tableData = response.data.result
|
||||
this.table.orderBy = this.table.tableColumns[0]
|
||||
this.table.currentPageData = this.getTargetPageData(1, this.table.pageSize, this.table.tableData)
|
||||
}
|
||||
})
|
||||
this.initChartTable(chartParams)
|
||||
}
|
||||
} else if (this.isSingleValue) {
|
||||
if (chartParams) {
|
||||
@@ -430,7 +387,7 @@ export default {
|
||||
},
|
||||
initMap (id) {
|
||||
const chart = am4Core.create(id, am4Maps.MapChart)
|
||||
chart.geodata = am4GeoDataWorldLow
|
||||
chart.geodata = getGeoData(storageKey.iso36112WorldLow)
|
||||
chart.projection = new am4Maps.projections.Miller()
|
||||
const polygonSeries = chart.series.push(new am4Maps.MapPolygonSeries())
|
||||
polygonSeries.useGeodata = true
|
||||
@@ -448,6 +405,84 @@ export default {
|
||||
},
|
||||
getTargetPageData (pageNum, pageSize, tableData) {
|
||||
return this.$_.slice(tableData, (pageNum - 1) * pageSize, pageNum * pageSize)
|
||||
},
|
||||
orderPieTableChange () {
|
||||
const chartParams = this.chartInfo.params ? JSON.parse(this.chartInfo.params) : null // 图表参数
|
||||
this.myChart.off('click')
|
||||
this.chartWithPieTableInit(chartParams)
|
||||
},
|
||||
chartWithPieTableInit (chartParams) {
|
||||
const self = this
|
||||
chartParams.valueColumn = this.orderPieTable
|
||||
const queryParams = { startTime: parseInt(this.startTime / 1000), endTime: parseInt(this.endTime / 1000), limit: 10, order: this.orderPieTable } // 统计数据的查询参数
|
||||
const tableQueryParams = { startTime: parseInt(this.startTime / 1000), endTime: this.endTime, limit: 10, order: this.orderPieTable } // 统计数据的查询参数
|
||||
tableQueryParams[chartParams.nameColumn] = [] // 处理两个图表不一样的地方
|
||||
get(replaceUrlPlaceholder(chartParams.url, queryParams)).then(response => {
|
||||
if (response.code === 200) {
|
||||
const data = response.data.result
|
||||
this.chartOption.series[0].data = data.map(d => {
|
||||
if (chartParams.nameColumn === 'fqdnCategoryName') {
|
||||
tableQueryParams[chartParams.nameColumn].push(d[chartParams.nameColumn])
|
||||
}
|
||||
return {
|
||||
data: d,
|
||||
name: d[chartParams.nameColumn],
|
||||
value: parseInt(d[chartParams.valueColumn])
|
||||
}
|
||||
})
|
||||
if (tableQueryParams[chartParams.nameColumn].length) {
|
||||
// tableQueryParams[chartParams.nameColumn] = JSON.stringify(tableQueryParams[chartParams.nameColumn])
|
||||
}
|
||||
if (this.chartOption.series[0].data && this.chartOption.series[0].data.length > 10) { // pieWithTable 图例超过10个改为滚动显示
|
||||
this.chartOption.legend.type = 'scroll'
|
||||
}
|
||||
this.myChart.setOption(this.chartOption)
|
||||
this.$nextTick(() => {
|
||||
this.myChart.resize()
|
||||
})
|
||||
get(replaceUrlPlaceholder(chartParams.urlTable, tableQueryParams)).then(response2 => {
|
||||
if (response2.code === 200) {
|
||||
this.pieTableData = response2.data.result
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
this.myChart.on('click', function (echartParams) {
|
||||
const childrenParams = { startTime: parseInt(self.startTime / 1000), endTime: parseInt(self.endTime / 1000), limit: 10 }
|
||||
childrenParams[chartParams.nameColumn] = echartParams.name
|
||||
if (self.selectPieChartName === echartParams.name) {
|
||||
self.selectPieChartName = ''
|
||||
childrenParams[chartParams.nameColumn] = tableQueryParams[chartParams.nameColumn]
|
||||
} else {
|
||||
self.selectPieChartName = echartParams.name
|
||||
}
|
||||
get(replaceUrlPlaceholder(chartParams.urlTable, childrenParams)).then(response2 => {
|
||||
if (response2.code === 200) {
|
||||
self.pieTableData = response2.data.result
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
tabelLimtChange () {
|
||||
const chartParams = this.chartInfo.params ? JSON.parse(this.chartInfo.params) : null // 图表参数
|
||||
this.initChartTable(chartParams)
|
||||
},
|
||||
initChartTable (chartParams) {
|
||||
const queryParams = { startTime: parseInt(this.startTime / 1000), endTime: parseInt(this.endTime / 1000), limit: this.table.limit, order: this.table.orderBy }
|
||||
get(replaceUrlPlaceholder(chartParams.url, queryParams)).then(response => {
|
||||
if (response.code === 200) {
|
||||
const tableColumns = new Set()
|
||||
response.data.result.forEach(d => {
|
||||
Object.keys(d).forEach(k => {
|
||||
tableColumns.add(k)
|
||||
})
|
||||
})
|
||||
this.table.tableColumns = Array.from(tableColumns)
|
||||
this.table.tableData = response.data.result
|
||||
this.table.currentPageData = this.getTargetPageData(1, this.table.pageSize, this.table.tableData)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -470,6 +505,7 @@ export default {
|
||||
chartInfo,
|
||||
layoutConstant,
|
||||
chartTableTopOptions,
|
||||
chartPieTableTopOptions,
|
||||
chartOption: getOption(props.chart.type),
|
||||
isEcharts: isEcharts(props.chart.type),
|
||||
isEchartsWithTable: isEchartsWithTable(props.chart.type),
|
||||
|
||||
@@ -101,6 +101,7 @@ export default {
|
||||
chart.i = chart.id
|
||||
return chart
|
||||
})
|
||||
// this.chartList = [this.chartList[7], this.chartList[12]]
|
||||
}
|
||||
},
|
||||
timeRefreshChange () {
|
||||
|
||||
@@ -1,13 +1,130 @@
|
||||
<template>
|
||||
<div></div>
|
||||
<div class="outer-box">
|
||||
<div class="cn-entities">
|
||||
<el-select
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
v-model="filterType"
|
||||
>
|
||||
<el-option v-for="(value, key) in entityType" :key="key" :label="value" :value="key"></el-option>
|
||||
</el-select>
|
||||
<el-input
|
||||
v-model="searchContent"
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
type="text"
|
||||
>
|
||||
<template #prefix>
|
||||
<span style="padding-left: 4px"><i class="el-icon-search"></i></span>
|
||||
</template>
|
||||
</el-input>
|
||||
<!-- 筛选区域 -->
|
||||
<left-filter
|
||||
:filter-data="filterData"
|
||||
></left-filter>
|
||||
<!-- 内容区域 -->
|
||||
<div style="background-color: lightcyan"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { entityType } from '@/utils/constants'
|
||||
import { ref } from 'vue'
|
||||
import LeftFilter from '@/components/entities/LeftFilter'
|
||||
import { getEntityIpFilterList, getEntityDomainFilterList, getEntityAppFilterList } from '@/utils/api'
|
||||
export default {
|
||||
name: 'EntityExplorer'
|
||||
name: 'EntityExplorer',
|
||||
data () {
|
||||
return {
|
||||
searchContent: '',
|
||||
filterData: []
|
||||
}
|
||||
},
|
||||
components: {
|
||||
LeftFilter
|
||||
},
|
||||
methods: {
|
||||
async loadData (filterType, conditionGroup) {
|
||||
let data
|
||||
switch (filterType) {
|
||||
case 'ip': {
|
||||
data = await getEntityIpFilterList({ type: conditionGroup })
|
||||
break
|
||||
}
|
||||
case 'domain': {
|
||||
data = await getEntityDomainFilterList({ type: conditionGroup })
|
||||
break
|
||||
}
|
||||
case 'app': {
|
||||
data = await getEntityAppFilterList({ type: conditionGroup })
|
||||
break
|
||||
}
|
||||
default: break
|
||||
}
|
||||
return data
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
filterType (n) {
|
||||
const requests = []
|
||||
const data = []
|
||||
switch (n) {
|
||||
case 'ip': {
|
||||
requests.push(this.loadData(n, 'country'))
|
||||
requests.push(this.loadData(n, 'asn'))
|
||||
data.push({ title: this.$t('entities.countryOrRegion'), key: 'country' })
|
||||
data.push({ title: this.$t('entities.systemNumber'), key: 'asn' })
|
||||
break
|
||||
}
|
||||
case 'domain': {
|
||||
requests.push(this.loadData(n, 'group'))
|
||||
requests.push(this.loadData(n, 'level'))
|
||||
data.push({ title: this.$t('entities.group'), key: 'group' })
|
||||
data.push({ title: this.$t('entities.level'), key: 'level' })
|
||||
break
|
||||
}
|
||||
case 'app': {
|
||||
requests.push(this.loadData(n, 'category'))
|
||||
requests.push(this.loadData(n, 'risk'))
|
||||
data.push({ title: this.$t('entities.category'), key: 'category' })
|
||||
data.push({ title: this.$t('entities.risk'), key: 'risk' })
|
||||
break
|
||||
}
|
||||
default: break
|
||||
}
|
||||
Promise.all(requests).then(responses => {
|
||||
data.forEach((d, i) => {
|
||||
d.data = responses[i]
|
||||
})
|
||||
this.filterData = data
|
||||
})
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
const country = await this.loadData(this.filterType, 'country')
|
||||
const asn = await this.loadData(this.filterType, 'asn')
|
||||
this.filterData.push({
|
||||
title: this.$t('entities.countryOrRegion'),
|
||||
key: 'country',
|
||||
data: country
|
||||
})
|
||||
this.filterData.push({
|
||||
title: this.$t('entities.systemNumber'),
|
||||
key: 'asn',
|
||||
data: asn
|
||||
})
|
||||
},
|
||||
setup () {
|
||||
const filterType = ref(Object.keys(entityType)[0])
|
||||
return {
|
||||
entityType,
|
||||
filterType
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~@/components/entities/entities.scss';
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user