CN-333 feat : Dashboard--dns 实时告警信息图表开发 以及CN-332的一些样式修改

This commit is contained in:
zhangxiaolong
2022-03-07 09:41:11 +08:00
28 changed files with 671 additions and 1103 deletions

View File

@@ -40,6 +40,7 @@
@import './views/charts/ChartTwoSituationStatistics';
@import './views/charts/chartAlarmInfo';
@import './views/chartHeader';
@import './views/charts/chartMap';
//@import '../chart';

View File

@@ -0,0 +1,60 @@
.cn-chart__map {
display: flex;
flex-direction: column;
height: 100%;
.map-drawing {
flex: 1;
}
.map-chart__legends {
flex-basis: 116px;
display: flex;
justify-content: center;
align-items: flex-start;
padding: 0 30px;
.map-chart__legend {
padding-bottom: 5px;
flex-grow: 0;
flex-shrink: 1;
display: flex;
flex-direction: column;
align-items: center;
border: 1px solid #E0E6EF;
border-left-color: transparent;
border-right-color: transparent;;
cursor: pointer;
&:first-of-type {
border-left-color: #E0E6EF;
}
&:last-of-type {
border-right-color: #E0E6EF;
}
&.map-chart__legend--active {
border-color: #59ABFF;
.legend__value {
color: #1890FF;
}
}
.legend__circle-marker {
flex: 0 0 12px;
margin: 12px 0 8px 0;
width: 12px;
border-radius: 50%;
}
.legend__value {
font-size: 18px;
font-weight: bold;
color: #333333;
}
.legend__name {
padding: 0 10px;
color: #666666;
font-size: 12px;
text-align: center;
}
}
}
}

View File

@@ -126,7 +126,8 @@
import { get } from '@/utils/http'
import { api } from '@/utils/api'
import * as echarts from 'echarts'
import { getChartColor, entityListLineOption } from '@/views/charts/charts/chart-options'
import { entityListLineOption } from '@/views/charts/charts/chart-options'
import { getChartColor } from '@/views/charts/charts/tools'
import { legendMapping } from '@/views/charts/charts/chart-table-title'
import unitConvert from '@/utils/unit-convert'
import { unitTypes } from '@/utils/constants'

View File

@@ -84,15 +84,17 @@ export const api = {
victimLocation: '/interface/detection/security/filter/victimLocation',
eventSeverity: '/interface/detection/security/filter/severity',
listBasic: '/interface/detection/security/list/basic',
listCount: '/interface/detection/security/list/count',
overviewBasic: '/interface/detection/security/detail/overview/basic',
overviewEvent: '/interface/detection/security/detail/overview/event'
},
performanceEvent: {
eventSeverityTrend: '/interface/detection/performance/filter/severityTrend',
securityType: '/interface/detection/performance/filter/eventType',
eventType: '/interface/detection/performance/filter/eventType',
eventSeverity: '/interface/detection/performance/filter/severity',
activeEntity: '/interface/detection/performance/filter/activeEntity',
listBasic: '/interface/detection/performance/list/basic',
listCount: '/interface/detection/performance/list/count',
overviewBasic: '/interface/detection/performance/detail/overview/basic'
}
},

View File

@@ -130,7 +130,19 @@ export const unitTypes = {
export const chartTableDefaultPageSize = 10 // table类型图表默认每页数据量
export const chartTableTopOptions = [10, 100] // table类型图表的TOP-N选项
export const chartActiveIpTableOrderOptions = ['machine'] // active ip table类型图表的order 选项
// table类型图表的TOP-N选项
// table类型图表column映射
export const chartTableColumnMapping = {
sessions: 'overall.sessions',
packets: 'overall.packets',
bytes: 'overall.bytes',
clientIp: 'overall.clientIp',
serverIp: 'overall.serverIp',
domain: 'overall.domain',
appName: 'overall.appName',
queryRate: 'dns.queryRate',
dnsLatency: 'dns.averageResolveLatency',
responseFailRate: 'dns.responseFailureRate'
}
export const chartPieTableTopOptions = [
{ name: 'Sessions', value: 'sessions' },
{ name: 'Packets', value: 'packets' },
@@ -170,6 +182,20 @@ export const detectionPageType = {
performanceEvent: 'performanceEvent'
}
export const dnsServerRole = {
RTDNS: 'RTDNS',
TLDNS: 'TLDNS',
OPRDNS: 'OPRDNS',
ADNS: 'ADNS',
SBDNS: 'SBDNS',
RTDNSM: 'RTDNSM'
}
export const chartColor = ['#5370C6', '#90CC74', '#FAC858', '#EE6666',
'#73BFDE', '#3BA172', '#FC8452', '#9960B4',
'#E97CCC', '#FEA69E', '#0F8AB2', '#57CBAC',
'#5888BC', '#63B6AC', '#EDC6B2', '#D5746B']
export const iso36112 = {
[storageKey.iso36112Capital]: 'data/countriesWithCapital',
[storageKey.iso36112WorldLow]: 'worldChinaLow',

View File

@@ -29,7 +29,7 @@ export function getMillisecond (time) {
ms = Math.floor(time * (10 ** (0 - difference)))
}
}
return ms
return ms ? Number(ms) : null
}
// 初始化日期
export function getNowTime (interval) {
@@ -40,3 +40,7 @@ export function getNowTime (interval) {
endTime
}
}
// 日期格式转换
export function rTime (date) {
return window.$dayJs.tz(new Date(date)).format('YYYY-MM-DD HH:mm:ss')
}

View File

@@ -444,12 +444,18 @@ export function lineToHump (name) {
}
// 下划线转换空格首位大写
export function lineToSpace (name) {
if (_.isEmpty(name)) {
return ''
}
return _.upperFirst(name.replace(/\_(\w)/g, function (all, letter) {
return ` ${letter.toUpperCase()}`
}))
}
// 驼峰转换下划线
export function humpToLine (name) {
if (_.isEmpty(name)) {
return ''
}
return name.replace(/([A-Z])/g, '_$1').toLowerCase()
}
// 搜索功能:对象转字符串

View File

@@ -16,6 +16,7 @@
:chart-data="chartData"
:query-params="queryParams"
:entity="entity"
@query="query"
@showLoading="showLoading"
></chart-map>
@@ -79,7 +80,7 @@
></chart-ip-open-port-bar>
<chart-table
v-else-if="isTable && isCurrentTable"
v-else-if="isTable && isBasicTable"
:chart-info="chartInfo"
:chart-data="chartData"
:table="table"
@@ -245,7 +246,7 @@ import {
isEchartsLine,
isSingleValue,
isTable,
isCurrentTable,
isBasicTable,
isActiveIpTable,
isTitle,
isMap,
@@ -371,6 +372,9 @@ export default {
`chart${this.chartInfo.id}`
)
},
query (params) {
this.$emit('query', params)
}
},
watch: {
chartData: {
@@ -401,7 +405,7 @@ export default {
),
isRelationShip: isRelationShip(props.chartInfo.type),
isTable: isTable(props.chartInfo.type),
isCurrentTable: isCurrentTable(props.chartInfo.type),
isBasicTable: isBasicTable(props.chartInfo.type),
isActiveIpTable: isActiveIpTable(props.chartInfo.type),
isMap: isMap(props.chartInfo.type),
isTitle: isTitle(props.chartInfo.type),

View File

@@ -27,6 +27,8 @@
{{ chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name }}
</div>
<template v-if="isCurrentTable">
<div class="chart-header__title" v-else-if="!isBasicTable" :class="{'chart-header__title--block': isBlock}" :title="chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name">{{chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name}}</div>
<template v-if="isBasicTable">
<div class="chart-header__title">
<span :title="chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name">{{
chartInfo.i18n ? $t(chartInfo.i18n) : chartInfo.name
@@ -257,6 +259,7 @@ export default {
},
data() {
return {
chartTableColumnMapping,
dropdownMenuShow: false,
errorText: '',
isFocus:false,
@@ -369,7 +372,7 @@ export default {
isBlock: isBlock(props.chartInfo.type),
isTabs: isTabs(props.chartInfo.type),
isTable: isTable(props.chartInfo.type),
isCurrentTable: isCurrentTable(props.chartInfo.type),
isBasicTable: isBasicTable(props.chartInfo.type),
isActiveIpTable: isActiveIpTable(props.chartInfo.type),
isEchartsWithTable: isEchartsWithTable(props.chartInfo.type),
isGroup: isGroup(props.chartInfo.type),

View File

@@ -48,7 +48,7 @@
<script>
import ChartHeader from './ChartHeader'
import Chart from '@/views/charts/Chart'
import testData from './charts/testData'
import { dnsServerRole, chartPieTableTopOptions, chartTableDefaultPageSize, chartTableTopOptions } from '@/utils/constants'
import {
isEcharts,
isSingleValue,
@@ -81,7 +81,7 @@ import {
import { tableTitleMapping, legendMapping } from '@/views/charts/charts/chart-table-title'
import { replaceUrlPlaceholder } from '@/utils/tools'
import { getNowTime, getSecond } from '@/utils/date-util'
import { chartPieTableTopOptions, chartTableDefaultPageSize, chartTableTopOptions } from '@/utils/constants'
import { get } from '@/utils/http'
import { ref } from 'vue'
import _ from 'lodash'
@@ -194,6 +194,10 @@ export default {
...extraParams
}
const requestUrl = url || (chartParams && chartParams.url)
// 默认参数特殊处理
if (requestUrl && requestUrl.indexOf('dnsServerRole') > -1) {
this.queryParams.dnsServerRole = extraParams.dnsServerRole || dnsServerRole.RTDNS
}
if (requestUrl) {
get(replaceUrlPlaceholder(requestUrl, this.queryParams)).then(response => {
// if (this.chartInfo.type === 23 && testData) {
@@ -201,6 +205,27 @@ export default {
// } else if (this.chartInfo.type === 24 && testData) {
// response = testData.data2
// }
if (this.chartInfo.type === 3) {
response = {
code: 200,
data: {
result: [
{
dnsServerRole: extraParams.dnsServerRole || dnsServerRole.RTDNS,
ipLocationCountry: 'China',
ipLocationId: 'CN',
count: 161
},
{
dnsServerRole: extraParams.dnsServerRole || dnsServerRole.RTDNS,
ipLocationCountry: 'Japan',
ipLocationId: 'JP',
count: 222
}
]
}
}
}
if (response.code === 200) {
if (Array.isArray(response.data.result)) {
response.data.result.forEach(item => {
@@ -210,9 +235,6 @@ export default {
})
}
this.chartData = response.data.result
this.table.tableData = response.data.result
this.table.tableColumns = this.getTableTitle(response.data.result)
this.table.currentPageData = this.getTargetPageData(1, this.table.pageSize, this.table.tableData)
this.resultType = response.data.resultType
if (this.chartInfo.type === 12) {
const newArr = []
@@ -231,7 +253,12 @@ export default {
}
})
}
if (this.isSingleValue) {
if (this.isTable) {
this.table.tableData = response.data.result
// this.table.tableColumns = chartParams.columns
// this.table.tableColumns = this.getTableTitle(response.data.result)
this.table.currentPageData = this.getTargetPageData(1, this.table.pageSize, this.table.tableData)
} else if (this.isSingleValue) {
if (chartParams && chartParams.dataKey) {
if (response.data.result && (response.data.result[chartParams.dataKey] || response.data.result[chartParams.dataKey] === 0)) {
this.chartData = response.data.result[chartParams.dataKey]
@@ -377,7 +404,19 @@ export default {
const dateRangeValue = 60
const { startTime, endTime } = getNowTime(dateRangeValue)
const chartTimeFilter = ref({ startTime, endTime, dateRangeValue })
let table = {}
if (isTable(props.chartInfo.type)) {
table = {
pageSize: chartTableDefaultPageSize,
limit: chartTableTopOptions[0], // top-n
orderBy: props.chartInfo.params.columns.order[0],
tableColumns: props.chartInfo.params.columns, // table字段
tableData: [], // table的所有数据
currentPageData: [] // table当前页的数据
}
}
return {
table,
chartTimeFilter,
isEcharts: isEcharts(props.chartInfo.type),
isEchartsTimeBar: isEchartsTimeBar(props.chartInfo.type),

View File

@@ -28,18 +28,18 @@
v-for="(c, i) in table.tableColumns"
show-overflow-tooltip
:key="i"
:label="c.label"
:prop="c.prop"
:label="$t(chartTableOrderOptionsMapping[c])"
:prop="c"
>
<template #default="{ row }">
<span v-if="c.prop === 'bytes'">
{{unitConvert(row[c.prop], unitTypes.byte).join(' ')}}
<span v-if="c === 'bytes'">
{{unitConvert(row[c], unitTypes.byte).join(' ')}}
</span>
<span v-else-if="c.prop === 'packets' || c.prop === 'sessions'">
{{unitConvert(row[c.prop], unitTypes.number).join(' ')}}
<span v-else-if="c === 'packets' || c === 'sessions'">
{{unitConvert(row[c], unitTypes.number).join(' ')}}
</span>
<span v-else>
{{row[c.prop]}}
{{row[c]}}
</span>
</template>
</el-table-column>
@@ -49,7 +49,7 @@
</template>
<script>
import { unitTypes } from '@/utils/constants'
import { unitTypes, chartTableOrderOptionsMapping } from '@/utils/constants'
import unitConvert from '@/utils/unit-convert'
export default {
name: 'ChartActiveIpTable',
@@ -61,6 +61,7 @@ export default {
},
data () {
return {
chartTableOrderOptionsMapping,
unitConvert,
unitTypes,
activeIpTable: {

View File

@@ -6,14 +6,10 @@
</template>
<script>
import * as echarts from 'echarts'
import StatisticsLegend from '@/views/charts/charts/StatisticsLegend'
import {
lineWithStatistics
} from '@/views/charts/charts/options/line'
import {
getChartColor
} from '@/views/charts/charts/chart-options'
} from '@/views/charts/charts/tools'
import chartEchartMixin from './chart-echart-mixin'
export default {

View File

@@ -36,7 +36,7 @@
<script>
import lodash from 'lodash'
import { ipOpenPortBar } from '@/views/charts/charts/options/bar'
import { getChartColor } from '@/views/charts/charts/chart-options'
import { getChartColor } from '@/views/charts/charts/tools'
import * as echarts from 'echarts'
export default {
name: 'ChartIpOpenPortBar',

View File

@@ -7,9 +7,8 @@
import * as am4Core from '@amcharts/amcharts4/core'
import * as am4Maps from '@amcharts/amcharts4/maps'
import { getGeoData, replaceUrlPlaceholder } from '@/utils/tools'
import { storageKey } from '@/utils/constants'
import { isMapBlock } from './tools'
import { storageKey, dnsServerRole } from '@/utils/constants'
import { isMapBlock, isMapPoint } from './tools'
import unitConvert, { valueToRangeValue } from '@/utils/unit-convert'
import { HeatLegend } from '@/components/amcharts/heatLegend'
import { getData } from '@/utils/api'
@@ -72,6 +71,7 @@ export default {
})
},
loadAm4ChartMap (polygonSeries, country, chartData) {
// chartData不为空是下钻
if (chartData) {
this.$emit('showLoading', true)
}
@@ -111,8 +111,9 @@ export default {
showValue: (r.value || r.value === 0) ? valueToRangeValue(r.value, chartParams.unitType).join(' ') : ''
}))
!this.$_.isEmpty(seriesData) && (polygonSeries.data = [...seriesData])
// 数据全为0的情况legend只显示1个颜色
const sorted = seriesData.sort((a, b) => b.value - a.value)
const allZero = this.$_.isEmpty(sorted) || Number(sorted[0].value) === 0 // 数据全为0的情况legend只显示1个颜色
const allZero = this.$_.isEmpty(sorted) || Number(sorted[0].value) === 0
polygonSeries.heatRules.push({
property: 'fill',
@@ -142,6 +143,56 @@ export default {
heatLegend.valueAxis.renderer.labels.template.adapter.add('text', function (labelText) {
return ''
})
} else if (data && this.isMapPoint) {
const seriesData = []
data.forEach(d => {
seriesData.push({
id: d.ipLocationId,
count: d.count,
dnsServerRole: d.dnsServerRole,
location: d.ipLocationCity || d.ipLocationProvince || d.ipLocationCountry,
desc: this.$t(this.dnsTypeI18n(d.dnsServerRole)),
color: this.circleColor[d.dnsServerRole].background,
border: this.circleColor[d.dnsServerRole].border
})
})
console.info(seriesData)
const imageSeries = this.myChart.series.push(new am4Maps.MapImageSeries())
imageSeries.data = seriesData
imageSeries.dataFields.value = 'count'
const imageTemplate = imageSeries.mapImages.template
imageTemplate.nonScaling = true
imageTemplate.adapter.add('latitude', function (latitude, target) {
const polygon = polygonSeries.getPolygonById(target.dataItem.dataContext.id)
if (polygon) {
return polygon.visualLatitude
}
return latitude
})
imageTemplate.adapter.add('longitude', function (longitude, target) {
const polygon = polygonSeries.getPolygonById(target.dataItem.dataContext.id)
if (polygon) {
return polygon.visualLongitude
}
return longitude
})
const circle = imageTemplate.createChild(am4Core.Circle)
circle.propertyFields.fill = 'color'
circle.propertyFields.stroke = 'border'
circle.strokeWidth = 1
circle.tooltipText = '[bold]{location}[/]\n{desc}: {count}'
imageSeries.heatRules.push({
target: circle,
property: 'radius',
min: 6,
max: 25,
dataField: 'value'
})
}
} catch (e) {
console.error(e)
@@ -161,6 +212,37 @@ export default {
this.myChart.goHome()
})
},
dnsTypeI18n (role) {
let i18n = ''
switch (role) {
case dnsServerRole.RTDNS: {
i18n = 'dns.rootDomainServers'
break
}
case dnsServerRole.TLDNS: {
i18n = 'dns.topLevelDomainServers'
break
}
case dnsServerRole.OPRDNS: {
i18n = 'dns.publicRecursiveDomainServers'
break
}
case dnsServerRole.ADNS: {
i18n = 'dns.authoritativeDomainServers'
break
}
case dnsServerRole.SBDNS: {
i18n = 'dns.selfBuiltDomainServers'
break
}
case dnsServerRole.RTDNSM: {
i18n = 'RTDNSM'
break
}
default: break
}
return i18n
},
generateTooltipHTML () {
return `
<div class="map-tooltip" style="padding-bottom: 10px;">
@@ -206,8 +288,35 @@ export default {
}
},
setup (props) {
const circleColor = {}
circleColor[dnsServerRole.RTDNS] = {
background: '#C0DEFE',
border: '#478AD5'
}
circleColor[dnsServerRole.TLDNS] = {
background: '#DBCFFA',
border: '#AA8CF2'
}
circleColor[dnsServerRole.ADNS] = {
background: '#A0E8E0',
border: '#1CC9B5'
}
circleColor[dnsServerRole.OPRDNS] = {
background: '#FFE1B5',
border: '#FFB84E'
}
circleColor[dnsServerRole.SBDNS] = {
background: '#FDC6C6',
border: '#FA7777'
}
circleColor[dnsServerRole.RTDNSM] = {
background: '#ECC6F7',
border: '#BF49DF'
}
return {
isMapBlock: isMapBlock(props.chartInfo.type)
circleColor,
isMapBlock: isMapBlock(props.chartInfo.type),
isMapPoint: isMapPoint(props.chartInfo.type)
}
}
}

View File

@@ -67,7 +67,7 @@ import {
import { get } from '@/utils/http'
import { replaceUrlPlaceholder } from '@/utils/tools'
import * as echarts from 'echarts'
import { getOption, getChartColor } from '@/views/charts/charts/chart-options'
import { getOption, getChartColor } from '@/views/charts/charts/tools'
export default {
name: 'chartSingleValue',
props: {

View File

@@ -9,22 +9,30 @@
<el-table-column v-if="table.currentPageData.length" type="index" label="#">
</el-table-column>
<el-table-column
v-for="(c, i) in table.tableColumns"
v-for="(c, i) in table.tableColumns.common"
show-overflow-tooltip
:key="i"
:label="c.label"
:prop="c.prop"
:label="$t(chartTableColumnMapping[c])"
:prop="c"
>
<template #header>{{$t(c.label)}}</template>
</el-table-column>
<el-table-column
v-for="(c, i) in table.tableColumns.order"
show-overflow-tooltip
:key="i"
:label="$t(chartTableColumnMapping[c])"
:prop="c"
>
<template #header>{{$t(chartTableColumnMapping[c])}}</template>
<template #default="{ row }">
<span v-if="c.prop === 'bytes'">
{{unitConvert(row[c.prop], unitTypes.byte).join(' ')}}
<span v-if="c === 'bytes'">
{{unitConvert(row[c], unitTypes.byte).join(' ')}}
</span>
<span v-else-if="c.prop === 'packets' || c.prop === 'sessions'">
{{unitConvert(row[c.prop], unitTypes.number).join(' ')}}
<span v-else-if="c === 'packets' || c === 'sessions'">
{{unitConvert(row[c], unitTypes.number).join(' ')}}
</span>
<span v-else>
{{row[c.prop]}}
{{row[c]}}
</span>
</template>
</el-table-column>
@@ -41,7 +49,7 @@
</template>
<script>
import { unitTypes } from '@/utils/constants'
import { unitTypes, chartTableColumnMapping } from '@/utils/constants'
import unitConvert from '@/utils/unit-convert'
import ChartTablePagination from '@/views/charts/charts/ChartTablePagination'
export default {
@@ -57,6 +65,7 @@ export default {
},
data () {
return {
chartTableColumnMapping,
unitConvert,
unitTypes
}

View File

@@ -19,7 +19,7 @@
</template>
<script>
import { getChartColor } from '@/views/charts/charts/chart-options'
import { getChartColor } from '@/views/charts/charts/tools'
import unitConvert, { valueToRangeValue } from '@/utils/unit-convert'
export default {
name: 'StatisticsLegend',

View File

@@ -1,333 +1,6 @@
/**
* @author 陈劲松
* @date 2021/6/16
* @description chart option和一些工具
*/
import { format } from 'echarts'
import { unitTypes } from '@/utils/constants'
import unitConvert from '@/utils/unit-convert'
import _ from 'lodash'
export const chartColor = ['#5370C6', '#90CC74', '#FAC858', '#EE6666',
'#73BFDE', '#3BA172', '#FC8452', '#9960B4',
'#E97CCC', '#FEA69E', '#0F8AB2', '#57CBAC',
'#5888BC', '#63B6AC', '#EDC6B2', '#D5746B']
export const chartBarColor = ['#0F8AB2', '#57CBAC']
export function getChartColor (index) {
return chartColor[index % chartColor.length]
}
export function getCharBartColor (index) {
return chartBarColor[index % chartBarColor.length]
}
const line = {
tooltip: {
appendToBody: true,
trigger: 'axis',
textStyle: {
width: '20px',
overflow: 'truncate'
},
formatter: axiosFormatter,
show: true,
className: 'nz-chart-tooltip',
extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important'
},
xAxis: {
type: 'time'
},
yAxis: {
type: 'value',
axisLabel: {
formatter: function (value, index, a, b) {
return unitConvert(value, unitTypes.number).join(' ')
}
},
minInterval: 1
},
animation: false,
grid: {
left: 55,
bottom: 30,
top: 100,
right: 25
},
color: chartColor,
legend: {
tooltip: {
show: true,
formatter: '{a}'
},
show: true,
right: 23,
top: 8,
padding: 2,
orient: 'horizontal',
icon: 'circle',
itemGap: 10,
itemWidth: 10,
textStyle: {
padding: [0, 0, 0, 2],
fontSize: 14
},
formatter: tooLongFormatter
},
axisLabel: {
fontSize: 14
},
series: [
{
name: '',
type: 'line',
smooth: false,
symbol: 'none',
data: []
}
]
}
const lineWithStatistics = {
tooltip: {
appendToBody: true,
trigger: 'axis',
textStyle: {
width: '20px',
overflow: 'truncate'
},
formatter: axiosFormatter,
className: 'nz-chart-tooltip',
extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important'
},
xAxis: {
type: 'time'
},
animation: false,
yAxis: {
type: 'value',
axisLabel: {
formatter: function (value, index) {
return unitConvert(value, unitTypes.number).join(' ')
}
},
minInterval: 1
},
color: chartColor,
grid: {
left: 55,
bottom: 30,
top: 20,
right: 20
},
legend: {
show: false
},
axisLabel: {
fontSize: 14
},
series: [
{
name: '',
type: 'line',
smooth: false,
symbol: 'none',
data: []
}
]
}
const lineStack = {
tooltip: {
appendToBody: true,
trigger: 'axis',
textStyle: {
width: '20px',
overflow: 'truncate'
},
formatter: axiosFormatter,
className: 'nz-chart-tooltip',
extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important'
},
xAxis: {
type: 'time'
},
color: chartColor,
yAxis: {
type: 'value',
axisLabel: {
formatter: function (value, index) {
return unitConvert(value, unitTypes.number).join(' ')
}
},
minInterval: 1
},
grid: {
left: 55,
bottom: 45,
top: 10,
right: 180
},
legend: {
show: true,
right: 30,
top: 'middle',
orient: 'vertical',
icon: 'circle',
itemGap: 20,
itemWidth: 10,
formatter: tooLongFormatter,
textStyle: {
padding: [0, 0, 0, 5],
fontSize: 14
}
},
axisLabel: {
fontSize: 14
},
series: [
{
name: '',
type: 'line',
stack: 'value',
areaStyle: {},
symbol: 'none',
data: []
}
]
}
const pieWithTable = {
tooltip: {
appendToBody: true
},
color: chartColor,
animation: false,
legend: {
orient: 'vertical',
type: 'plain',
left: '60%',
top: 'middle',
icon: 'circle',
itemWidth: 10, // 设置宽度
itemHeight: 10, // 设置高度
itemGap: 20,
formatter: tooLongFormatter,
tooltip: {
show: true
}
},
series: [
{
type: 'pie',
selectedMode: 'single',
radius: ['42%', '65%'],
center: ['30%', '50%'],
data: [],
label: {
formatter: '{d}%'
},
tooltip: {
formatter: function (param, index, callback) {
return `${param.name}: ${unitConvert(param.value, param.data.unitType).join(' ')}`
}
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}
const ipHostedDomain = {
color: chartColor,
animation: false,
tooltip: {
show: true
},
legend: {
orient: 'vertical',
type: 'plain',
right: '8%',
top: 'middle',
icon: 'circle',
itemWidth: 10, // 设置宽度
itemHeight: 10, // 设置高度
itemGap: 20,
tooltip: {
show: true
}
},
series: [
{
type: 'pie',
selectedMode: 'single',
radius: ['42%', '65%'],
center: ['36%', '50%'],
data: [],
label: {
formatter: '{d}%'
},
tooltip: {
formatter: function (param, index, callback) {
return `${param.name}: ${unitConvert(param.value, param.data.unitType).join(' ')}`
}
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}
const singleValueLine = {
tooltip: {
show: true,
enterable: true,
showContent: true,
appendToBody: true,
trigger: 'axis',
textStyle: {
width: '20px',
overflow: 'truncate'
}
},
xAxis: {
type: 'time',
show: false
},
yAxis: {
type: 'value',
show: false
},
animation: false,
grid: {
left: 0,
bottom: 2,
top: 5,
right: 0
},
color: chartColor,
legend: {
show: false
},
series: [
{
type: 'line',
legendHoverLink: false,
itemStyle: {
normal: {
color: '#81C9FF',
lineStyle: {
width: 2
}
}
},
data: [],
showSymbol: false,
areaStyle: { color: '#C9EAFF' }
}
]
}
import { axisFormatter } from '@/views/charts/charts/tools'
import { chartColor } from '@/utils/constants'
export const entityListLineOption = {
tooltip: {
appendToBody: true,
@@ -336,7 +9,7 @@ export const entityListLineOption = {
width: '20px',
overflow: 'truncate'
},
formatter: axiosFormatter,
formatter: axisFormatter,
show: true,
className: 'nz-chart-tooltip',
extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important'
@@ -376,461 +49,3 @@ export const entityListLineOption = {
}
]
}
const relationShip = {
grid: {
left: 0,
bottom: 50,
top: 80,
right: 0
},
series: [
{
type: 'graph',
layout: 'force',
symbolSize: 40,
roam: true,
force: {
repulsion: 350
},
draggable: true,
label: { show: true },
edgeSymbol: ['none', 'arrow'],
edgeSymbolSize: 7,
data: [],
links: []
}
]
}
const sankey = {
tooltip: {
trigger: 'item',
triggerOn: 'mousemove'
},
series: [
{
type: 'sankey',
data: [],
links: [],
right: '5%',
top: 50,
bottom: 100,
levels: [
{
depth: 0,
itemStyle: {
color: '#47D49C'
},
lineStyle: {
color: '#999'
}
}, {
depth: 1,
itemStyle: {
color: '#A69BF5'
},
lineStyle: {
color: '#999'
}
}, {
depth: 2,
itemStyle: {
color: '#73A0FA'
},
lineStyle: {
color: '#999'
}
}
]
}
]
}
const ipOpenPortBar = {
xAxis: {
type: 'category',
axisTick: { show: false },
axisLine: { show: false }
},
grid: {
top: 30,
left: 60,
right: 50,
bottom: 50
},
yAxis: {
type: 'value',
show: false
},
series: [{
barWidth: 38,
data: [],
type: 'bar',
label: { show: true, position: 'top' },
barCategoryGap: '10%'
}]
}
const categoryBar = {
tooltip: {
appendToBody: true,
trigger: 'axis',
textStyle: {
width: '20px',
overflow: 'truncate'
},
formatter: categoryVerticalFormatter,
show: true,
className: 'nz-chart-tooltip',
extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important'
},
xAxis: {
type: 'category',
axisTick: { show: false },
axisLine: { show: false }
},
grid: {
top: 20,
left: 10,
right: 25,
bottom: 20,
containLabel: true
},
yAxis: {
type: 'value',
axisTick: { show: false },
axisLine: { show: false }
},
color: chartColor,
series: [{
barWidth: 15,
data: [],
type: 'bar',
label: { show: false },
barCategoryGap: '10%',
itemStyle: {
color: function (params) {
return getCharBartColor([params.dataIndex])
}
}
}]
}
const timeBar = {
tooltip: {
appendToBody: true,
trigger: 'axis',
textStyle: {
width: '20px',
overflow: 'truncate'
},
formatter: timeVerticalFormatter,
show: true,
className: 'nz-chart-tooltip',
extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important'
},
xAxis: {
type: 'time',
axisTick: { show: false },
axisLine: { show: false },
axisLabel: {
interval: 0,
// rotate: -40, //设置日期显示样式(倾斜度)
formatter: function (value) { // 在这里写你需要的时间格式
const t_date = new Date(value)
return [t_date.getMonth() + 1, t_date.getDate()].join('/') + ' ' + [t_date.getHours(), t_date.getMinutes()].join(':')
}
}
},
grid: {
top: 20,
left: 25,
right: 25,
bottom: 20,
containLabel: true
},
yAxis: {
type: 'value',
axisTick: { show: false },
axisLine: { show: false },
axisLabel: {
formatter: function (value, index, a, b) {
return unitConvert(value, unitTypes.number).join(' ')
}
},
minInterval: 1
},
color: chartColor,
series: [{
barWidth: 15,
data: [],
type: 'bar',
label: { show: false },
barCategoryGap: '10%',
itemStyle: {
color: function (params) {
return getCharBartColor([params.dataIndex])
}
}
}]
}
const typeOptionMappings = [
{ value: 11, option: line }, // 常规折线图
{ value: 12, option: lineWithStatistics }, // 带统计表格的折线图
{ value: 13, option: lineStack }, // 折线堆叠图
{ value: 22, option: ipOpenPortBar }, // ip详情--开放端口的柱状图
{ value: 23, option: timeBar }, // 矿机所属单位
{ value: 24, option: categoryBar }, // 挖矿事件统计
{ value: 31, option: pieWithTable }, // 常规折线图
{ value: 33, option: ipHostedDomain }, // ip详情--托管域名
{ value: 34, option: ipHostedDomain }, // app详情--相关域名
{ value: 42, option: relationShip }, // 关系图
{ value: 43, option: sankey }, // 桑基图
{ value: 52, option: singleValueLine }
]
const typeCategory = {
MAP: 'map',
TABLE: 'table',
ECHARTS: 'echarts',
TITLE: 'title',
SINGLE: 'singleValue',
TABS: 'tabs'
}
export function getTypeCategory (type) {
if (isMap(type)) {
return typeCategory.MAP
} else if (isEcharts(type)) {
return typeCategory.ECHARTS
} else if (isTable(type)) {
return typeCategory.TABLE
} else if (isSingleValue(type)) {
return typeCategory.SINGLE
} else if (isTitle(type)) {
return typeCategory.TITLE
} else if (isTabs(type)) {
return typeCategory.TABS
}
}
/* 柱状图:挖矿事件统计(time类型柱状图) */
export function isEchartsTimeBar (type) {
return type == 23
}
/* 柱状图:矿机所属单位(category类型柱状图) */
export function isEchartsCategoryBar (type) {
return type == 24
}
/* 饼图柱状图等 */
export function isEcharts (type) {
return type >= 11 && type <= 50
}
/* 折线,饼图 */
export function isEchartsLineBar (type) {
return type == 11 || type == 12 || type == 31 || type == 32 || type == 33 || type == 34
}
/* 地图 */
export function isMap (type) {
return type >= 1 && type <= 10
}
/* 连线地图 */
export function isMapLine (type) {
return type === 1
}
/* 色块地图 */
export function isMapBlock (type) {
return type === 2
}
/* 带统计的折线图 */
export function isEchartsWithStatistics (type) {
return type === 12
}
/* 关系图 */
export function isRelationShip (type) {
return type === 42
}
/* 桑基图 */
export function isSankey (type) {
return type === 43
}
/* 单值 */
export function isSingleValue (type) {
return type >= 51 && type <= 60
}
/* 带折线图的单值 */
export function isSingleValueWithEcharts (type) {
return type === 52
}
/* 带折线图的单值 */
export function isSingleValueWithEchartsTemp (type) {
return type === 55
}
/* 带Table的饼图 */
export function isEchartsWithTable (type) {
return type === 31
}
/* 普通饼图 */
export function isEchartsPie (type) {
return type === 32
}
/* table */
export function isTable (type) {
return type >= 61 && type <= 70
}
/* table */
export function isActiveIpTable (type) {
return type == 63
}
/* title */
export function isTitle (type) {
return type === 93
}
/* tabs */
export function isTabs (type) {
return type === 91
}
/* IP实体基本信息 */
export function isIpBasicInfo (type) {
return type === 4
}
/* IP实体开放端口 */
export function isIpOpenPort (type) {
return type === 22
}
/* IP实体托管域名 */
export function isIpHostedDomain (type) {
return type === 33
}
/* APP实体相关域名 */
export function isAppRelatedDomain (type) {
return type === 34
}
/* APP实体基本信息 */
export function isAppBasicInfo (type) {
return type === 82
}
/* DOMAIN实体Whois */
export function isDomainWhois (type) {
return type === 83
}
/* DOMAIN实体DNS记录 */
export function isDomainDnsRecord (type) {
return type === 84
}
/* 近期挖矿事件 */
export function isCryptocurrencyEventList (type) {
return type === 85
}
/* 组 */
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
}
export const layoutConstant = {
HEADER: 'header',
FOOTER: 'footer'
}
export function getLayout (type) {
const layout = []
if (!isSingleValue(type) && !isTitle(type)) {
layout.push(layoutConstant.HEADER)
}
if (type === 12 || type === 31) {
layout.push(layoutConstant.FOOTER)
}
return layout
}
function tooLongFormatter (name) {
return format.truncateText(name, 110, '12')
}
function axiosFormatter (params) {
let str = '<div>'
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">
${unitConvert(item.data[1], item.data[2]).join(' ')}
</span>`
str += '</div>'
})
str += '</div>'
return str
}
export function timeVerticalFormatter (params) {
let str = '<div>'
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">
${unitConvert(item.data[1], item.data[2]).join(' ')}
</span>`
str += '</div>'
})
str += '</div>'
return str
}
export function timeHorizontalFormatter (params) {
let str = '<div>'
params.forEach((item, i) => {
const tData = item.data[1]
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">
${unitConvert(item.data[0], item.data[2]).join(' ')}
</span>`
str += '</div>'
})
str += '</div>'
return str
}
export function categoryHorizontalFormatter (params) {
let str = '<div>'
params.forEach((item, i) => {
str += '<div class="cn-chart-tooltip-box">'
str += item.data[1] + ': ' + item.data[0]
str += '</div>'
})
str += '</div>'
return str
}
export function categoryVerticalFormatter (params) {
let str = '<div>'
params.forEach((item, i) => {
str += '<div class="cn-chart-tooltip-box">'
str += item.data[0] + ': ' + item.data[1]
str += '</div>'
})
str += '</div>'
return str
}

View File

@@ -1,7 +1,6 @@
import unitConvert from '@/utils/unit-convert'
import { unitTypes } from '@/utils/constants'
import { chartColor } from '@/views/charts/charts/chart-options'
import { axisFormatter, tooLongFormatter } from '../tools'
import { unitTypes, chartColor } from '@/utils/constants'
import { axisFormatter, tooLongFormatter } from '@/views/charts/charts/tools'
export const line = {
tooltip: {

View File

@@ -1,6 +1,6 @@
import unitConvert from '@/utils/unit-convert'
import { chartColor } from '@/views/charts/charts/chart-options'
import { tooLongFormatter } from '../tools'
import { chartColor } from '@/utils/constants'
export const pieWithTable = {
tooltip: {

View File

@@ -6,11 +6,8 @@ import { ipOpenPortBar, timeBar, categoryBar } from './options/bar'
import { pieWithTable, ipHostedDomain } from './options/pie'
import { relationShip } from './options/graph'
import { sankey } from './options/sankey'
import { chartColor } from '@/utils/constants'
export const chartColor = ['#5370C6', '#90CC74', '#FAC858', '#EE6666',
'#73BFDE', '#3BA172', '#FC8452', '#9960B4',
'#E97CCC', '#FEA69E', '#0F8AB2', '#57CBAC',
'#5888BC', '#63B6AC', '#EDC6B2', '#D5746B']
export const chartBarColor = ['#0F8AB2', '#57CBAC']
export function getChartColor (index) {
return chartColor[index % chartColor.length]
@@ -66,6 +63,10 @@ export function isMapLine (type) {
export function isMapBlock (type) {
return type === 2
}
/* 散点地图 */
export function isMapPoint (type) {
return type === 3
}
/* 普通折线图 */
export function isEchartsLine (type) {
return type === 11
@@ -106,8 +107,8 @@ export function isEchartsWithTable (type) {
export function isTable (type) {
return type >= 61 && type <= 70
}
/* current table */
export function isCurrentTable (type) {
/* basic table */
export function isBasicTable (type) {
return type === 61
}
/* table */
@@ -158,11 +159,11 @@ export function isAlarmInfo (type) {
export function isCryptocurrencyEventList (type) {
return type === 85
}
/* 单支持情况统计 */
/* 单支持情况统计 */
export function isSingleSupportStatistics (type) {
return type === 86
}
/* 两个支持情况统计 */
/* 两个支持情况统计 */
export function isTwoSupportStatistics (type) {
return type === 87
}

View File

@@ -14,9 +14,16 @@
></detection-search>
<!-- 内容区 -->
<div class="explorer-container" style="height: calc(100% - 20px); flex-direction: column">
<div class="detection__event-severity-bar" :id="`eventSeverityTrendBar${pageType}`">
<detection-no-data v-if="isEventSeverityNoData"></detection-no-data>
<div class="entity__loading" style="background: #eff2f5;opacity: .6;" v-show="loading">
<i class="el-icon-loading"></i>
</div>
<template v-if="isEventSeverityNoData">
<div class="no-data detection__event-severity-bar" >No data</div>
</template>
<template v-if="!isEventSeverityNoData">
<div class="detection__event-severity-bar" :id="`eventSeverityTrendBar${pageType}`">
</div>
</template>
<div style="display: flex; flex-grow: 1">
<detection-filter
:filter-data="filterData[pageType]"
@@ -31,25 +38,38 @@
<div class="chart-header">
<div class="chart-header__title">{{$t('detection.severity')}}</div>
</div>
<div class="chart-content" :id="`eventSeverityPie${pageType}`">
<detection-no-data v-if="isStatisticsSeverityNoData"></detection-no-data>
</div>
<template v-if="isStatisticsSeverityNoData">
<div class="no-data chart-content" >No data</div>
</template>
<template v-if="!isStatisticsSeverityNoData">
<div class="chart-content" :id="`eventSeverityPie${pageType}`">
</div>
</template>
</div>
<div class="statistics__category">
<div class="chart-header">
<div class="chart-header__title">{{$t('detection.categoryProportion')}}</div>
</div>
<div class="chart-content" :id="`detectionCategoryPer${pageType}`">
<detection-no-data v-if="isStatisticsCategoryNoData"></detection-no-data>
</div>
<template v-if="isStatisticsCategoryNoData">
<div class="no-data chart-content" >No data</div>
</template>
<template v-if="!isStatisticsCategoryNoData">
<div class="chart-content" :id="`detectionCategoryPer${pageType}`">
</div>
</template>
</div>
<div class="statistics__active-attack">
<div class="chart-header">
<div class="chart-header__title">{{pageType === detectionPageType.securityEvent ? $t('detection.activeAttacker') : $t('detections.activeEntity')}}</div>
</div>
<div class="chart-content" style="" :id="`detectionActiveAttacker${pageType}`">
<detection-no-data v-if="isStatisticsActiveAttackNoData"></detection-no-data>
</div>
<template v-if="isStatisticsActiveAttackNoData">
<div class="no-data chart-content" >No data</div>
</template>
<template v-if="!isStatisticsActiveAttackNoData">
<div class="chart-content" :id="`detectionActiveAttacker${pageType}`">
</div>
</template>
</div>
</div>
<detection-list
@@ -87,7 +107,7 @@ import DetectionFilter from '@/views/detections/DetectionFilter'
import DetectionList from '@/views/detections/DetectionList'
import Pagination from '@/components/common/Pagination'
import { defaultPageSize, detectionPageType } from '@/utils/constants'
import { getNowTime, getSecond } from '@/utils/date-util'
import { getNowTime, getSecond, rTime } from '@/utils/date-util'
import { ref } from 'vue'
import * as echarts from 'echarts'
import { multipleBarOption, pieForSeverity, activeAttackBar, getAttackColor, getSeverityColor, getSeriesIndex } from '@/views/detections/options/detectionOptions'
@@ -183,8 +203,8 @@ export default {
data: [] // 从接口动态获取,本项在获得数据后需要特殊处理左边框颜色
},
{
title: this.$t('detections.securityType'),
column: 'securityType',
title: this.$t('detections.eventType'),
column: 'eventType',
collapse: false,
value: [],
data: [] // 从接口动态获取
@@ -193,24 +213,122 @@ export default {
},
listData: [],
listLoading: false,
eventSeverityData: [],
severityPerOption: null,
severityPerData: [],
categoryPerOption: null,
categoryPerData: [],
activeAttackOption: null,
activeAttackData: [],
isEventSeverityNoData: true,
isStatisticsSeverityNoData: true,
isStatisticsCategoryNoData: true,
isStatisticsActiveAttackNoData: true
eventSeverityData: [],
statisticsSeverityData: [],
statisticsCategoryData: [],
statisticsActiveAttackData: [],
isEventSeverityNoData: false,
isStatisticsSeverityNoData: false,
isStatisticsCategoryNoData: false,
isStatisticsActiveAttackNoData: false,
loading: false
}
},
methods: {
// 初始化顶部大柱状图
initEventSeverityTrendData (params) {
this.loading = true
getData(api.detection[this.pageType].eventSeverityTrend, params).then(data => {
/* data = [
{
"stat_time": "2022-01-01T10:07:03.008Z",
"event_severity": "critical",
"count": 5
},
{
"stat_time": "2022-01-02T10:07:03.008Z",
"event_severity": "critical",
"count": 15
},{
"stat_time": "2022-01-03T10:07:03.008Z",
"event_severity": "critical",
"count": 25
},
{
"stat_time": "2022-01-04T10:07:03.008Z",
"event_severity": "critical",
"count": 7
},{
"stat_time": "2022-01-01T10:07:03.008Z",
"event_severity": "high",
"count": 8
},
{
"stat_time": "2022-01-02T10:07:03.008Z",
"event_severity": "high",
"count": 2
},{
"stat_time": "2022-01-03T10:07:03.008Z",
"event_severity": "high",
"count": 25
},
{
"stat_time": "2022-01-04T10:07:03.008Z",
"event_severity": "high",
"count": 7
},{
"stat_time": "2022-01-01T10:07:03.008Z",
"event_severity": "medium",
"count": 9
},
{
"stat_time": "2022-01-02T10:07:03.008Z",
"event_severity": "medium",
"count": 15
},{
"stat_time": "2022-01-03T10:07:03.008Z",
"event_severity": "medium",
"count": 35
},
{
"stat_time": "2022-01-04T10:07:03.008Z",
"event_severity": "medium",
"count": 7
},{
"stat_time": "2022-01-01T10:07:03.008Z",
"event_severity": "low",
"count": 5
},
{
"stat_time": "2022-01-02T10:07:03.008Z",
"event_severity": "low",
"count": 1
},{
"stat_time": "2022-01-03T10:07:03.008Z",
"event_severity": "low",
"count": 25
},
{
"stat_time": "2022-01-04T10:07:03.008Z",
"event_severity": "low",
"count": 17
},{
"stat_time": "2022-01-01T10:07:03.008Z",
"event_severity": "info",
"count": 5
},
{
"stat_time": "2022-01-02T10:07:03.008Z",
"event_severity": "info",
"count": 15
},{
"stat_time": "2022-01-03T10:07:03.008Z",
"event_severity": "info",
"count": 25
},
{
"stat_time": "2022-01-04T10:07:03.008Z",
"event_severity": "info",
"count": 27
},
] */
/* data2 = [
{
legend: 'critical',
values: [[1435781430781, '999'], [1435781431781, '222'], [1435781432781, '11'], [1435781433781, '3']]
@@ -229,23 +347,40 @@ export default {
values: [[1435781430781, '5'], [1435781431781, '7'], [1435781432781, '5'], [1435781433781, '8']]
}
] */
this.eventSeverityData = data
if (!this.$_.isEmpty(data)) {
const chartDom = document.getElementById(`eventSeverityTrendBar${this.pageType}`)
const detectionChart = echarts.init(chartDom)
const eventSeverityTrendOption = this.$_.cloneDeep(multipleBarOption)
const dataMap = new Map()
data.forEach(item => {
eventSeverityTrendOption.series[Number(getSeriesIndex(item.legend))].data = item.values.map(v => Number(v[1]))
if (item.eventSeverity) {
if (!dataMap.has(item.eventSeverity)) {
const count = [[item.statTime, item.count]]
dataMap.set(item.eventSeverity, count)
} else {
dataMap.get(item.eventSeverity).push([item.statTime, item.count])
}
}
})
eventSeverityTrendOption.xAxis.data = data[0].values.map(v => [window.$dayJs.tz(Number(v[0])).format('YYYY-MM-DD HH:mm:ss')])
this.$nextTick(() => {
detectionChart.setOption(eventSeverityTrendOption)
const chartDom = document.getElementById(`eventSeverityTrendBar${this.pageType}`)
const eventSeverityTrendOption = this.$_.cloneDeep(multipleBarOption)
dataMap.forEach(function (value, key) {
eventSeverityTrendOption.series[Number(getSeriesIndex(key))].data = value.map(v => Number(v[1]))
})
this.isEventSeverityNoData = false
eventSeverityTrendOption.xAxis.data = dataMap.get('critical').map(v => rTime(v[0]))
const detectionChart = echarts.init(chartDom)
detectionChart.setOption(eventSeverityTrendOption)
// this.isEventSeverityNoData = false
} else {
// this.isEventSeverityNoData = true
}
}).catch(error => {
}).finally(() => {
this.$nextTick(() => {
this.loading = false
})
})
},
// 初始化左侧事件严重等级和小饼图
initEventSeverityData (params) {
getData(api.detection[this.pageType].eventSeverity, params).then(data => {
@@ -267,6 +402,8 @@ export default {
count: 300
}
] */
this.statisticsSeverityData = data
// this.isStatisticsSeverityNoData = true
if (!this.$_.isEmpty(data)) {
this.filterData[this.pageType][0].data = data.map(r => ({ label: r.eventSeverity, value: r.eventSeverity, count: r.count }))
const eventSeverityOption = this.$_.cloneDeep(pieForSeverity)
@@ -276,7 +413,28 @@ export default {
const chartDom = document.getElementById(`eventSeverityPie${this.pageType}`)
const detectionChart = echarts.init(chartDom)
detectionChart.setOption(eventSeverityOption)
this.isStatisticsSeverityNoData = false
// this.isStatisticsSeverityNoData = false
}
}).catch(error => {
})
},
initEventTypeData (params) {
getData(api.detection[this.pageType].eventType, params).then(data => {
this.statisticsCategoryData = data
if (!this.$_.isEmpty(data)) {
this.filterData[this.pageType][1].data = data.map(r => ({
label: r.eventType,
value: r.eventType,
count: r.count
}))
const chartDom = document.getElementById(`detectionCategoryPer${this.pageType}`)
const detectionChart = echarts.init(chartDom)
const securityTypeOption = this.$_.cloneDeep(pieForSeverity)
securityTypeOption.series[0].data = data.map(d => {
return { value: d.count, name: d.eventType }
})
detectionChart.setOption(securityTypeOption)
}
}).catch(error => {
@@ -284,41 +442,20 @@ export default {
},
initSecurityTypeData (params) {
getData(api.detection[this.pageType].securityType, params).then(data => {
/* data = [
{
attackType: 'command and control',
count: 1048
}, {
attackType: 'payload delivery',
count: 735
}, {
attackType: 'cryptomining',
count: 580
}, {
attackType: 'phishing',
count: 484
}, {
attackType: 'dga',
count: 300
}, {
attackType: 'ddos',
count: 50
}
] */
this.statisticsCategoryData = data
if (!this.$_.isEmpty(data)) {
this.filterData[this.pageType][1].data = data.map(r => ({
label: r.attackType,
value: r.attackType,
label: r.securityType,
value: r.securityType,
count: r.count
}))
const chartDom = document.getElementById(`detectionCategoryPer${this.pageType}`)
const detectionChart = echarts.init(chartDom)
const securityTypeOption = this.$_.cloneDeep(pieForSeverity)
securityTypeOption.series[0].data = data.map(d => {
return { value: d.count, name: d.attackType, itemStyle: { color: getAttackColor(d.attackType) } }
return { value: d.count, name: d.securityType, itemStyle: { color: getAttackColor(d.securityType) } }
})
detectionChart.setOption(securityTypeOption)
this.isStatisticsCategoryNoData = false
}
}).catch(error => {
@@ -376,6 +513,8 @@ export default {
count: 55355
}
] */
this.statisticsActiveAttackData = data
if (!this.$_.isEmpty(data)) {
this.filterData[this.pageType][4].data = data.map(r => ({
label: r.offenderIp,
@@ -395,7 +534,7 @@ export default {
return [d.count, d.offenderIp]
}).reverse()
detectionChart.setOption(offenderIpOption)
this.isStatisticsActiveAttackNoData = false
// this.isStatisticsActiveAttackNoData = false
}
}).catch(error => {
@@ -550,15 +689,17 @@ export default {
count: 50
}
] */
const chartDom = document.getElementById(`detectionActiveAttacker${this.pageType}`)
const detectionChart = echarts.init(chartDom)
const option = this.$_.cloneDeep(activeAttackBar)
option.series[0].data = data.map(d => {
return [d.count, d.name]
}).reverse()
detectionChart.setOption(option)
this.statisticsActiveAttackData = data
if (!this.$_.isEmpty(data)) {
const chartDom = document.getElementById(`detectionActiveAttacker${this.pageType}`)
const detectionChart = echarts.init(chartDom)
const option = this.$_.cloneDeep(activeAttackBar)
option.series[0].data = data.map(d => {
return [d.count, d.name]
}).reverse()
detectionChart.setOption(option)
}
}).catch(error => {
})
},
computeFilterPage (data) {
@@ -571,7 +712,9 @@ export default {
const params = {
startTime: getSecond(this.timeFilter.startTime),
endTime: getSecond(this.timeFilter.endTime),
q: this.q
q: this.q,
pageSize: this.pageObj.pageSize,
pageNo: this.pageObj.pageNo
}
getData(api.detection[this.pageType].listBasic, params).then(data => {
if (this.pageType === detectionPageType.securityEvent) {
@@ -873,15 +1016,28 @@ export default {
this.listData = data
}).catch(error => {
})
getData(api.detection[this.pageType].listCount, params).then(data => {
this.pageObj.total = data
}).catch(error => {
})
},
timeRefreshChange () {
this.initNoData()
if (!this.$refs.dateTimeRange.isCustom) {
const value = this.timeFilter.dateRangeValue
this.$refs.dateTimeRange.quickChange(value)
}
},
initNoData () {
this.isEventSeverityNoData = false
this.isStatisticsSeverityNoData = false
this.isStatisticsCategoryNoData = false
this.isStatisticsActiveAttackNoData = false
},
reload (s, e, v) {
this.initNoData()
this.dateTimeRangeChange(s, e, v)
},
// methods
@@ -889,6 +1045,7 @@ export default {
this.timeFilter = { startTime: s, endTime: e, dateRangeValue: v }
},
search (metaList, formatSql) {
this.initNoData()
if (formatSql) {
this.q = formatSql
this.metaList = metaList
@@ -900,7 +1057,16 @@ export default {
this.queryList()
this.queryListTotal()
},
resetFilterData () {
this.filterData.securityEvent.forEach(d => {
d.data = []
})
this.filterData.performanceEvent.forEach(d => {
d.data = []
})
},
queryFilter () {
this.resetFilterData()
const params = {
startTime: getSecond(this.timeFilter.startTime),
endTime: getSecond(this.timeFilter.endTime),
@@ -908,14 +1074,15 @@ export default {
}
this.initEventSeverityTrendData(params)
this.initEventSeverityData(params)
this.initSecurityTypeData(params)
if (this.pageType === detectionPageType.securityEvent) {
this.initOffenderIpData(params)
this.initOffenderLocationData(params)
this.initVictimIpData(params)
this.initVictimLocationData(params)
this.initSecurityTypeData(params)
} else if (this.pageType === detectionPageType.performanceEvent) {
this.initActiveEntity(params)
this.initEventTypeData(params)
}
},
queryListTotal () {
@@ -961,6 +1128,68 @@ export default {
this.queryList()
},
watch: {
eventSeverityData: {
deep: true,
handler (n) {
if (!n || n.length === 0) {
this.timeout = setTimeout(() => {
this.isEventSeverityNoData = true
// this.$set(this.isEventSeverityNoData ,true)
this.loading = false
}, 500)
} else {
clearTimeout(this.timeout)
// this.$set(this.isEventSeverityNoData ,false)
this.isEventSeverityNoData = false
this.loading = false
}
}
},
statisticsSeverityData: {
deep: true,
handler (n) {
if (!n || n.length === 0) {
this.timeout = setTimeout(() => {
// this.$set(this.isStatisticsSeverityNoData ,true)
this.isStatisticsSeverityNoData = true
}, 500)
} else {
clearTimeout(this.timeout)
// this.$set(this.isStatisticsSeverityNoData ,false)
this.isStatisticsSeverityNoData = false
}
}
},
statisticsCategoryData: {
deep: true,
handler (n) {
if (!n || n.length === 0) {
this.timeout = setTimeout(() => {
this.isStatisticsCategoryNoData = true
// this.$set(this.isStatisticsCategoryNoData ,true)
}, 500)
} else {
clearTimeout(this.timeout)
// this.$set(this.isStatisticsCategoryNoData ,false)
this.isStatisticsCategoryNoData = false
}
}
},
statisticsActiveAttackData: {
deep: true,
handler (n) {
if (!n || n.length === 0) {
this.timeout = setTimeout(() => {
this.isStatisticsActiveAttackNoData = true
// this.$set(this.isStatisticsActiveAttackNoData ,true)
}, 500)
} else {
clearTimeout(this.timeout)
// this.$set(this.isStatisticsActiveAttackNoData ,false)
this.isStatisticsActiveAttackNoData = false
}
}
},
timeFilter (n) {
this.search(this.metaList, this.q)
},
@@ -1016,7 +1245,6 @@ export default {
setup () {
const { params } = useRoute()
const pageType = params.typeName
const dateRangeValue = 60
const { startTime, endTime } = getNowTime(dateRangeValue)
const timeFilter = ref({ startTime, endTime, dateRangeValue })

View File

@@ -18,6 +18,9 @@ export function getSeriesIndex (type) {
return mapping && mapping.index ? _.cloneDeep(mapping.index) : null
}
export const activeAttackColor = ['#51a9ee', '#49bcf2', '#4ad7eb', '#4cd4c8',
'#7acc7e', '#a7db69']
const activeAttackColorMappings = [
{ value: 'command and control', color: '#51a9ee' },
{ value: 'payload delivery', color: '#49bcf2' },
@@ -149,7 +152,7 @@ export const pieForSeverity = {
tooltip: {
appendToBody: true
},
color: chartColor,
color: activeAttackColor,
animation: false,
legend: {
orient: 'vertical',

View File

@@ -8,7 +8,7 @@
<div class="overview__title">Fields</div>
<div class="overview__row">
<div class="row__label">{{$t('entities.category')}}</div>
<div class="row__content">{{basicInfo.appCategory}}</div>
<div class="row__content">{{basicInfo.appCategory || '-'}}</div>
</div>
<div class="overview__row">
<div class="row__label">{{$t('entities.subcategory')}}</div>
@@ -35,8 +35,7 @@
</template>
<script>
import { get } from '@/utils/http'
import { api } from '@/utils/api'
import { api, getData } from '@/utils/api'
import { eventSeverityColor } from '@/utils/constants'
export default {
name: 'DetectionPerformanceEventAppOverview',
@@ -74,35 +73,17 @@ export default {
},
methods: {
query () {
this.basicInfo = {
clientLocationCountry: 1212,
clientLocationProvince: '112.2.2.3',
clientLocationRegion: 'China',
clientAsn: 'Hebei',
serverLocationCountry: 'Xingtai',
serverLocationProvince: 'hehe',
serverLocationRegion: '2.2.2.2',
serverAsn: 'China',
domainCategoryName: 'Hebei',
domainCategoryGroup: 'Xingtai',
domainReputationScore: 'hehe',
domainReputationLevel: 'high',
appCategory: 'vpn',
appSubcategory: 'foreign vpn',
appRisk: 'critical'
}
/* this.queryBasic().then(responses => {
}) */
this.queryBasic().then(responses => {
responses && (this.basicInfo = responses)
})
},
queryBasic () {
return new Promise((resolve, reject) => {
try {
get(api.detection.performanceEvent.overviewBasic, { serverIp: this.detection.appName }).then(response => {
if (response.code === 200) {
resolve(response.data.list)
} else {
reject(response)
}
getData(api.detection.performanceEvent.overviewBasic, { domain: this.detection.appName,startTime: this.detection.startTime }).then(data => {
resolve(data[0])
}).catch(error => {
reject(error)
})
} catch (e) {
reject(e)

View File

@@ -8,7 +8,7 @@
<div class="overview__title">Fields</div>
<div class="overview__row">
<div class="row__label">{{$t('entities.category')}}</div>
<div class="row__content">{{basicInfo.domainCategoryName}}</div>
<div class="row__content">{{basicInfo.domainCategoryName || '-'}}</div>
</div>
<div class="overview__row">
<div class="row__label">{{$t('entities.group')}}</div>
@@ -39,8 +39,7 @@
</template>
<script>
import { get } from '@/utils/http'
import { api } from '@/utils/api'
import { api, getData } from '@/utils/api'
import { eventSeverityColor } from '@/utils/constants'
export default {
name: 'DetectionPerformanceEventDomainOverview',
@@ -78,35 +77,17 @@ export default {
},
methods: {
query () {
this.basicInfo = {
clientLocationCountry: 1212,
clientLocationProvince: '112.2.2.3',
clientLocationRegion: 'China',
clientAsn: 'Hebei',
serverLocationCountry: 'Xingtai',
serverLocationProvince: 'hehe',
serverLocationRegion: '2.2.2.2',
serverAsn: 'China',
domainCategoryName: 'Hebei',
domainCategoryGroup: 'Xingtai',
domainReputationScore: 'hehe',
domainReputationLevel: 'high',
appCategory: 'vpn',
appSubcategory: 'foreign vpn',
appRisk: 'critical'
}
/* this.queryBasic().then(responses => {
}) */
this.queryBasic().then(responses => {
responses && (this.basicInfo = responses)
})
},
queryBasic () {
return new Promise((resolve, reject) => {
try {
get(api.detection.performanceEvent.overviewBasic, { serverIp: this.detection.appName }).then(response => {
if (response.code === 200) {
resolve(response.data.list)
} else {
reject(response)
}
getData(api.detection.performanceEvent.overviewBasic, { appName: this.detection.appName,startTime: this.detection.startTime }).then(data => {
resolve(data[0])
}).catch(error => {
reject(error)
})
} catch (e) {
reject(e)

View File

@@ -65,31 +65,15 @@ export default {
},
methods: {
query () {
this.basicInfo = {
clientLocationCountry: 1212,
clientLocationProvince: '112.2.2.3',
clientLocationRegion: 'China',
clientAsn: 'Hebei',
serverLocationCountry: 'Xingtai',
serverLocationProvince: 'hehe',
serverLocationRegion: '2.2.2.2',
serverAsn: 'China',
domainCategoryName: 'Hebei',
domainCategoryGroup: 'Xingtai',
domainReputationScore: 'hehe',
domainReputationLevel: 'high',
appCategory: 'vpn',
appSubcategory: 'foreign vpn',
appRisk: 'critical'
}
/* this.queryBasic().then(responses => {
}) */
this.queryBasic().then(responses => {
responses && (this.basicInfo = responses)
})
},
queryBasic () {
return new Promise((resolve, reject) => {
try {
getData(api.detection.performanceEvent.overviewBasic, { serverIp: this.detection.serverIp }).then(data => {
resolve(data)
getData(api.detection.performanceEvent.overviewBasic, { serverIp: this.detection.serverIp,startTime: this.detection.startTime }).then(data => {
resolve(data[0])
}).catch(error => {
reject(error)
})

View File

@@ -3,7 +3,7 @@
<div class="overview__left">
<div class="overview__title">{{$t('overall.remark')}}</div>
<div class="overview__row">
<div class="row__content">{{basicInfo.malwareDescription || '-'}}</div>
<div class="row__content">Description</div>
</div>
<div class="overview__title">Fields</div>
<div class="overview__row">
@@ -117,7 +117,7 @@
<div class="overview__title">{{$t('detections.relatedDetections')}}</div>
<div class="overview__row-timeline">
<div class="row-timeline" v-for="event in events" :key="event">
<div class="row-timeline__time-info" :style="event.startTime === basicInfo.startTime ? 'color: #333;font-weight: bold;' : ''">{{formatT0(event.startTime)}}</div>
<div class="row-timeline__time-info" :style="event.startTime === basicInfo.startTime ? 'color: #333;font-weight: bold;' : ''">{{formatT0(event)}}</div>
<div class="row-timeline__line">
<div class="line-point-larger" v-if="event.startTime === basicInfo.startTime">
<div class="line-point"></div>
@@ -154,7 +154,8 @@
import { get } from '@/utils/http'
import { api } from '@/utils/api'
import { getMillisecond } from '@/utils/date-util'
import { eventSeverityColor } from '@/utils/constants'
import { eventSeverityColor, unitTypes } from '@/utils/constants'
import unitConvert from '@/utils/unit-convert'
export default {
name: 'DetectionOverview',
props: {
@@ -170,126 +171,41 @@ export default {
},
computed: {
formatT0 () {
return function (startTime) {
return startTime === this.basicInfo.startTime ? 'T0' : 'T0-10m'
return function (event) {
const diffSeconds = event.diffSeconds
if (diffSeconds === 0) {
return 'T0'
}
const eventStartTime = event.startTime
const entityStartTime = this.basicInfo ? this.basicInfo.startTime : ''
if (!this.$_.isEmpty(diffSeconds) && !this.$_.isEmpty(eventStartTime) && !this.$_.isEmpty(entityStartTime)) {
const suffix = unitConvert(diffSeconds, unitTypes.time, 's', null, 0).join('')
if (eventStartTime > entityStartTime) {
return `T0+${suffix}`
} else if (eventStartTime < entityStartTime) {
return `T0-${suffix}`
}
}
return ''
}
}
},
methods: {
getMillisecond,
query () {
this.basicInfo = {
eventId: 1212,
offenderIp: '112.2.2.3',
offenderLocationCountry: 'China',
offenderLocationProvince: 'Hebei',
offenderLocationRegion: 'Xingtai',
offenderAsn: 'hehe',
victimIp: '2.2.2.2',
victimLocationCountry: 'China',
victimLocationProvince: 'Hebei',
victimLocationRegion: 'Xingtai',
victimAsn: 'hehe',
domain: '5aibj.com',
domainCategoryName: 'bbs',
domainCategoryGroup: 'hehe',
domainReputationLevel: 'high',
appName: 'express vpn',
appCategory: 'vpn',
appSubcategory: 'foreign vpn',
appRisk: 'critical',
cryptominingPool: 'btcP',
cryptominingCoinType: 'btc',
cryptominingSoftware: 'a',
malwareName: 'gtw',
malwareAlias: 'gt',
malwareDescription: 'this is description,this is description,this is description,this is description,this is description,this is description,this is description,this is description,this is description,this is description,',
malwarePlatforms: 'windows',
malwareTechniques: 'Audio captures',
malwareGroups: 'Silver terrier',
startTime: 1645417930
}
this.events = [
{
eventSeverity: 'high',
securityType: 'command and control',
offenderIp: '2.2.2.2',
victimIp: '2.2.2.3',
startTime: 1645307930
},
{
eventSeverity: 'critical',
securityType: 'command and control',
offenderIp: '2.2.2.2',
victimIp: '112.2.2.3',
startTime: 1645317930
},
{
eventSeverity: 'high',
securityType: 'command and control',
offenderIp: '2.2.2.2',
victimIp: '2.2.2.3',
startTime: 1645327930
},
{
eventSeverity: 'high',
securityType: 'command and control',
offenderIp: '2.2.2.2',
victimIp: '2.2.2.3',
startTime: 1645337930
},
{
eventSeverity: 'high',
securityType: 'command and control',
offenderIp: '2.2.2.2',
victimIp: '2.2.2.3',
startTime: 1645347930
},
{
eventSeverity: 'high',
securityType: 'command and control',
offenderIp: '2.2.2.2',
victimIp: '2.2.2.3',
startTime: 1645357930
},
{
eventSeverity: 'high',
securityType: 'command and control',
offenderIp: '2.2.2.2',
victimIp: '2.2.2.3',
startTime: 1645367930
},
{
eventSeverity: 'high',
securityType: 'command and control',
offenderIp: '2.2.2.2',
victimIp: '2.2.2.3',
startTime: 1645397930
},
{
eventSeverity: 'high',
securityType: 'command and control',
offenderIp: '2.2.2.2',
victimIp: '2.2.2.3',
startTime: 1645407930
},
{
eventSeverity: 'high',
securityType: 'command and control',
offenderIp: '2.2.2.2',
victimIp: '2.2.2.3',
startTime: 1645417930
}
]
Promise.all([this.queryBasic(), this.queryEvent()]).then(responses => {
console.info(responses)
responses[0] && (this.basicInfo = responses[0])
responses[1] && (this.events = responses[1])
})
},
queryBasic () {
return new Promise((resolve, reject) => {
try {
get(api.detectionOverviewBasic, { eventId: this.detection.eventId }).then(response => {
get(api.detection.securityEvent.overviewBasic, { eventId: this.detection.eventId,startTime: this.detection.startTime }).then(response => {
if (response.code === 200) {
resolve(response.data.list)
resolve(response.data.result[0])
} else {
reject(response)
}
@@ -302,9 +218,9 @@ export default {
queryEvent () {
return new Promise((resolve, reject) => {
try {
get(api.detectionOverviewEvent, { eventId: this.detection.eventId }).then(response => {
get(api.detection.securityEvent.overviewEvent, { startTime: this.detection.startTime, offenderIp: this.detection.offenderIp, victimIp: this.detection.victimIp }).then(response => {
if (response.code === 200) {
resolve(response.data.list)
resolve(response.data.result)
} else {
reject(response)
}

View File

@@ -257,8 +257,7 @@ export default {
const queryParams = {
startTime: parseInt(this.timeFilter.startTime / 1000),
endTime: parseInt(this.timeFilter.endTime / 1000),
ip: this.entityData.ipAddr,
country: this.entityData.ipLocationCountry
ip: this.entityData.ipAddr
}
return queryParams
},