CN-1676 fix: Detection 新内容实现

This commit is contained in:
刘洪洪
2024-08-09 16:09:49 +08:00
parent f170dfc1c8
commit 53f7124d14
22 changed files with 1607 additions and 429 deletions

View File

@@ -138,6 +138,29 @@ $bg-color-page: var(--el-bg-color-page);
border-radius: 3px; border-radius: 3px;
margin-right: 10px; margin-right: 10px;
} }
.detection-event-name {
font-family: NotoSansSChineseRegular;
font-size: 16px;
color: var(--el-text-color-primary);
font-weight: 400;
}
.detection-event-key {
font-family: Roboto-Black;
font-style: italic;
font-size: 12px;
color: var(--el-color-info);
letter-spacing: 0;
line-height: 14px;
font-weight: 400;
margin-left: 4px;
}
.detection-event-line {
border-left: 1px var(--el-color-info) solid;
margin: 8px;
}
} }
.cn-detection__body { .cn-detection__body {
@@ -188,6 +211,35 @@ $bg-color-page: var(--el-bg-color-page);
color: var(--el-color-success); color: var(--el-color-success);
font-weight: 500; font-weight: 500;
} }
.item__key {
font-family: NotoSansSChineseRegular;
font-size: 16px;
color: var(--el-text-color-primary) !important;
font-weight: 400;
}
.item__key__type {
font-family: Roboto-Black;
font-style: italic;
font-size: 12px;
color: var(--el-color-info);
letter-spacing: 0;
line-height: 14px;
font-weight: 400;
margin-left: 4px;
margin-right: 16px;
}
.item__key__nums {
background: rgba(250,144,28,0.14);
border-radius: 12px;
font-family: NotoSansSChineseRegular;
font-size: 12px;
color: #FA901C !important;
font-weight: 400;
padding: 2px 8px;
}
} }
.basic-info__item1 { .basic-info__item1 {

View File

@@ -58,6 +58,11 @@ $color-regular: var(--el-text-color-regular);
width: 80px; width: 80px;
} }
.row__content__charts {
width: 600px;
height: 220px;
}
.row__content--metric { .row__content--metric {
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
@@ -114,8 +119,20 @@ $color-regular: var(--el-text-color-regular);
.row__content1 { .row__content1 {
display: block; display: block;
padding-right: 50px; padding-right: 50px;
.charts__visual__map {
width: 490px;
height: 32px;
margin: 12px 0 10px 60px;
background: linear-gradient(to right, #d7c668, #ffdd4a, #ffb65a, #ff9a79, #d84c4c);
}
} }
} }
.overview__row__display {
display: flex;
flex-direction: column;
}
} }
} }
.overview__row-timeline { .overview__row-timeline {

View File

@@ -12,6 +12,38 @@
width: 100%; width: 100%;
} }
.detections__search {
display: flex;
.detections__search__btns {
width: 80px;
height: 40px;
border: 1px solid var(--el-border-color-light);
margin-right: 10px;
border-radius: 2px;
display: flex;
div {
width: 40px;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--el-fill-color);
cursor: pointer;
i {
color: var(--el-text-color-primary);
}
}
.active__btn {
i {
color: var(--el-color-primary) !important;
}
background-color: var(--el-bg-color) !important;
}
}
}
.detections__container { .detections__container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "cn-icon"; /* Project id 2614877 */ font-family: "cn-icon"; /* Project id 2614877 */
src: url('iconfont.woff2?t=1711625913930') format('woff2'), src: url('iconfont.woff2?t=1722997039116') format('woff2'),
url('iconfont.woff?t=1711625913930') format('woff'), url('iconfont.woff?t=1722997039116') format('woff'),
url('iconfont.ttf?t=1711625913930') format('truetype'); url('iconfont.ttf?t=1722997039116') format('truetype');
} }
.cn-icon { .cn-icon {
@@ -13,6 +13,14 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.cn-icon-fuhe:before {
content: "\e815";
}
.cn-icon-danfenxi:before {
content: "\e816";
}
.cn-icon-tag-fill:before { .cn-icon-tag-fill:before {
content: "\e775"; content: "\e775";
} }

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -141,6 +141,272 @@ if (openMock) {
data.status = 1 data.status = 1
} }
return {
msg: 'success',
code: 200,
data: data
}
})
Mock.mock(new RegExp(urlAndVersion + '/detection/event/keyFields/statistics.*'), 'get', function (requestObj) {
const data = {
resultType: 'table',
result: [
{ key: '192.168.1.1, test.com', count: 25 },
{ key: 'baidu.com,app', count: 23 },
{ key: '192.168.2.33, app', count: 15 },
{ key: '192.168.8.8, test.com', count: 12 },
{ key: 'baidu.com, test.com', count: 8 },
{ key: '192.168.1.101, test.cn', count: 5 },
{ key: 'jd.com, app', count: 25 }
]
}
return {
msg: 'success',
code: 200,
data: data
}
})
Mock.mock(new RegExp(urlAndVersion + '/detection/event/list.*'), 'get', function (requestObj) {
const data = {
resultType: 'table',
result: [
{
eventId: 1212,
eventType: 'Anonymity',
eventName: 'Tor',
matchIds: '1, 2',
keyFields: 'ip, domain',
keyValues: '192.168.1.1, test.com',
ruleId: 2,
ruleVersion: '1',
ruleType: 'indicator_match',
isBuiltin: 1,
status: 1,
startTime: 1697092617,
endTime: 1697092777,
durationS: 30
},
{
eventId: 1213,
eventType: 'Anonymity',
eventName: 'Tor',
matchIds: '3, 4',
keyFields: 'ip, domain',
keyValues: '192.168.1.1, test.com',
ruleId: 3,
ruleVersion: '1',
ruleType: 'threshold',
isBuiltin: 1,
status: 1,
startTime: 1697092617,
endTime: 1697092777,
durationS: 30
},
{
eventId: 1214,
eventType: 'Anonymity',
eventName: 'Tor',
matchIds: '5, 6',
keyFields: 'ip, domain',
keyValues: '192.168.1.1, test.com',
ruleId: 3,
ruleVersion: '1',
ruleType: 'sequence/unordered_sequence',
isBuiltin: 1,
status: 0,
startTime: 1697092617,
endTime: 1697092777,
durationS: 30
}
]
}
return {
msg: 'success',
code: 200,
data: data
}
})
Mock.mock(new RegExp(urlAndVersion + '/detection/event/name/statistics.*'), 'get', function (requestObj) {
const data = {
resultType: 'table',
result: [
{
eventName: 'event1',
count: 25
},
{
eventName: 'event2',
count: 23
},
{
eventName: 'event3',
count: 15
},
{
eventName: 'event4',
count: 12
}
]
}
return {
msg: 'success',
code: 200,
data: data
}
})
Mock.mock(new RegExp(urlAndVersion + '/detection/event/count.*'), 'get', function (requestObj) {
const data = {
resultType: 'single',
result: 3
}
return {
msg: 'success',
code: 200,
data: data
}
})
Mock.mock(new RegExp(urlAndVersion + '/detection/security/event/timedistribution.*'), 'get', function (requestObj) {
const data = {
resultType: 'table',
result: [
{
statTime: 1722565322,
severity: 'critical',
count: 25
},
{
statTime: 1722565502,
severity: 'info',
count: 25
},
{
statTime: 1722566702,
severity: 'critical',
count: 25
},
{
statTime: 1722568322,
severity: 'critical',
count: 25
}
]
}
return {
msg: 'success',
code: 200,
data: data
}
})
Mock.mock(new RegExp(urlAndVersion + '/detection/security/event/detail.*'), 'get', function (requestObj) {
const data = {
eventIds: [1, 2, 3, 4, 5],
indicatorMatchs: [],
thresholdMatchs: [
{
matchId: 2,
ruleId: 2,
ruleType: 'threshold',
eventType: 'Command and Control',
eventName: 'event2',
severity: 'high',
keyFields: 'domain',
keyValues: 'test.com',
thresholdNum: 3,
recordsNum: 5,
reset: 60,
startTime: 169780543432,
endTime: 169790486213
}
],
sequenceMatchs: [
{
matchId: 3,
ruleId: 3,
ruleType: 'sequence/unordered_sequence',
eventType: 'Command and Control',
eventName: 'event3',
severity: 'low',
eventInfo: '[{"stage_id":"A","recv_time":10000000,"client_ip":"192.168.1.1"},{"stage_id":"B","recv_time":10000001,"client_ip":"192.168.1.2",...}]'
}
]
}
const indicatorMatchObj = {
matchId: 1,
ruleId: 1,
ruleType: 'indicator_match',
eventType: 'Anonymity',
eventName: 'event1',
severity: 'critical',
matchNum: 20,
indicatorFields: 'ip,domain',
indicatorValues: '192.168.1.1,test.com',
reset: 60,
clientIp: '192.168.1.1',
client_country_region: 'china',
client_super_admin_area: 'beijing',
client_admin_area: 'beijing',
client_longitude: '116.30',
client_latitude: '40.50',
serverIp: '192.168.1.2',
server_country_region: 'china',
server_super_admin_area: 'beijing',
server_admin_area: 'beijing',
server_longitude: '116.30',
server_latitude: '40.50',
domain: 'test.com',
app: 'test',
matchTime: 1722503700000
}
for (let i = 0; i < 10; i++) {
data.indicatorMatchs.push(JSON.parse(JSON.stringify(indicatorMatchObj)))
indicatorMatchObj.matchId += 1
indicatorMatchObj.ruleId += 2
indicatorMatchObj.matchTime += 900000
indicatorMatchObj.matchNum = Math.floor((Math.random() * 100) + 1)
}
return {
msg: 'success',
code: 200,
data: data
}
})
Mock.mock(new RegExp(urlAndVersion + '/detection/event/status/statistics.*'), 'get', function (requestObj) {
const data = {
resultType: 'table',
result: [
{
status: 1,
count: 25
},
{
status: 0,
count: 23
}
]
}
return {
msg: 'success',
code: 200,
data: data
}
})
Mock.mock(new RegExp(urlAndVersion + '/detection/event/type/statistics.*'), 'get', function (requestObj) {
const data = {
resultType: 'table',
result: [
{ eventType: 'Anonymity', count: 25 },
{ eventType: 'Command and Control', count: 13 }
]
}
return { return {
msg: 'success', msg: 'success',
code: 200, code: 200,

View File

@@ -176,6 +176,17 @@ export const api = {
create: { create: {
topKeys: apiVersion + '/detection/topKeys', // topKeys列表 topKeys: apiVersion + '/detection/topKeys', // topKeys列表
create: apiVersion + '/rule/detection' create: apiVersion + '/rule/detection'
},
event: {
keyStatistics: apiVersion + '/detection/event/keyFields/statistics', // 事件key统计
nameStatistics: apiVersion + '/detection/event/name/statistics', // 事件名称统计
statusStatistics: apiVersion + '/detection/event/status/statistics', // 状态统计
typeStatistics: apiVersion + '/detection/event/type/statistics', // 事件类型统计
list: apiVersion + '/detection/event/list', // 事件列表
count: apiVersion + '/detection/event/count', // 事件总数
timeDistribution: apiVersion + '/detection/event/timedistribution', // 事件等级分布
detail: apiVersion + '/detection/event/detail', // 事件详情
detailTimeDistribution: apiVersion + '/detection/event/detail/timedistribution' // 事件详情分布统计
} }
}, },
// Dashboard // Dashboard

File diff suppressed because one or more lines are too long

View File

@@ -3,7 +3,7 @@
<div class="filter-case__header">{{$t('detections.filters')}}</div> <div class="filter-case__header">{{$t('detections.filters')}}</div>
<div class="no-data" v-if="isNoData">{{ $t('npm.noData') }}</div> <div class="no-data" v-if="isNoData">{{ $t('npm.noData') }}</div>
<template v-for="(filter, index) in filterData" :key="index"> <template v-for="(filter, index) in filterData" :key="index">
<div class="detection-filter" v-show="filter.data.length > 0"> <div class="detection-filter" v-show="filter.data.length > 0 && filter.show">
<div class="filter__header">{{filter.title}}</div> <div class="filter__header">{{filter.title}}</div>
<div class="filter__body" style="position: relative"> <div class="filter__body" style="position: relative">

View File

@@ -5,18 +5,38 @@
<div class="detection-list--list"> <div class="detection-list--list">
<div class="no-data" v-if="myListData.length===0">{{ $t('npm.noData') }}</div> <div class="no-data" v-if="myListData.length===0">{{ $t('npm.noData') }}</div>
<div v-if="!isCollapse" @click="collapse" class="cn-detection__shadow new-cn-detection__shadow"></div> <div v-if="!isCollapse" @click="collapse" class="cn-detection__shadow new-cn-detection__shadow"></div>
<detection-row <template v-if="eventFlag===detectionEventType.single">
class="detection-border margin-b-10" <detection-row
v-for="(data, index) in myListData" class="detection-border margin-b-10"
:detection="data" v-for="(data, index) in myListData"
:page-type="pageType" :detection="data"
:timeFilter="timeFilter" :page-type="pageType"
:key="index" :timeFilter="timeFilter"
:pageObj="pageObj" :key="index"
:ref="`detectionRow${index}`" :pageObj="pageObj"
:index="index" :eventFlag="eventFlag"
@switchCollapse="switchCollapse" :ref="`detectionRow${index}`"
></detection-row> :index="index"
@switchCollapse="switchCollapse"
:q="q"
></detection-row>
</template>
<template v-if="eventFlag===detectionEventType.aggregation">
<detection-row-events
class="detection-border margin-b-10"
v-for="(data, index) in myListData"
:detection="data"
:page-type="pageType"
:timeFilter="timeFilter"
:eventFlag="eventFlag"
:key="index"
:pageObj="pageObj"
:ref="`detectionRow${index}`"
:index="index"
:q="q"
@switchCollapse="switchCollapse"
></detection-row-events>
</template>
</div> </div>
</div> </div>
</div> </div>
@@ -27,11 +47,14 @@ import DetectionRow from '@/views/detections/DetectionRow'
import Loading from '@/components/common/Loading' import Loading from '@/components/common/Loading'
import axios from 'axios' import axios from 'axios'
import { api } from '@/utils/api' import { api } from '@/utils/api'
import DetectionRowEvents from '@/views/detections/DetectionRowEvents'
import { detectionEventType } from '@/utils/constants'
export default { export default {
name: 'DetectionList', name: 'DetectionList',
components: { components: {
Loading, Loading,
DetectionRow DetectionRow,
DetectionRowEvents
}, },
props: { props: {
listData: Array, listData: Array,
@@ -39,7 +62,9 @@ export default {
pageObj: Object, pageObj: Object,
loading: Boolean, loading: Boolean,
timeFilter: Object, timeFilter: Object,
pageType: String // 安全事件、服务质量 pageType: String, // 安全事件、服务质量
eventFlag: String, // 事件标识,单独/聚合
q: String
}, },
data () { data () {
return { return {
@@ -51,7 +76,8 @@ export default {
tableId: 'detectionList', tableId: 'detectionList',
listDataCopy: [], listDataCopy: [],
noData: true, noData: true,
myListData: [] // listData的克隆避免因为修改listData里的malWareName而触发watch监听 myListData: [], // listData的克隆避免因为修改listData里的malWareName而触发watch监听
detectionEventType
} }
}, },
mounted () { mounted () {

View File

@@ -9,62 +9,38 @@
</div> </div>
</div> </div>
<div class="cn-detection__case"> <div class="cn-detection__case">
<div class="cn-detection__icon" :style="`background-color: ${eventSeverityColor[detection.severity]}`"></div>
<div class="cn-detection__row"> <div class="cn-detection__row">
<div class="cn-detection__header" v-if="pageType === detectionPageType.securityEvent"> <div class="cn-detection__header">
<span <span class="detection-event-severity-block" style="margin-left: 16px;">{{ detection.eventName || '-' }}</span>
class="detection-event-severity-color-block" <div v-for="(item, index) in myDetection.keyList" :key="index">
:style="`background-color: ${eventSeverityColor[detection.eventSeverity]}`"> <span class="detection-event-name">{{item.key}}</span>
</span> <span class="detection-event-key">({{ item.type }})</span>
<span class="detection-event-severity-block">{{ detection.eventName || '-' }}</span> <span v-if="index < myDetection.keyList.length - 1" class="detection-event-line"></span>
<i class="cn-icon cn-icon-attacker detection-list-icon" ></i>{{detection.offenderIp || '-'}} </div>
<div v-if="detection.domain" class="domain">{{detection.domain}}</div>
<span class="line">-------</span>
<span class="circle"></span>
<i class="cn-icon cn-icon-attacked detection-list-icon" ></i>{{detection.victimIp || '-'}}
</div>
<div class="cn-detection__header" v-else-if="pageType === detectionPageType.performanceEvent">
<div class="cn-entity__icon"><i :class="iconClass"></i></div>
<div style="padding-left: 3px;">{{detection.serverIp || detection.domain || detection.appName || 'Unknown'}}</div>
</div> </div>
<div class="cn-detection__body"> <div class="cn-detection__body">
<div class="body__basic-info"> <div class="body__basic-info">
<div class="basic-info"> <div class="basic-info">
<div class="basic-info__item" v-if="detection.severity"> <div class="basic-info__item" style="margin-left: 16px;" v-if="detection.eventType">
<i class="cn-icon cn-icon-severity-level"></i>
<span>{{$t('detection.list.security')}}&nbsp;:&nbsp;&nbsp;</span>
<span>{{changeI18nOfSeverity(detection.severity)}}</span>
</div>
<div class="basic-info__item" v-if="detection.eventSeverity">
<i class="cn-icon cn-icon-severity-level"></i>
<span>{{$t('detections.severity')}}&nbsp;:&nbsp;&nbsp;</span>
<span>{{detection.eventSeverity || '-'}}</span>
</div>
<div class="basic-info__item" v-if="detection.eventType">
<i class="cn-icon cn-icon-event-type"></i> <i class="cn-icon cn-icon-event-type"></i>
<span>{{$t('detections.eventType')}}&nbsp;:&nbsp;&nbsp;</span> <span>{{$t('detections.eventType')}}&nbsp;:&nbsp;&nbsp;</span>
<span>{{detection.eventType || '-'}}</span> <span>{{detection.eventType || '-'}}</span>
</div> </div>
<div class="basic-info__item" v-if="detection.malware">
<i class="cn-icon cn-icon-trojan"></i>
<span>{{$t('detection.list.malwareName')}}&nbsp;:&nbsp;&nbsp;</span>
<span>{{ $_.get(detection, 'malware.malwareName', '-') || '-' }}</span>
</div>
<div class="basic-info__item" v-if="detection.darkweb">
<i class="cn-icon cn-icon-trojan"></i>
<span>{{$t('detection.nodeType')}}&nbsp;:&nbsp;&nbsp;</span>
<span>{{ $_.get(detection, 'darkweb.nodeType', '-') || '-' }}</span>
</div>
<div class="basic-info__item"> <div class="basic-info__item">
<i class="cn-icon cn-icon-time2"></i> <i class="cn-icon cn-icon-time2"></i>
<span>{{$t('detection.list.startTime')}}&nbsp;:&nbsp;&nbsp;</span> <span>{{$t('detection.list.startTime')}}&nbsp;:&nbsp;&nbsp;</span>
<span>{{dateFormatByAppearance(detection.startTime) || '-'}}</span> <span>{{dateFormatByAppearance(detection.startTime) || '-'}}</span>
</div> </div>
<div class="basic-info__item">
<i class="cn-icon cn-icon-time2"></i>
<span>{{$t('detection.list.endTime')}}&nbsp;:&nbsp;&nbsp;</span>
<span>{{dateFormatByAppearance(detection.endTime) || '-'}}</span>
</div>
<div class="basic-info__item"> <div class="basic-info__item">
<i class="cn-icon cn-icon-duration"></i> <i class="cn-icon cn-icon-duration"></i>
<span>{{$t('overall.duration')}}&nbsp;:&nbsp;&nbsp;</span> <span>{{$t('overall.duration')}}&nbsp;:&nbsp;&nbsp;</span>
<span> <span>
{{ detection.matchTimes || '-'}} {{ $t('detection.list.times') }} /
{{unitConvert(parseInt(detection.durationS), 'time', 's', null, 0).join(' ') || '-'}} {{unitConvert(parseInt(detection.durationS), 'time', 's', null, 0).join(' ') || '-'}}
</span> </span>
<div v-if="parseInt(detection.status) === 0" class="margin-l-10 detection-row-active">{{$t('detections.active')}}</div> <div v-if="parseInt(detection.status) === 0" class="margin-l-10 detection-row-active">{{$t('detections.active')}}</div>
@@ -76,32 +52,27 @@
<el-collapse-transition> <el-collapse-transition>
<div class="cn-detection__detail-overview" v-if="!isCollapse"> <div class="cn-detection__detail-overview" v-if="!isCollapse">
<el-divider></el-divider> <el-divider></el-divider>
<detection-security-event-overview <indicator-match-overview
v-if="pageType === detectionPageType.securityEvent" v-if="detection.ruleType===detectionRuleType.indicator.key"
:detection="detection" :detection="detection"
:time-filter="timeFilter" :time-filter="timeFilter"
:pageObj="pageObj" :pageObj="pageObj"
></detection-security-event-overview> :q="q"
<template v-else> ></indicator-match-overview>
<detection-performance-event-ip-overview <threshold-overview
v-if="detection.entityType === entityType.ip.toLowerCase()" v-if="detection.ruleType===detectionRuleType.threshold.key"
:detection="detection" :detection="detection"
:time-filter="timeFilter" :time-filter="timeFilter"
:pageObj="pageObj" :pageObj="pageObj"
></detection-performance-event-ip-overview> :q="q"
<detection-performance-event-domain-overview ></threshold-overview>
v-else-if="detection.entityType === entityType.domain.toLowerCase()" <sequence-overview
:detection="detection" v-if="detection.ruleType===detectionRuleType.sequence.key || detection.ruleType===detectionRuleType.unordered.key"
:time-filter="timeFilter" :detection="detection"
:pageObj="pageObj" :time-filter="timeFilter"
></detection-performance-event-domain-overview> :pageObj="pageObj"
<detection-performance-event-app-overview :q="q"
v-else-if="detection.entityType === entityType.app.toLowerCase()" ></sequence-overview>
:detection="detection"
:time-filter="timeFilter"
:pageObj="pageObj"
></detection-performance-event-app-overview>
</template>
</div> </div>
</el-collapse-transition> </el-collapse-transition>
</div> </div>
@@ -109,38 +80,41 @@
</template> </template>
<script> <script>
import { eventSeverityColor, detectionPageType, entityType } from '@/utils/constants' import { eventSeverityColor, detectionPageType, entityType, detectionRuleType } from '@/utils/constants'
import { getMillisecond, dateFormatByAppearance } from '@/utils/date-util' import { getMillisecond, dateFormatByAppearance } from '@/utils/date-util'
import unitConvert from '@/utils/unit-convert' import unitConvert from '@/utils/unit-convert'
import DetectionSecurityEventOverview from '@/views/detections/overview/DetectionSecurityEventOverview'
import DetectionPerformanceEventIpOverview from '@/views/detections/overview/DetectionPerformanceEventIpOverview'
import DetectionPerformanceEventAppOverview from '@/views/detections/overview/DetectionPerformanceEventAppOverview'
import DetectionPerformanceEventDomainOverview from '@/views/detections/overview/DetectionPerformanceEventDomainOverview'
import { overwriteUrl, urlParamsHandler, changeI18nOfSeverity } from '@/utils/tools' import { overwriteUrl, urlParamsHandler, changeI18nOfSeverity } from '@/utils/tools'
import IndicatorMatchOverview from '@/views/detections/overview/IndicatorMatchOverview'
import ThresholdOverview from '@/views/detections/overview/ThresholdOverview'
import SequenceOverview from '@/views/detections/overview/SequenceOverview'
export default { export default {
name: 'DetectionRow', name: 'DetectionRow',
components: { components: {
DetectionSecurityEventOverview, IndicatorMatchOverview,
DetectionPerformanceEventIpOverview, ThresholdOverview,
DetectionPerformanceEventAppOverview, SequenceOverview
DetectionPerformanceEventDomainOverview
}, },
props: { props: {
index: Number, index: Number,
timeFilter: Object, timeFilter: Object,
detection: Object, detection: Object,
pageType: String, // 安全事件、服务质量 pageType: String, // 安全事件、服务质量
pageObj: Object pageObj: Object,
q: String
}, },
data () { data () {
return { return {
entityType, entityType,
detectionPageType, detectionPageType,
isCollapse: true, // 是否是折叠状态, true为折叠false为展开 isCollapse: true, // 是否是折叠状态, true为折叠false为展开
eventSeverityColor eventSeverityColor,
detectionRuleType,
myDetection: {}
} }
}, },
mounted () { mounted () {
this.initKeyInfo()
this.initExpendTab() this.initExpendTab()
}, },
computed: { computed: {
@@ -244,6 +218,17 @@ export default {
this.$emit('switchCollapse', this.isCollapse, this.index) this.$emit('switchCollapse', this.isCollapse, this.index)
} }
} }
},
initKeyInfo () {
let keyValues = this.detection.keyValues
let keyFields = this.detection.keyFields
keyValues = keyValues.split(',')
keyFields = keyFields.split(',')
const keyList = []
keyValues.forEach((item, index) => {
keyList.push({ key: item, type: keyFields[index] })
})
this.myDetection.keyList = keyList
} }
} }
} }

View File

@@ -0,0 +1,231 @@
<template>
<div class="cn-detection--list" :style="{zIndex: !isCollapse ? 5 : 'unset'}">
<!-- 左侧下拉按钮 -->
<div class="cn-detection__collapse">
<div class="cn-detection__collapse-block" @click="switchCollapse">
<span :class="{'reg-down': !isCollapse}">
<i class="cn-icon cn-icon-arrow-right"></i>
</span>
</div>
</div>
<div class="cn-detection__case">
<div class="cn-detection__row">
<div class="cn-detection__body">
<div class="body__basic-info">
<div class="basic-info">
<div class="basic-info__item" style="margin-left: 16px;">
<span v-for="(item, index) in myDetection.keyList" :key="index">
<span class="item__key">{{item.key}}</span>
<span class="item__key__type">({{ item.type }})</span>
<span v-if="index < myDetection.keyList.length - 1" class="detection-event-line"></span>
</span>
<span class="item__key__nums">{{ detection.count }} Events</span>
</div>
<div class="basic-info__item">
<i class="cn-icon cn-icon-Event1"></i>
<span>{{$t('detections.eventName')}}&nbsp;:&nbsp;&nbsp;</span>
<span>{{detection.eventName || '-'}}</span>
</div>
<div class="basic-info__item">
<i class="cn-icon cn-icon-event-type"></i>
<span>{{$t('detections.eventType')}}&nbsp;:&nbsp;&nbsp;</span>
<span>{{detection.eventType || '-'}}</span>
</div>
<div class="basic-info__item">
<i class="cn-icon cn-icon-time2"></i>
<span>{{$t('detection.lastTime')}}&nbsp;:&nbsp;&nbsp;</span>
<span>{{dateFormatByAppearance(detection.startTime) || '-'}}</span>
</div>
</div>
</div>
</div>
</div>
<el-collapse-transition>
<div class="cn-detection__detail-overview" v-if="!isCollapse">
<el-divider></el-divider>
<indicator-match-overview
v-if="detection.ruleType===detectionRuleType.indicator.key"
:detection="detection"
:time-filter="timeFilter"
:page-obj="pageObj"
:event-flag="eventFlag"
:q="q"
></indicator-match-overview>
<threshold-overview
v-if="detection.ruleType===detectionRuleType.threshold.key"
:detection="detection"
:time-filter="timeFilter"
:pageObj="pageObj"
:event-flag="eventFlag"
:q="q"
></threshold-overview>
<sequence-overview
v-if="detection.ruleType===detectionRuleType.sequence.key || detection.ruleType===detectionRuleType.unordered.key"
:detection="detection"
:time-filter="timeFilter"
:pageObj="pageObj"
:event-flag="eventFlag"
:q="q"
></sequence-overview>
</div>
</el-collapse-transition>
</div>
</div>
</template>
<script>
import { eventSeverityColor, detectionPageType, entityType, detectionRuleType } from '@/utils/constants'
import { getMillisecond, dateFormatByAppearance } from '@/utils/date-util'
import unitConvert from '@/utils/unit-convert'
import { overwriteUrl, urlParamsHandler, changeI18nOfSeverity } from '@/utils/tools'
import IndicatorMatchOverview from '@/views/detections/overview/IndicatorMatchOverview'
import ThresholdOverview from '@/views/detections/overview/ThresholdOverview'
import SequenceOverview from '@/views/detections/overview/SequenceOverview'
export default {
name: 'DetectionRowEvents',
components: {
IndicatorMatchOverview,
ThresholdOverview,
SequenceOverview
},
props: {
index: Number,
timeFilter: Object,
detection: Object,
pageType: String, // 安全事件、服务质量
pageObj: Object,
eventFlag: String,
q: String
},
data () {
return {
entityType,
detectionPageType,
isCollapse: true, // 是否是折叠状态, true为折叠false为展开
eventSeverityColor,
detectionRuleType,
myDetection: {}
}
},
mounted () {
this.initKeyInfo()
this.initExpendTab()
},
computed: {
iconClass () {
let className
switch (this.detection.entityType) {
case ('ip'): {
className = 'cn-icon cn-icon-ip2'
break
}
case ('domain'): {
className = 'cn-icon cn-icon-domain2'
break
}
case ('app'): {
className = 'cn-icon cn-icon-app2'
break
}
default:
break
}
return className
},
pointColor () {
return function (detection) {
let color = '#8FA1BE'
if (detection.startTime && detection.endTime) {
if (getMillisecond(detection.endTime) - getMillisecond(detection.startTime) < 5 * 60 * 1000) {
color = 'var(--cn-color-critical)'
}
}
return { backgroundColor: color }
}
}
},
watch: {
isCollapse (newVal) {
const newQuery = this.$route.query
if (newVal && newQuery.eventId) {
delete newQuery.eventId
this.reloadUrl(newQuery, 'cleanOldParams')
}
}
},
methods: {
unitConvert,
getMillisecond,
dateFormatByAppearance,
changeI18nOfSeverity,
/* 切换折叠状态 */
switchCollapse () {
this.isCollapse = !this.isCollapse
this.$emit('switchCollapse', this.isCollapse, this.index)
if (this.isCollapse) {
const newQuery = this.$route.query
delete newQuery.eventId
this.reloadUrl(newQuery, 'cleanOldParams')
} else {
this.reloadUrl({ eventId: this.detection.eventId })
}
},
/* 设为折叠状态 */
collapse () {
this.isCollapse = true
},
/**
* 向地址栏添加/删除参数
*/
reloadUrl (newParam, clean) {
const { query } = this.$route
let newUrl = urlParamsHandler(window.location.href, query, newParam)
if (clean) {
newUrl = urlParamsHandler(window.location.href, query, newParam, clean)
}
overwriteUrl(newUrl)
},
/**
* 初始化从npm跳转过来的id并展开tab
*/
initExpendTab () {
if (this.$route.query.eventId) {
if (this.$route.query.eventId === this.detection.eventId) {
const container = document.getElementById('cnContainer')
const dom = document.getElementsByClassName('cn-detection__case')
// 未展开的item折叠块高度67+下边距10+底部线高度1兼容不同分辨率下的tab高度
let itemHeight = 78
if (dom && this.index > 0) {
itemHeight = dom[0].clientHeight + 11
}
let topHeight = 554 + this.index * itemHeight
// 经过测试对比第7个以后的tab会往上移动但是手动展开和自动展开tab的滚动条高度一致为解决该问题自动加上误差值
if (this.index > 6) {
topHeight = topHeight + 6
}
container.scrollTop = topHeight
this.isCollapse = false
this.$emit('switchCollapse', this.isCollapse, this.index)
}
}
},
initKeyInfo () {
let keyValues = this.detection.keyValues
let keyFields = this.detection.keyFields
keyValues = keyValues.split(',')
keyFields = keyFields.split(',')
const keyList = []
keyValues.forEach((item, index) => {
keyList.push({ key: item, type: keyFields[index] })
})
this.myDetection.keyList = keyList
}
}
}
</script>

View File

@@ -4,10 +4,6 @@
<div class="explorer-top-tools explorer-detection-top-tools"> <div class="explorer-top-tools explorer-detection-top-tools">
<div class="explorer-top-tools-title">{{$t('overall.detections')}}</div> <div class="explorer-top-tools-title">{{$t('overall.detections')}}</div>
<div style="display: flex"> <div style="display: flex">
<div class="explorer-top-tools-block" @click="jumpNewDetetion" v-if="hasPermission('detectionPolicy')">
<i class="cn-icon cn-icon-configure-policies detection-icon-setting"></i>
<span>{{$t('config.detections.configurePolicies')}}</span>
</div>
<DateTimeRange <DateTimeRange
class="date-time-range" class="date-time-range"
:start-time="timeFilter.startTime" :start-time="timeFilter.startTime"
@@ -21,16 +17,25 @@
:end-time="timeFilter.endTime"/> :end-time="timeFilter.endTime"/>
</div> </div>
</div> </div>
<div style="width: 100%; padding-bottom: 50px;">
<chart-tabs :data="tabsData" router></chart-tabs>
</div>
<!-- 搜索组件 --> <!-- 搜索组件 -->
<detection-search <div class="detections__search">
ref="search" <div class="detections__search__btns">
:page-type="pageType" <div @click="clickEventFlag(detectionEventType.single)" :class="eventFlag===detectionEventType.single ? 'active__btn' : ''">
@search="search" <i class="cn-icon cn-icon-danfenxi"></i>
></detection-search> </div>
<div @click="clickEventFlag(detectionEventType.aggregation)" :class="eventFlag===detectionEventType.aggregation ? 'active__btn' : ''">
<i class="cn-icon cn-icon-fuhe"></i>
</div>
</div>
<detection-search
style="width: calc(100% - 92px);"
ref="search"
:page-type="pageType"
@search="search"
></detection-search>
</div>
<!-- 内容区 --> <!-- 内容区 -->
<div class="detections__container"> <div class="detections__container">
<loading :loading="loading"></loading> <loading :loading="loading"></loading>
@@ -44,17 +49,29 @@
<div style="display: flex; flex-grow: 1; height: 100%;"> <div style="display: flex; flex-grow: 1; height: 100%;">
<detection-filter <detection-filter
class="detection-border" class="detection-border"
:filter-data="filterData[pageType]" :filter-data="filterData"
:q="q" :q="q"
:time-filter="timeFilter" :time-filter="timeFilter"
@filter="getFilter" @filter="getFilter"
:event-flag="eventFlag"
></detection-filter> ></detection-filter>
<div class="detection__list"> <div class="detection__list">
<div class="detection__list-statistics detection-border"> <div class="detection__list-statistics detection-border">
<div class="statistics__category">
<div class="chart-header">
<div class="chart-header__title">{{$t('detection.eventType')}}</div>
</div>
<template v-if="isStatisticsCategoryNoData">
<div class="no-data chart-content" >{{ $t('npm.noData') }}</div>
</template>
<template v-else>
<div class="chart-content" :id="`detectionCategoryPer${pageType}`"></div>
</template>
</div>
<div class="statistics__severity"> <div class="statistics__severity">
<div class="chart-header"> <div class="chart-header">
<div class="chart-header__title">{{$t('detections.severity')}}</div> <div class="chart-header__title">{{$t('detections.eventName')}}</div>
</div> </div>
<template v-if="isStatisticsSeverityNoData"> <template v-if="isStatisticsSeverityNoData">
<div class="no-data chart-content" >{{ $t('npm.noData') }}</div> <div class="no-data chart-content" >{{ $t('npm.noData') }}</div>
@@ -64,20 +81,9 @@
</template> </template>
</div> </div>
<div class="statistics__category">
<div class="chart-header">
<div class="chart-header__title">{{$t('detections.eventType')}}</div>
</div>
<template v-if="isStatisticsCategoryNoData">
<div class="no-data chart-content" >{{ $t('npm.noData') }}</div>
</template>
<template v-else>
<div class="chart-content" :id="`detectionCategoryPer${pageType}`"></div>
</template>
</div>
<div class="statistics__active-attack"> <div class="statistics__active-attack">
<div class="chart-header"> <div class="chart-header">
<div class="chart-header__title">{{pageType === detectionPageType.securityEvent ? $t('detection.activeOffender') : $t('detections.activeEntity')}}</div> <div class="chart-header__title">Key</div>
</div> </div>
<template v-if="isStatisticsActiveAttackNoData"> <template v-if="isStatisticsActiveAttackNoData">
<div class="no-data chart-content" >{{ $t('npm.noData') }}</div> <div class="no-data chart-content" >{{ $t('npm.noData') }}</div>
@@ -92,6 +98,8 @@
:pageObj="pageObj" :pageObj="pageObj"
:time-filter="timeFilter" :time-filter="timeFilter"
:page-type="pageType" :page-type="pageType"
:event-flag="eventFlag"
:q="q"
@pageSize="pageSize" @pageSize="pageSize"
@pageNo="pageNo" @pageNo="pageNo"
:loading="listLoading" :loading="listLoading"
@@ -120,7 +128,7 @@ import TimeRefresh from '@/components/common/TimeRange/TimeRefresh'
import DetectionFilter from '@/views/detections/DetectionFilter' import DetectionFilter from '@/views/detections/DetectionFilter'
import DetectionList from '@/views/detections/DetectionList' import DetectionList from '@/views/detections/DetectionList'
import Pagination from '@/components/common/Pagination' import Pagination from '@/components/common/Pagination'
import { defaultPageSize, detectionPageType } from '@/utils/constants' import { defaultPageSize, detectionPageType, detectionEventType } from '@/utils/constants'
import { getNowTime, getSecond, getMillisecond } from '@/utils/date-util' import { getNowTime, getSecond, getMillisecond } from '@/utils/date-util'
import { ref, shallowRef } from 'vue' import { ref, shallowRef } from 'vue'
import * as echarts from 'echarts' import * as echarts from 'echarts'
@@ -136,7 +144,6 @@ import axios from 'axios'
import { urlParamsHandler, overwriteUrl, extensionEchartY, reverseSortBy, changeI18nOfSeverity } from '@/utils/tools' import { urlParamsHandler, overwriteUrl, extensionEchartY, reverseSortBy, changeI18nOfSeverity } from '@/utils/tools'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import Loading from '@/components/common/Loading' import Loading from '@/components/common/Loading'
import ChartTabs from '@/components/common/ChartTabs'
import { useStore } from 'vuex' import { useStore } from 'vuex'
import { format } from 'echarts' import { format } from 'echarts'
import Parser from '@/components/advancedSearch/meta/parser' import Parser from '@/components/advancedSearch/meta/parser'
@@ -151,8 +158,7 @@ export default {
TimeRefresh, TimeRefresh,
DetectionFilter, DetectionFilter,
DetectionList, DetectionList,
Pagination, Pagination
ChartTabs
}, },
data () { data () {
return { return {
@@ -162,98 +168,57 @@ export default {
path: '/detection/securityEvent', path: '/detection/securityEvent',
icon: 'cn-icon cn-icon-a-SecurityEvent' icon: 'cn-icon cn-icon-a-SecurityEvent'
} }
// {
// i18n: 'entities.regulatoryRiskEvents',
// path: '/detection/securityEvent',
// icon: 'cn-icon cn-icon-a-RegulatoryRiskEvent',
// disable: true
// },
// {
// i18n: 'overall.performanceEvents',
// path: '/detection/performanceEvent',
// icon: 'cn-icon cn-icon-a-PerformanceEvent'
// }
], ],
chartInit: [], chartInit: [],
// pageObj: {
// pageNo: 1,
// pageSize: defaultPageSize,
// total: 0,
// resetPageNo: true
// },
q: '', q: '',
detectionPageType, detectionPageType,
filterData: { filterData: [
securityEvent: [ {
{ title: this.$t('detection.eventType'),
title: this.$t('overall.status'), column: 'eventType',
column: 'status', topColumn: 'event_type',
topColumn: 'status', collapse: false,
collapse: false, value: [],
value: [], // value之间是or的关系 showMore: true,
showMore: false, showDisabled: true,
data: [] // 从接口动态获取,本项在获得数据后需要特殊处理左边框颜色 show: true,
}, showIndex: 5, // index作为showMore分割从1开始而非0
{ data: []
title: this.$t('detections.severity'), },
column: 'severity', {
topColumn: 'severity', title: this.$t('detections.eventName'),
collapse: false, column: 'eventName',
value: [], // value之间是or的关系 topColumn: 'event_name',
showMore: false, collapse: false,
data: [] // 从接口动态获取,本项在获得数据后需要特殊处理左边框颜色 value: [],
}, showMore: false,
{ showDisabled: true,
title: this.$t('detections.eventType'), show: true,
column: 'eventType', data: []
topColumn: 'event_type', },
collapse: false, {
value: [], title: 'Key',
showMore: true, column: 'keyFields',
showDisabled: true, topColumn: 'key_fields,key_values',
showIndex: 5, // index作为showMore分割从1开始而非0 collapse: false,
data: [] // 从接口动态获取 value: [],
}, showMore: true,
{ showDisabled: true,
title: this.$t('detections.victimIp'), showIndex: 5,
column: 'victimIP', show: true,
topColumn: 'victim_ip', data: [] // 从接口动态获取
collapse: false, },
value: [], {
showMore: true, title: this.$t('overall.status'),
showDisabled: true, column: 'status',
showIndex: 5, topColumn: 'status',
data: [] // 从接口动态获取 collapse: false,
}, value: [], // value之间是or的关系
{ showMore: false,
title: this.$t('detections.offenderIp'), show: true,
column: 'offenderIP', data: [] // 从接口动态获取,本项在获得数据后需要特殊处理左边框颜色
topColumn: 'offender_ip', }
collapse: false, ],
value: [],
showMore: true,
showDisabled: true,
showIndex: 5,
data: [] // 从接口动态获取
}
],
performanceEvent: [
{
title: this.$t('detections.eventSeverity'),
column: 'eventSeverity',
collapse: false,
value: [], // value之间是or的关系
data: [] // 从接口动态获取,本项在获得数据后需要特殊处理左边框颜色
},
{
title: this.$t('detections.eventType'),
column: 'eventType',
collapse: false,
value: [],
data: [] // 从接口动态获取
}
]
},
listData: [], listData: [],
listLoading: false, listLoading: false,
severityPerOption: null, severityPerOption: null,
@@ -273,12 +238,13 @@ export default {
loading: false, loading: false,
oldActiveEntitySearchValue: '', oldActiveEntitySearchValue: '',
initFlag: true, // 初始化标识初始化时保证mounted执行 initFlag: true, // 初始化标识初始化时保证mounted执行
detectionChart: shallowRef(null) detectionChart: shallowRef(null),
detectionEventType
} }
}, },
methods: { methods: {
initStatusData (params) { initStatusData (params) {
axios.get(api.detection[this.pageType].statusStatistics, { params }).then(res => { axios.get(api.detection.event.statusStatistics, { params }).then(res => {
if (res.status === 200) { if (res.status === 200) {
const data = res.data.data.result const data = res.data.data.result
if (data && data.length > 0) { if (data && data.length > 0) {
@@ -286,7 +252,7 @@ export default {
return Number(b.count) - Number(a.count) return Number(b.count) - Number(a.count)
}) })
} }
this.filterData[this.pageType][0].data = data.map(r => { this.filterData[3].data = data.map(r => {
let label = '' let label = ''
if (r.status === '0' || r.status === 0) { if (r.status === '0' || r.status === 0) {
label = this.$t('detections.active') label = this.$t('detections.active')
@@ -295,18 +261,18 @@ export default {
} }
return { label, value: r.status, count: r.count } return { label, value: r.status, count: r.count }
}) })
this.isCheckFilterByQ(params, 0) this.isCheckFilterByQ(params, 3)
} }
}).catch(e => { }).catch(e => {
console.error(e) console.error(e)
this.filterData[this.pageType][0].data = [] this.filterData[3].data = []
this.$message.error(this.errorMsgHandler(e)) this.$message.error(this.errorMsgHandler(e))
}) })
}, },
/** 初始化顶部大柱状图 */ /** 初始化顶部大柱状图 */
initEventSeverityTrendData (params) { initTimeDistribution (params) {
this.loading = true this.loading = true
axios.get(api.detection[this.pageType].timeDistribution, { params }).then(res => { axios.get(api.detection.event.timeDistribution, { params }).then(res => {
const data = res.data.data.result const data = res.data.data.result
this.eventSeverityData = data this.eventSeverityData = data
if (!this.$_.isEmpty(data)) { if (!this.$_.isEmpty(data)) {
@@ -394,8 +360,8 @@ export default {
}) })
}, },
/** 初始化左侧事件严重等级和第一个小饼图 */ /** 初始化左侧事件严重等级和第一个小饼图 */
initEventSeverityData (params) { initEventNameData (params) {
axios.get(api.detection[this.pageType].severityStatistics, { params }).then(res => { axios.get(api.detection.event.nameStatistics, { params }).then(res => {
const data = res.data.data.result const data = res.data.data.result
if (data && data.length > 0) { if (data && data.length > 0) {
data.sort((a, b) => { data.sort((a, b) => {
@@ -404,11 +370,11 @@ export default {
} }
this.statisticsSeverityData = data this.statisticsSeverityData = data
if (!this.$_.isEmpty(data)) { if (!this.$_.isEmpty(data)) {
this.filterData[this.pageType][1].data = data.map(r => ({ label: changeI18nOfSeverity(r.severity), value: r.severity, count: r.count })) this.filterData[1].data = data.map(r => ({ label: changeI18nOfSeverity(r.eventName), value: r.eventName, count: r.count }))
this.isCheckFilterByQ(params, 1) this.isCheckFilterByQ(params, 1)
const eventSeverityOption = this.$_.cloneDeep(pieForSeverity) const eventSeverityOption = this.$_.cloneDeep(pieForSeverity)
eventSeverityOption.series[0].data = data.map(d => { eventSeverityOption.series[0].data = data.map(d => {
return { value: d.count, name: changeI18nOfSeverity(d.severity), itemStyle: { color: getSeverityColor(d.severity) } } return { value: d.count, name: changeI18nOfSeverity(d.eventName), itemStyle: { color: getSeverityColor(d.eventName) } }
}) })
const chartDom = document.getElementById(`eventSeverityPie${this.pageType}`) const chartDom = document.getElementById(`eventSeverityPie${this.pageType}`)
let detectionChart = echarts.getInstanceByDom(chartDom) let detectionChart = echarts.getInstanceByDom(chartDom)
@@ -421,88 +387,32 @@ export default {
const vm = this const vm = this
detectionChart.off('click') detectionChart.off('click')
detectionChart.on('click', e => { detectionChart.on('click', e => {
if (this.pageType === 'performanceEvent') { this.getFilter(e.data.name, vm.filterData[1].column)
vm.filterData.performanceEvent[0].value = vm.triggerFilterDataValue(vm.filterData.performanceEvent[0].value, e.data.name) vm.filterData[1].value = vm.triggerFilterDataValue(vm.filterData[1].value, e.data.name)
} else if (this.pageType === 'securityEvent') {
this.getFilter(e.data.name, vm.filterData.securityEvent[1].column)
vm.filterData.securityEvent[1].value = vm.triggerFilterDataValue(vm.filterData.securityEvent[1].value, e.data.name)
}
}) })
} }
}).catch(e => { }).catch(e => {
console.error(e) console.error(e)
this.filterData[this.pageType][1].data = [] this.filterData[1].data = []
this.$message.error(this.errorMsgHandler(e))
})
},
initEventTypeData (params) {
axios.get(api.detection[this.pageType].eventType, { params }).then(res => {
const data = res.data.data.result
this.statisticsCategoryData = data
if (!this.$_.isEmpty(data)) {
this.filterData[this.pageType][2].data = data.map(r => ({
label: r.eventType,
value: r.eventType,
count: r.count
}))
const { showMore, showIndex, showDisabled } = this.computeFilterPage(this.filterData[this.pageType][2].data)
this.filterData[this.pageType][2].showMore = showMore
this.filterData[this.pageType][2].showIndex = showIndex
this.filterData[this.pageType][2].showDisabled = showDisabled
const chartDom = document.getElementById(`detectionCategoryPer${this.pageType}`)
let detectionChart = echarts.getInstanceByDom(chartDom)
if (detectionChart) {
echarts.dispose(detectionChart)
}
detectionChart = echarts.init(chartDom)
this.chartInit.push(shallowRef(detectionChart))
const securityTypeOption = this.$_.cloneDeep(pieForSeverity)
securityTypeOption.series[0].data = data.map(d => {
return { value: d.count, name: d.eventType }
})
if (chartDom) {
let oneColumnWidth = (chartDom.clientWidth * 0.56) - 30
if (data.length > 6) {
oneColumnWidth = (chartDom.clientWidth * 0.56) / 2 - 30
}
securityTypeOption.legend.formatter = function (name) {
return format.truncateText(name, oneColumnWidth, '12px')
}
}
detectionChart.setOption(securityTypeOption)
const vm = this
detectionChart.off('click')
detectionChart.on('click', e => {
vm.filterData.performanceEvent[1].value = vm.triggerFilterDataValue(vm.filterData.performanceEvent[1].value, e.data.name)
})
}
}).catch(e => {
console.error(e)
this.filterData[this.pageType][2].data = []
this.filterData[this.pageType][2].showMore = false
this.filterData[this.pageType][2].showIndex = 5
this.filterData[this.pageType][2].showDisabled = true
this.$message.error(this.errorMsgHandler(e)) this.$message.error(this.errorMsgHandler(e))
}) })
}, },
/** 第二个饼图和左侧filter的eventType */ /** 第二个饼图和左侧filter的eventType */
initSecurityTypeData (params) { initEventTypeData (params) {
axios.get(api.detection[this.pageType].eventTypeStatistics, { params }).then(res => { axios.get(api.detection.event.typeStatistics, { params }).then(res => {
const data = res.data.data.result const data = res.data.data.result
this.statisticsCategoryData = data this.statisticsCategoryData = data
if (!this.$_.isEmpty(data)) { if (!this.$_.isEmpty(data)) {
this.filterData[this.pageType][2].data = data.map(r => ({ this.filterData[0].data = data.map(r => ({
label: r.eventType, label: r.eventType,
value: r.eventType, value: r.eventType,
count: r.count count: r.count
})) }))
this.isCheckFilterByQ(params, 2) this.isCheckFilterByQ(params, 0)
const { showMore, showIndex, showDisabled } = this.computeFilterPage(this.filterData[this.pageType][2].data) const { showMore, showIndex, showDisabled } = this.computeFilterPage(this.filterData[0].data)
this.filterData[this.pageType][2].showMore = showMore this.filterData[0].showMore = showMore
this.filterData[this.pageType][2].showIndex = showIndex this.filterData[0].showIndex = showIndex
this.filterData[this.pageType][2].showDisabled = showDisabled this.filterData[0].showDisabled = showDisabled
const chartDom = document.getElementById(`detectionCategoryPer${this.pageType}`) const chartDom = document.getElementById(`detectionCategoryPer${this.pageType}`)
this.detectionChart = echarts.getInstanceByDom(chartDom) this.detectionChart = echarts.getInstanceByDom(chartDom)
if (this.detectionChart) { if (this.detectionChart) {
@@ -528,35 +438,35 @@ export default {
const vm = this const vm = this
this.detectionChart.off('click') this.detectionChart.off('click')
this.detectionChart.on('click', e => { this.detectionChart.on('click', e => {
this.getFilter(e.data.name, vm.filterData.securityEvent[2].column) this.getFilter(e.data.name, vm.filterData[0].column)
vm.filterData.securityEvent[2].value = vm.triggerFilterDataValue(vm.filterData.securityEvent[2].value, e.data.name) vm.filterData[0].value = vm.triggerFilterDataValue(vm.filterData[0].value, e.data.name)
}) })
} }
}).catch(e => { }).catch(e => {
console.error(e) console.error(e)
this.filterData[this.pageType][2].data = [] this.filterData[0].data = []
this.filterData[this.pageType][2].showMore = false this.filterData[0].showMore = false
this.filterData[this.pageType][2].showIndex = 5 this.filterData[0].showIndex = 5
this.filterData[this.pageType][2].showDisabled = true this.filterData[0].showDisabled = true
this.$message.error(this.errorMsgHandler(e)) this.$message.error(this.errorMsgHandler(e))
}) })
}, },
/** 横向柱状图和左侧filter的offenderIp */ /** 横向柱状图和左侧filter的key */
initOffenderIpData (params) { initKeyData (params) {
axios.get(api.detection[this.pageType].offenderIpStatistics, { params }).then(res => { axios.get(api.detection.event.keyStatistics, { params }).then(res => {
let data = res.data.data.result let data = res.data.data.result
this.statisticsActiveAttackData = data this.statisticsActiveAttackData = data
if (!this.$_.isEmpty(data)) { if (!this.$_.isEmpty(data)) {
this.filterData[this.pageType][4].data = data.map(r => ({ this.filterData[2].data = data.map(r => ({
label: r.offenderIp, label: `${r.keyFields},${r.keyValues}`,
value: r.offenderIp, value: r.keyFields,
count: r.count count: r.count
})) }))
this.isCheckFilterByQ(params, 4) this.isCheckFilterByQ(params, 2)
const { showMore, showIndex, showDisabled } = this.computeFilterPage(this.filterData[this.pageType][4].data) const { showMore, showIndex, showDisabled } = this.computeFilterPage(this.filterData[2].data)
this.filterData[this.pageType][4].showMore = showMore this.filterData[2].showMore = showMore
this.filterData[this.pageType][4].showIndex = showIndex this.filterData[2].showIndex = showIndex
this.filterData[this.pageType][4].showDisabled = showDisabled this.filterData[2].showDisabled = showDisabled
const chartDom = document.getElementById(`detectionActiveAttacker${this.pageType}`) const chartDom = document.getElementById(`detectionActiveAttacker${this.pageType}`)
let detectionChart = echarts.getInstanceByDom(chartDom) let detectionChart = echarts.getInstanceByDom(chartDom)
@@ -569,7 +479,7 @@ export default {
data.sort(reverseSortBy('count')) data.sort(reverseSortBy('count'))
data = data.slice(0, 5) data = data.slice(0, 5)
offenderIpOption.series[0].data = data.map(d => { offenderIpOption.series[0].data = data.map(d => {
return [d.count, d.offenderIp] return [d.count, d.keyFields]
}).reverse() }).reverse()
detectionChart.setOption(offenderIpOption) detectionChart.setOption(offenderIpOption)
@@ -577,104 +487,20 @@ export default {
detectionChart.off('click') detectionChart.off('click')
detectionChart.on('click', e => { detectionChart.on('click', e => {
if (e.data) { if (e.data) {
vm.getFilter(e.data[1], vm.filterData.securityEvent[4].column) vm.getFilter(e.data[1], vm.filterData[2].column)
vm.filterData.securityEvent[4].value = vm.triggerFilterDataValue(vm.filterData.securityEvent[4].value, e.data[1]) vm.filterData[2].value = vm.triggerFilterDataValue(vm.filterData[2].value, e.data[1])
} }
}) })
} }
}).catch(e => { }).catch(e => {
console.error(e) console.error(e)
this.filterData[this.pageType][4].data = [] this.filterData[2].data = []
this.filterData[this.pageType][4].showMore = false this.filterData[2].showMore = false
this.filterData[this.pageType][4].showIndex = 5 this.filterData[2].showIndex = 5
this.filterData[this.pageType][4].showDisabled = true this.filterData[2].showDisabled = true
this.$message.error(this.errorMsgHandler(e)) this.$message.error(this.errorMsgHandler(e))
}) })
}, },
initVictimIpData (params) {
axios.get(api.detection[this.pageType].victimIpStatistics, { params }).then(res => {
const data = res.data.data.result
this.filterData[this.pageType][3].data = data.map(r => ({ label: r.victimIp, value: r.victimIp, count: r.count }))
this.isCheckFilterByQ(params, 3)
const { showMore, showIndex, showDisabled } = this.computeFilterPage(this.filterData[this.pageType][3].data)
this.filterData[this.pageType][3].showMore = showMore
this.filterData[this.pageType][3].showIndex = showIndex
this.filterData[this.pageType][3].showDisabled = showDisabled
}).catch(e => {
console.error(e)
this.filterData[this.pageType][3].data = []
this.filterData[this.pageType][3].showMore = false
this.filterData[this.pageType][3].showIndex = 5
this.filterData[this.pageType][3].showDisabled = true
this.$message.error(this.errorMsgHandler(e))
})
},
initActiveEntity (params) {
axios.get(api.detection[this.pageType].activeEntity, { params }).then(res => {
let data = res.data.data.result
this.statisticsActiveAttackData = data
if (!this.$_.isEmpty(data)) {
const chartDom = document.getElementById(`detectionActiveAttacker${this.pageType}`)
let detectionChart = echarts.getInstanceByDom(chartDom)
if (detectionChart) {
echarts.dispose(detectionChart)
}
detectionChart = echarts.init(chartDom)
this.chartInit.push(shallowRef(detectionChart))
const option = this.$_.cloneDeep(activeAttackBar)
data.sort(reverseSortBy('count'))
data = data.slice(0, 5)
option.series[0].data = data.map(d => {
return [d.count, d.name, d.entityType]
}).reverse()
detectionChart.setOption(option)
extensionEchartY(detectionChart)// y轴标签过长时鼠标悬浮显示所有内容
const vm = this
detectionChart.off('click')
detectionChart.on('click', e => {
const entityType = e.data[2]
let column = ''
if (entityType) {
switch (entityType) {
case 'app': {
column = 'app_name'
break
}
case 'domain': {
column = 'domain'
break
}
case 'ip': {
column = 'server_ip'
break
}
default: {
break
}
}
if (column) {
// 点击的name和上次的name一致则清空该项条件
if (vm.oldActiveEntitySearchValue === e.data[1]) {
vm.$refs.search.changeParams({ column: column, oldValue: [vm.oldActiveEntitySearchValue], newValue: [] })
vm.$nextTick(() => {
vm.oldActiveEntitySearchValue = ''
})
} else {
vm.$refs.search.changeParams({ column: column, oldValue: vm.oldActiveEntitySearchValue ? [vm.oldActiveEntitySearchValue] : [], newValue: [e.data[1]] })
vm.$nextTick(() => {
vm.oldActiveEntitySearchValue = e.data[1]
})
}
}
}
})
}
}).catch(error => {
console.error(error)
})
},
triggerFilterDataValue (array, value) { triggerFilterDataValue (array, value) {
const r = [...array] const r = [...array]
const index = array.indexOf(value) const index = array.indexOf(value)
@@ -698,16 +524,17 @@ export default {
endTime: getSecond(this.timeFilter.endTime), endTime: getSecond(this.timeFilter.endTime),
resource: q, resource: q,
pageSize: this.pageObj.pageSize, pageSize: this.pageObj.pageSize,
pageNo: this.pageObj.pageNo pageNo: this.pageObj.pageNo,
isGroup: this.eventFlag === detectionEventType.single ? 0 : 1
} }
axios.get(api.detection[this.pageType].securityList, { params }).then(response => { axios.get(api.detection.event.list, { params }).then(response => {
if (response.status === 200) { if (response.status === 200) {
const data = response.data.data.result const data = response.data.data.result
if (data.length > 0) { if (data.length > 0) {
data.forEach(item => { // data.forEach(item => {
item.eventInfoObj = JSON.parse(item.eventInfo) // item.eventInfoObj = JSON.parse(item.eventInfo)
item.startTime = parseFloat(item.startTime) // item.startTime = parseFloat(item.startTime)
}) // })
this.listData = data this.listData = data
} else { } else {
this.listData = [] this.listData = []
@@ -718,7 +545,9 @@ export default {
this.$message.error(response.data.message) this.$message.error(response.data.message)
} }
}) })
axios.get(api.detection[this.pageType].securityCount, { params }).then(res => { delete params.pageSize
delete params.pageNo
axios.get(api.detection.event.count, { params }).then(res => {
this.pageObj.total = parseInt(this.$_.get(res, 'data.data.result', 0)) this.pageObj.total = parseInt(this.$_.get(res, 'data.data.result', 0))
}).catch(error => { }).catch(error => {
console.error(error) console.error(error)
@@ -800,10 +629,7 @@ export default {
this.queryList(this.q) this.queryList(this.q)
}, },
resetFilterData () { resetFilterData () {
this.filterData.securityEvent.forEach(d => { this.filterData.forEach(d => {
d.data = []
})
this.filterData.performanceEvent.forEach(d => {
d.data = [] d.data = []
}) })
}, },
@@ -814,16 +640,12 @@ export default {
endTime: getSecond(this.timeFilter.endTime), endTime: getSecond(this.timeFilter.endTime),
resource: q resource: q
} }
this.initStatusData(params) this.initTimeDistribution(params) // 顶部柱状图
this.initEventSeverityTrendData(params) this.initEventTypeData(params) // 左侧filter的eventType右侧的第一个饼图
this.initEventSeverityData(params) this.initEventNameData(params) // 左侧filter的eventName右侧的第二个饼图
if (this.pageType === detectionPageType.securityEvent) { this.initKeyData(params) // 左侧filter的key右侧的横向柱状图
this.initOffenderIpData(params) if (this.eventFlag === detectionEventType.single) {
this.initVictimIpData(params) this.initStatusData(params) // 左侧filter的status在聚合事件下不显示
this.initSecurityTypeData(params)
} else if (this.pageType === detectionPageType.performanceEvent) {
this.initActiveEntity(params)
this.initEventTypeData(params)
} }
}, },
pageSize (val) { pageSize (val) {
@@ -891,20 +713,20 @@ export default {
if (params.resource) { if (params.resource) {
let obj let obj
if (index === 0) { if (index === 0) {
obj = this.filterData[this.pageType][index].data.find(d => params.resource.indexOf(d.value) > -1 && params.resource.indexOf('status') > -1) obj = this.filterData[index].data.find(d => params.resource.indexOf(d.value) > -1 && params.resource.indexOf('status') > -1)
} else { } else {
obj = this.filterData[this.pageType][index].data.find(d => params.resource.indexOf(d.value) > -1) obj = this.filterData[index].data.find(d => params.resource.indexOf(d.value) > -1)
} }
if (obj) { if (obj) {
this.filterData[this.pageType][index].value = [obj.value] this.filterData[index].value = [obj.value]
this.filterData[this.pageType][index].flag = true this.filterData[index].flag = true
} else { } else {
this.filterData[this.pageType][index].value = [] this.filterData[index].value = []
this.filterData[this.pageType][index].flag = true this.filterData[index].flag = true
} }
} else { } else {
this.filterData[this.pageType][index].value = [] this.filterData[index].value = []
this.filterData[this.pageType][index].flag = true this.filterData[index].flag = true
} }
}, },
getFilter (name, topColumn) { getFilter (name, topColumn) {
@@ -915,6 +737,17 @@ export default {
value: name value: name
} }
this.$refs.search.changeParams([params]) this.$refs.search.changeParams([params])
} else if (topColumn === 'keyFields') {
const nameList = name.split(',')
const columnList = this.filterData[2].topColumn.split(',')
nameList.forEach((item, index) => {
const params = {
column: columnList[index],
operator: '=',
value: item
}
this.$refs.search.changeParams([params])
})
} else { } else {
const params = { const params = {
column: topColumn, column: topColumn,
@@ -926,6 +759,17 @@ export default {
this.$nextTick(() => { this.$nextTick(() => {
this.emitter.emit('advanced-search') this.emitter.emit('advanced-search')
}) })
},
clickEventFlag (e) {
this.eventFlag = e
const { query } = this.$route
const newUrl = urlParamsHandler(window.location.href, query, {
eventFlag: e
})
overwriteUrl(newUrl)
this.filterData[3].show = e === detectionEventType.single
this.queryFilter(this.q)
this.queryList(this.q)
} }
}, },
mounted () { mounted () {
@@ -945,6 +789,7 @@ export default {
} }
const parser = new Parser(schemaDetectionSecurity) const parser = new Parser(schemaDetectionSecurity)
q = parser.conversionEnum(q) q = parser.conversionEnum(q)
this.q = q
this.queryFilter(q) this.queryFilter(q)
if (this.initFlag) { if (this.initFlag) {
this.timer = setTimeout(() => { this.timer = setTimeout(() => {
@@ -1039,6 +884,7 @@ export default {
overwriteUrl(newUrl) overwriteUrl(newUrl)
} }
const pageType = path.replace('/detection/', '') const pageType = path.replace('/detection/', '')
const eventFlag = ref(query.eventFlag || detectionEventType.single)
// 获取url携带的range、startTime、endTime // 获取url携带的range、startTime、endTime
const rangeParam = query.range const rangeParam = query.range
const startTimeParam = query.startTime const startTimeParam = query.startTime
@@ -1049,12 +895,15 @@ export default {
if (!startTimeParam || !endTimeParam) { if (!startTimeParam || !endTimeParam) {
const { startTime, endTime } = getNowTime(dateRangeValue) const { startTime, endTime } = getNowTime(dateRangeValue)
timeFilter.value.startTime = getSecond(startTime) timeFilter.value.startTime = getSecond(startTime)
// todo 目前有数据的时间,开发时切换过来
// timeFilter.value.startTime = 1722928331
timeFilter.value.endTime = getSecond(endTime) timeFilter.value.endTime = getSecond(endTime)
// 如果没有时间参数就将参数写入url // 如果没有时间参数就将参数写入url
const newUrl = urlParamsHandler(window.location.href, useRoute().query, { startTime: timeFilter.value.startTime, endTime: timeFilter.value.endTime, range: dateRangeValue }) const newUrl = urlParamsHandler(window.location.href, useRoute().query, { startTime: timeFilter.value.startTime, endTime: timeFilter.value.endTime, range: dateRangeValue })
overwriteUrl(newUrl) overwriteUrl(newUrl)
} else { } else {
timeFilter.value.startTime = parseInt(startTimeParam) // timeFilter.value.startTime = parseInt(startTimeParam)
timeFilter.value.startTime = 1722928331
timeFilter.value.endTime = parseInt(endTimeParam) timeFilter.value.endTime = parseInt(endTimeParam)
} }
const pageObj = ref({ const pageObj = ref({
@@ -1067,7 +916,8 @@ export default {
return { return {
timeFilter, timeFilter,
pageType, pageType,
pageObj pageObj,
eventFlag
} }
} }
} }

View File

@@ -383,3 +383,36 @@ export const metricOption = {
} }
] ]
} }
export const lineOption = {
xAxis: {
type: 'time',
data: [],
axisTick: {
show: false
},
axisLine: {
show: false
},
axisLabel: {
formatter: xAxisTimeFormatter,
rich: xAxisTimeRich
}
},
yAxis: {
max: 100
},
grid: {
top: '12px',
left: '30px',
bottom: '30px',
right: '20px'
},
series: [
{
data: [],
type: 'line',
color: '#ff9a79',
symbol: 'none'
}
]
}

View File

@@ -0,0 +1,304 @@
<template>
<div class="events-timeline">
<div class="timeline__circle">
<div v-for="(item, index) in myTimeData" :key="index">
<div
:class="index===activeCircle ? 'circle__item-active' : 'circle__item'"
:style="`margin-left: ${item.marginLeft};`"
@click="onChange(item, index)"></div>
</div>
</div>
<div class="timeline__line"></div>
<div class="timeline-container">
<div v-for="(item, index) in timeLine" :key="index">
<div v-if="item.showFlag" style="color: #666;font-size: 12px;">{{ item.time }}</div>
</div>
</div>
</div>
</template>
<!--------------调用-------start-------------->
<!--<events-timeline :timeFilter="timeFilter" @change="onChange"></events-timeline>-->
<!--回调数据{ startTime: 1709192498339, endTime: 1709192498339 }-->
<!--------------调用-------end-------------->
<script>
import { getMillisecond } from '@/utils/date-util'
import { changeTimestampToTime } from '@/utils/tools'
import { throttle } from 'lodash'
export default {
name: 'EventsTimeline',
props: {
timeFilter: {
type: Object
},
timeData: Array
},
data () {
return {
timeLine: [],
myTimeData: [
{ eventId: '1111111', lastTime: 1722391500, startTime: 1722391500 },
{ eventId: '2222222', lastTime: 1722391920, startTime: 1722391920 },
{ eventId: '3333333', lastTime: 1722391920, startTime: 1722391920 },
{ eventId: '444', lastTime: 1722392340, startTime: 1722392340 },
{ eventId: '555', lastTime: 1722392580, startTime: 1722392580 },
{ eventId: '666', lastTime: 1722392880, startTime: 1722392880 }
],
activeCircle: 0
}
},
watch: {
timeFilter (n) {
if (n) {
this.currentTime = 99
this.getDate()
this.onChange('change')
}
},
timeData (n) {
if (n) {
const timeFilter = {
startTime: getMillisecond(this.timeFilter.startTime),
endTime: getMillisecond(this.timeFilter.endTime)
}
this.handleTimelineCircle(timeFilter)
}
}
},
mounted () {
this.initDate('init')
},
methods: {
initDate (e) {
// 切换页面进来时timeFilter时间戳为秒而非毫秒
const timeFilter = {
startTime: getMillisecond(this.timeFilter.startTime),
endTime: getMillisecond(this.timeFilter.endTime)
}
const myTimeRange = []
const timeInterval = this.getTimeInterval(timeFilter) // 时间间隔
const showTimeInterval = this.showTimeTimeInterval(timeFilter) // 显示时间的时间间隔
let startTime = timeFilter.startTime // 开始时间
const firstTime = new Date(timeFilter.startTime).getMinutes() // 开始时间的分钟数,作为计算基础
// 处理时间轴上红圈事件
this.$nextTick(() => {
this.handleTimelineCircle(timeFilter)
})
// 根据显示时间间隔计算所需开始时间如获取3小时的时间开始时间为第31分钟则按30分钟开始计算
if (showTimeInterval % 5 === 0) {
if (firstTime % 5 !== 0) {
startTime = startTime - (firstTime % showTimeInterval) * 60 * 1000
}
} else if (firstTime % 2 !== 0) {
startTime = startTime - 60 * 1000
}
for (let i = startTime; i <= timeFilter.endTime; i += showTimeInterval * 60 * 1000) {
const obj = this.formatTime(i, showTimeInterval, timeInterval)
if (obj) {
myTimeRange.push({ time: obj.time, time1: obj.time1, stamp: new Date(obj.time1).getTime(), lastStamp: new Date(obj.time1).getTime() - showTimeInterval * 60 * 1000, showFlag: obj.showFlag })
}
}
this.timeLine = myTimeRange
// if (e === 'init') {
// this.$emit('change', timeObj)
// }
},
// 按时间间隔格式化时间
formatTime (timestamp, showTimeInterval, timeInterval) {
const date = new Date(timestamp)
const minute = date.getMinutes()
// const remainder = minute % showTimeInterval
const remainder1 = minute % timeInterval
// if (remainder === 0) {
// 零点,显示年月日
if (date.getHours() === 0 && minute === 0) {
const month = date.getMonth() + 1
const day = date.getDate()
const obj = {
time: `${date.getFullYear()}-${month < 10 ? ('0' + month) : month}-${day < 10 ? ('0' + day) : day}`,
time1: changeTimestampToTime(timestamp).substring(0, changeTimestampToTime(timestamp).length - 3),
showFlag: false
}
if (remainder1 === 0) {
obj.showFlag = true
}
return obj
} else {
// 非零点显示时分
const obj = {
time: `${date.getHours() < 10 ? ('0' + date.getHours()) : date.getHours()}:${minute < 10 ? ('0' + minute) : minute}`,
time1: changeTimestampToTime(timestamp).substring(0, changeTimestampToTime(timestamp).length - 3),
showFlag: false
}
if (remainder1 === 0) {
obj.showFlag = true
}
return obj
}
// }
},
// 获取间隔时间如一小时最小间隔时间1分钟3小时间隔2分钟
getTimeInterval (timeFilter) {
const step = (timeFilter.endTime - timeFilter.startTime) / (1000 * 60)
switch (true) {
case step < 30: {
return 1
}
case step >= 30 && step < 60: {
return 2
}
case step === 60: {
return 5
}
case step <= 3 * 60: {
return 20
}
case step <= 6 * 60: {
return 30
}
case step <= 12 * 60: {
return 60
}
case step <= 24 * 60: {
return 2 * 60
}
case step <= 2 * 24 * 60: {
return 4 * 60
}
case step <= 7 * 24 * 60: {
return 12 * 60
}
case step <= 30 * 24 * 60: {
return 48 * 60
}
case step <= 90 * 24 * 60: {
return 6 * 24 * 60
}
case step <= 24 * 30 * 24 * 60: {
return 30 * 24 * 60
}
}
},
// 时间轴显示时间的间隔时间,
showTimeTimeInterval (timeFilter) {
const step = (timeFilter.endTime - timeFilter.startTime) / (1000 * 60)
switch (true) {
case step <= 60: {
return 1
}
case step <= 3 * 60: {
return 2
}
case step <= 6 * 60: {
return 5
}
case step <= 12 * 60: {
return 10
}
case step <= 24 * 60: {
return 20
}
case step <= 2 * 24 * 60: {
return 40
}
case step <= 7 * 24 * 60: {
return 360
}
case step <= 30 * 24 * 60: {
return 24 * 60
}
case step <= 90 * 24 * 60: {
return 6 * 24 * 60
}
case step <= 24 * 30 * 24 * 60: {
return 30 * 24 * 60
}
}
},
// 时间轴红点的点击事件,添加防抖处理
onChange: throttle(function (e, index) {
this.activeCircle = index
this.$emit('change', e.eventId)
}, 500),
// onChange (e, index) {
// this.activeCircle = index
// this.throttleFunc(e)
// },
throttleFunc: throttle(function (e) {
this.$emit('change', e.eventId)
}, 1000),
formatTooltip (value) {
if (this.timeLine.length > 0 && this.timeLine[value]) {
return this.timeLine[value].time1
}
},
handleTimelineCircle (timeFilter) {
this.myTimeData = this.$_.cloneDeep(this.timeData)
this.activeCircle = this.myTimeData.length - 1
this.myTimeData.forEach((item, index) => {
item.statTime = getMillisecond(item.statTime)
item.lastTime = this.$_.cloneDeep(item.statTime)
let minutes = new Date(item.lastTime).getMinutes()
let hours = new Date(item.lastTime).getHours()
minutes = minutes < 10 ? '0' + minutes : minutes
hours = hours < 10 ? '0' + hours : hours
item.time = hours + ':' + minutes
item.diffTime = (timeFilter.endTime - timeFilter.startTime) / 1000
item.itemDiffTime = (timeFilter.endTime - item.lastTime) / 1000
if (index === 0) {
let marginLeft = 500 - ((item.itemDiffTime / item.diffTime) * 500)
marginLeft = Math.round(marginLeft) + 8 + 'px'
item.marginLeft = marginLeft
} else {
let marginLeft = ((this.myTimeData[index - 1].itemDiffTime - item.itemDiffTime) / item.diffTime) * 500
marginLeft = (Math.round(marginLeft) - 13) + 'px'
item.marginLeft = marginLeft
}
})
}
}
}
</script>
<style lang="scss">
.events-timeline {
width: 500px;
.timeline__circle {
display: flex;
.circle__item, .circle__item-active {
width: 14px;
height: 14px;
border-radius: 50%;
background: var(--el-color-danger-light-3);
cursor: pointer;
}
.circle__item-active {
background: var(--el-color-danger);
}
}
.timeline__line {
width: 100%;
border-bottom: 1px var(--el-border-color-light) solid;
margin-bottom: 10px;
}
.timeline-container {
width: 100%;
background-color: rgba(255, 255, 255, 0.50);
display: flex;
justify-content: space-between;
}
}
</style>

View File

@@ -0,0 +1,97 @@
<template>
<div class="detection-detail-overview">
<div class="overview__left">
<div class="overview__title">{{ $t('overall.remark') }}</div>
<div class="overview__row">
<div class="row__content1">
${key} experienced exceptions exceeding the threshold number of times.
</div>
</div>
<!-- <div class="overview__title">{{ $t('overall.summary') }}</div>-->
<div class="overview__title">{{ $t('overall.fields') }}</div>
<div class="overview__row">
<div class="row__label">{{ $t('detection.list.startTime') }}</div>
<div class="row__content">
<i class="cn-icon cn-icon-time2 row__content__icon"></i>
{{ detection.startTime ? dateFormatByAppearance(detection.startTime) : '-' }}
</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detection.detail.matchTime') }}</div>
<div class="row__content">
<i class="cn-icon cn-icon-time2 row__content__icon"></i>
{{ detection.startTime ? dateFormatByAppearance(detection.startTime) : '-' }}
</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('overall.clientIp') }}</div>
<!-- <div class="row__content">{{ detection.victimIp || '-' }}</div>-->
<div class="row__content">192.168.12.34</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('npm.clientLocation') }}</div>
<div class="row__content">
<div>
<!-- <div v-if="$_.get(basicInfo, 'victimInfo.location.country')">-->
<!-- <img v-if="basicInfo.victimInfo.location.country===countryNameIdMapping.Unknown || !countryNameIdMapping[basicInfo.victimInfo.location.country]" src="../../../../public/images/flag/Unknown.svg" class="filter-country-flag">-->
<!-- <img v-else :src="require(`../../../../public/images/flag/${countryNameIdMapping[basicInfo.victimInfo.location.country]}.png`)" class="filter-country-flag" >-->
<img :src="require(`../../../../public/images/flag/CN.png`)" class="filter-country-flag" >
</div>
China, beijing
</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('overall.serverIp') }}</div>
<!--<div class="row__content">{{ detection.victimIp || '-' }}</div>-->
<div class="row__content">192.168.12.34</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detection.detail.serverLocation') }}</div>
<div class="row__content">
<div>
<!-- <div v-if="$_.get(basicInfo, 'victimInfo.location.country')">-->
<!-- <img v-if="basicInfo.victimInfo.location.country===countryNameIdMapping.Unknown || !countryNameIdMapping[basicInfo.victimInfo.location.country]" src="../../../../public/images/flag/Unknown.svg" class="filter-country-flag">-->
<!-- <img v-else :src="require(`../../../../public/images/flag/${countryNameIdMapping[basicInfo.victimInfo.location.country]}.png`)" class="filter-country-flag" >-->
<img :src="require(`../../../../public/images/flag/CN.png`)" class="filter-country-flag" >
</div>
China, beijing
</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detection.detail.indicatorValues') }}</div>
<div class="row__content">Tor</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('overall.domain') }}</div>
<div class="row__content">baidu.com</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('overall.app') }}</div>
<div class="row__content">Wechat</div>
</div>
</div>
</div>
</template>
<script>
import { dateFormatByAppearance } from '@/utils/date-util'
import detectionDetailMixin from '@/views/detections/overview/detectionDetailMixin'
export default {
name: 'IndicatorMatchOverview',
mixins: [detectionDetailMixin],
props: {
detection: Object,
timeFilter: Object,
q: String
},
data () {
return {
basicInfo: {}
}
},
methods: {
dateFormatByAppearance
}
}
</script>

View File

@@ -0,0 +1,79 @@
<template>
<div class="detection-detail-overview">
<div class="overview__left">
<div class="overview__title">{{ $t('overall.remark') }}</div>
<div class="overview__row">
<div class="row__content1">
${key} experienced exceptions exceeding the threshold number of times.
</div>
</div>
<div class="overview__title">{{ $t('detection.detail.stage') }}1</div>
<div class="overview__row">
<div class="row__label">Dns_query</div>
<div class="row__content">{{ $_.get(myDetection, 'eventInfoList[0].domain', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">Time</div>
<div class="row__content">
<i class="cn-icon cn-icon-time2 row__content__icon"></i>
{{ myDetection.startTime ? dateFormatByAppearance(myDetection.startTime) : '-' }}
</div>
</div>
<div class="overview__title">{{ $t('detection.detail.stage') }}2</div>
<div class="overview__row">
<div class="row__label">Decoded_as</div>
<div class="row__content">{{ $_.get(myDetection, 'eventInfoList[1].client_ip', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">APP</div>
<div class="row__content">{{ $_.get(myDetection, 'eventInfoList[1].app_transition', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">Time</div>
<div class="row__content">
<i class="cn-icon cn-icon-time2 row__content__icon"></i>
{{ myDetection.startTime ? dateFormatByAppearance(myDetection.startTime) : '-' }}
</div>
</div>
<div v-if="eventFlag===detectionEventType.aggregation">
<div class="overview__title" style="margin: 10px 0;">{{ $t('detection.timeOfOccurrences') }}</div>
<div class="overview__row">
<div class="row__content1">
<events-timeline :timeFilter="timeFilter" :timeData="timeData" @change="changeTimeline"></events-timeline>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { dateFormatByAppearance } from '@/utils/date-util'
import EventsTimeline from './EventsTimeline'
import detectionDetailMixin from '@/views/detections/overview/detectionDetailMixin'
export default {
name: 'SequenceOverview',
mixins: [detectionDetailMixin],
props: {
detection: Object,
timeFilter: Object,
pageObj: Object,
eventFlag: String,
q: String
},
components: {
EventsTimeline
},
data () {
return {}
},
methods: {
dateFormatByAppearance
}
}
</script>

View File

@@ -0,0 +1,73 @@
<template>
<div class="detection-detail-overview">
<div class="overview__left">
<div class="overview__title">{{ $t('overall.remark') }}</div>
<div class="overview__row">
<div class="row__content1">
${key} experienced exceptions exceeding the threshold number of times.
</div>
</div>
<div class="overview__title">{{ $t('overall.summary') }}</div>
<div class="overview__row overview__row__display">
<div class="row__content1">
<div class="row__content__charts" :id="`myChart${detection.eventId}`"></div>
</div>
<div class="row__content1">
<div class="charts__visual__map"></div>
</div>
</div>
<div v-if="eventFlag===detectionEventType.aggregation">
<div class="overview__title" style="margin: 10px 0;">{{ $t('detection.timeOfOccurrences') }}</div>
<div class="overview__row">
<div class="row__content1">
<events-timeline :timeFilter="timeFilter" :timeData="timeData" @change="changeTimeline"></events-timeline>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { lineOption } from '@/views/detections/options/detectionOptions'
import { detectionEventType } from '@/utils/constants'
import EventsTimeline from '@/views/detections/overview/EventsTimeline'
import detectionDetailMixin from '@/views/detections/overview/detectionDetailMixin'
export default {
name: 'ThresholdOverview',
props: {
detection: Object,
timeFilter: Object,
eventFlag: String,
q: String
},
mixins: [detectionDetailMixin],
components: {
EventsTimeline
},
data () {
return {
myChart: null,
lineOption: lineOption,
myDetection: {},
detectionEventType
}
},
mounted () {
window.addEventListener('resize', this.resize)
this.myDetection = this.detection
},
methods: {
resize () {
if (this.myChart) {
this.myChart.resize()
}
}
},
beforeUnmount () {
window.removeEventListener('resize', this.resize)
}
}
</script>

View File

@@ -0,0 +1,107 @@
import { detectionEventType, detectionRuleType } from '@/utils/constants'
import { getMillisecond, getSecond } from '@/utils/date-util'
import axios from 'axios'
import { api } from '@/utils/api'
import { lineOption } from '@/views/detections/options/detectionOptions'
import { markRaw } from 'vue'
import * as echarts from 'echarts'
export default {
props: {
detection: Object,
timeFilter: Object,
pageObj: Object,
eventFlag: String,
q: String
},
data () {
return {
detectionEventType,
myDetection: {},
timeData: [],
isGroup: 0,
myChart: null,
lineOption: lineOption
}
},
mounted () {
this.initData()
},
methods: {
initData () {
this.myDetection = this.detection
if (this.eventFlag === detectionEventType.aggregation) {
this.isGroup = 1
const timeParams = {
startTime: getSecond(this.timeFilter.startTime),
endTime: getSecond(this.timeFilter.endTime),
resource: this.q,
keyFields: `'${this.detection.keyFields}'`,
keyValues: `'${this.detection.keyValues}'`,
ruleId: this.detection.ruleId,
ruleVersion: `'${this.detection.ruleVersion}'`
}
axios.get(api.detection.event.detailTimeDistribution, { params: timeParams }).then(res => {
if (res.status === 200) {
this.timeData = res.data.data.result
}
}).catch(e => {
//
})
} else {
this.isGroup = 0
}
if (this.myChart) {
this.myChart.dispose()
}
const params = {
startTime: this.timeFilter.startTime,
endTime: this.timeFilter.endTime,
eventId: this.detection.eventId,
ruleType: this.detection.ruleType,
resource: this.q,
isGroup: this.isGroup
}
axios.get(api.detection.event.detail, { params }).then(res => {
if (res.status === 200) {
// 类型为Threshold时处理折线图
if (this.detection.ruleType === detectionRuleType.threshold.key) {
const seriesData = []
res.data.data.result.forEach(item => {
seriesData.push([getMillisecond(JSON.parse(item.statTime)), item.recordsNums])
})
this.lineOption.series[0].data = seriesData
this.myChart = markRaw(echarts.init(document.getElementById('myChart' + this.detection.eventId)))
this.myChart.setOption(this.lineOption)
} else {
const detailData = res.data.data.result.pop()
if (detailData.eventInfo) {
detailData.eventInfoList = JSON.parse(detailData.eventInfo)
}
this.myDetection = detailData
}
}
})
},
changeTimeline (e) {
const params = {
startTime: this.timeFilter.startTime,
endTime: this.timeFilter.endTime,
eventId: e,
ruleType: this.detection.ruleType,
resource: this.q,
isGroup: this.isGroup
}
axios.get(api.detection.event.detail, { params }).then(res => {
if (res.status === 200) {
const detailData = res.data.data.result[0]
if (detailData.eventInfo) {
detailData.eventInfoList = JSON.parse(detailData.eventInfo)
}
this.myDetection = detailData
}
})
}
}
}