CN-1676 fix: Detection 新内容实现
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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.
@@ -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,
|
||||||
|
|||||||
@@ -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
@@ -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">
|
||||||
|
|||||||
@@ -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 () {
|
||||||
|
|||||||
@@ -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')}} : </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')}} : </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')}} : </span>
|
<span>{{$t('detections.eventType')}} : </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')}} : </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')}} : </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')}} : </span>
|
<span>{{$t('detection.list.startTime')}} : </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')}} : </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')}} : </span>
|
<span>{{$t('overall.duration')}} : </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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
231
src/views/detections/DetectionRowEvents.vue
Normal file
231
src/views/detections/DetectionRowEvents.vue
Normal 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')}} : </span>
|
||||||
|
<span>{{detection.eventName || '-'}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="basic-info__item">
|
||||||
|
<i class="cn-icon cn-icon-event-type"></i>
|
||||||
|
<span>{{$t('detections.eventType')}} : </span>
|
||||||
|
<span>{{detection.eventType || '-'}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="basic-info__item">
|
||||||
|
<i class="cn-icon cn-icon-time2"></i>
|
||||||
|
<span>{{$t('detection.lastTime')}} : </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>
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|||||||
304
src/views/detections/overview/EventsTimeline.vue
Normal file
304
src/views/detections/overview/EventsTimeline.vue
Normal 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>
|
||||||
97
src/views/detections/overview/IndicatorMatchOverview.vue
Normal file
97
src/views/detections/overview/IndicatorMatchOverview.vue
Normal 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>
|
||||||
79
src/views/detections/overview/SequenceOverview.vue
Normal file
79
src/views/detections/overview/SequenceOverview.vue
Normal 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>
|
||||||
73
src/views/detections/overview/ThresholdOverview.vue
Normal file
73
src/views/detections/overview/ThresholdOverview.vue
Normal 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>
|
||||||
107
src/views/detections/overview/detectionDetailMixin.js
Normal file
107
src/views/detections/overview/detectionDetailMixin.js
Normal 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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user