CN-1391 fix: Detection列表按新版设计更新接口和样式

This commit is contained in:
刘洪洪
2023-10-20 15:45:11 +08:00
parent 48b3e2aebd
commit 0f52bd5362
11 changed files with 814 additions and 364 deletions

View File

@@ -60,12 +60,13 @@
font-weight: 400; font-weight: 400;
} }
.row__content { .row__content, .row__content1 {
display: flex; display: flex;
//color: #3976CB; //color: #3976CB;
color: #046ECA; color: #046ECA;
font-weight: 500; font-weight: 500;
font-size: 14px; font-size: 14px;
align-items: center;
&.row__content--link { &.row__content--link {
font-style: italic; font-style: italic;
@@ -87,6 +88,17 @@
font-style: italic; font-style: italic;
color: #1890FF; color: #1890FF;
} }
.row__content__icon {
font-size: 14px;
margin-right: 5px;
color: #1890FF;
}
.row__content__svg {
font-size: 14px;
margin-right: 7px;
}
} }
} }
} }
@@ -215,13 +227,20 @@
} }
} }
} }
.row__tag { .row__tag, .row__tag__level {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
padding: 0 4px; padding: 0 4px;
//color: white; //color: white;
} }
.row__tag__level {
height: 16px;
font-size: 12px;
font-weight: 400;
color: #fff;
border-radius: 2px;
}
.performance-event-remark { .performance-event-remark {
font-family: NotoSansSChineseRegular; font-family: NotoSansSChineseRegular;

View File

@@ -1234,7 +1234,8 @@ export default class Parser {
// 不区分大小写用this.columnList里的label // 不区分大小写用this.columnList里的label
arr.forEach(item => { arr.forEach(item => {
if (str.toLowerCase().indexOf(item.toLowerCase()) > -1) { if (str.toLowerCase().indexOf(item.toLowerCase()) > -1) {
str = str.replace(new RegExp(item, 'gi'), item) // todo 记录一下此处取消了转小写转换,后续搜索验证
// str = str.replace(new RegExp(item, 'gi'), item)
if (!operatorList.some(ite => str.includes(ite)) && str.toLowerCase() !== item.toLowerCase()) { if (!operatorList.some(ite => str.includes(ite)) && str.toLowerCase() !== item.toLowerCase()) {
str = this.checkFormatByStr(str) str = this.checkFormatByStr(str)
} }

353
src/mock/detectionList.js Normal file
View File

@@ -0,0 +1,353 @@
import Mock from 'mockjs'
const urlAndVersion = BASE_CONFIG.baseUrl + BASE_CONFIG.apiVersion
const openMock = true
if (openMock) {
Mock.mock(new RegExp(urlAndVersion + '/detection/security/list.*'), 'get', function (requestObj) {
const result = []
for (let i = 0; i < 20; i++) {
const obj = {
eventId: 1212,
eventType: 'Anonymity',
eventName: 'Tor',
eventKey: '2,1.1.1,12.2.2.2',
ruleId: 2,
ruleType: 'indicator_match',
isBuiltin: 1,
severity: 'critical',
offenderIp: '1.1.1.1',
victimIp: '2.2.2.2',
domain: '*.vioee.com',
app: 'vio',
startTime: 1697092617,
endTime: 1697092777,
durationS: 30000,
matchTimes: 1,
status: 1,
eventInfo: "{\'knowledge_id\': 123, \'name\': \'example_ioc_malware\', \'ioc_type\': \'domain\', \'ioc_value\': \'iocentity.com\'}",
eventInfoObj: {
knowledge_id: 123,
name: 'example_ioc_malware',
ioc_type: 'domain',
ioc_value: 'iocentity.com'
}
}
if (i % 2 === 0) {
obj.status = 0
}
result.push(obj)
}
const data = {
resultType: 'table',
result: result
}
return {
msg: 'OK',
code: 200,
data: data
}
})
Mock.mock(new RegExp(urlAndVersion + '/detection/security/severity/timedistribution.*'), 'get', function (requestObj) {
const result = [
{
statTime: 1697523900,
severity: 'critical',
count: 25
},
{
statTime: 1697524200,
severity: 'high',
count: 31
},
{
statTime: 1697524500,
severity: 'info',
count: 21
},
{
statTime: 1697524800,
severity: 'low',
count: 25
},
{
statTime: 1697525100,
severity: 'medium',
count: 32
},
{
statTime: 1697525400,
severity: 'critical',
count: 22
},
{
statTime: 1697525700,
severity: 'critical',
count: 23
},
{
statTime: 1697526000,
severity: 'critical',
count: 25
},
{
statTime: 1697526300,
severity: 'critical',
count: 21
}
]
const data = {
resultType: 'table',
result: result
}
return {
msg: 'OK',
code: 200,
data: data
}
})
Mock.mock(new RegExp(urlAndVersion + '/detection/security/severity/statistics.*'), 'get', function (requestObj) {
const result = [
{
severity: 'critical',
count: 25
},
{
severity: 'high',
count: 31
},
{
severity: 'info',
count: 21
},
{
severity: 'low',
count: 25
},
{
severity: 'medium',
count: 32
}
]
const data = {
resultType: 'table',
result: result
}
return {
msg: 'OK',
code: 200,
data: data
}
})
Mock.mock(new RegExp(urlAndVersion + '/detection/security/event-type/statistics.*'), 'get', function (requestObj) {
const result = [
{
eventType: 'Anonymity',
count: 25
},
{
eventType: 'Command and Control',
count: 31
}
]
const data = {
resultType: 'table',
result: result
}
return {
msg: 'OK',
code: 200,
data: data
}
})
Mock.mock(new RegExp(urlAndVersion + '/detection/security/offender-ip/statistics.*'), 'get', function (requestObj) {
const result = [
{
offenderIp: '221.7.1.20',
count: 25
},
{
offenderIp: '58.247.118.253',
count: 31
}
]
const data = {
resultType: 'table',
result: result
}
return {
msg: 'OK',
code: 200,
data: data
}
})
Mock.mock(new RegExp(urlAndVersion + '/detection/security/victim-ip/statistics.*'), 'get', function (requestObj) {
const result = [
{
victimIp: '21.77.1.201',
count: 25
},
{
victimIp: '58.47.118.153',
count: 31
},
{
victimIp: '22.47.223.57',
count: 43
}
]
const data = {
resultType: 'table',
result: result
}
return {
msg: 'OK',
code: 200,
data: data
}
})
Mock.mock(new RegExp(urlAndVersion + '/detection/security/status/statistics.*'), 'get', function (requestObj) {
const result = [
{
status: 1,
count: 25
},
{
status: 0,
count: 31
}
]
const data = {
resultType: 'table',
result: result
}
return {
msg: 'OK',
code: 200,
data: data
}
})
Mock.mock(new RegExp(urlAndVersion + '/detection/security/ip/relation/event.*'), 'get', function (requestObj) {
const result = [
{
eventId: 10010,
severity: 'high',
eventType: 'Command and Control',
offenderIp: '1.1.1.1',
victimIp: '2.2.2.2',
startTime: 1697092617
}
]
const data = {
resultType: 'table',
result: result
}
return {
msg: 'OK',
code: 200,
data: data
}
})
Mock.mock(new RegExp(urlAndVersion + '/detection/security/count.*'), 'get', function (requestObj) {
return {
msg: 'OK',
code: 200,
data: {
resultType: 'single',
result: 123
}
}
})
Mock.mock(new RegExp(urlAndVersion + '/detection/security/entity/detail/ip.*'), 'get', function (requestObj) {
return {
msg: 'OK',
code: 200,
data: {
asn: {
id: 2,
asn: '14061',
organization: 'DIGITALOCEAN-ASN - DigitalOcean, LLC, US'
},
malware: {
threatType: 'command and control',
malwareName: 'IcedID',
malwareAlias: 'BokBot,IceID',
mitreAttackDescription: '[IcedID](https://attack.mitre.org/software/S0483) is a modular banking malware designed to steal financial information that has been observed in the wild since at least 2017. [IcedID](https://attack.mitre.org/software/S0483) has been downloaded by [Emotet](https://attack.mitre.org/software/S0367) in multiple campaigns.(Citation: IBM IcedID November 2017)(Citation: Juniper IcedID June 2020)',
mitreAttackPlatforms: 'Windows',
mitreAttackTechniques: '[""Asymmetric Cryptography(T1573.002)"",""Asynchronous Procedure Call(T1055.004)"",""Browser Session Hijacking(T1185)"",""Domain Account(T1087.002)"",""Ingress Tool Transfer(T1105)"",""Malicious File(T1204.002)"",""Msiexec(T1218.007)"",""Native API(T1106)"",""Obfuscated Files or Information(T1027)"",""Permission Groups Discovery(T1069)"",""Registry Run Keys / Startup Folder(T1547.001)"",""Scheduled Task(T1053.005)"",""Software Packing(T1027.002)"",""Spearphishing Attachment(T1566.001)"",""Steganography(T1027.003)"",""System Information Discovery(T1082)"",""Visual Basic(T1059.005)"",""Web Protocols(T1071.001)"",""Windows Management Instrumentation(T1047)""]',
mitreAttackGroups: '[""TA551(G0127)""]',
confidenceLevel: 'high',
reference: ''
},
location: {
continent: 'North America',
country: 'United States',
province: 'New York',
city: '',
lngwgs: '-74.006',
latwgs: '40.713',
isp: 'dba Omsoft',
owner: 'tie net'
}
}
}
})
Mock.mock(new RegExp(urlAndVersion + '/detection/security/entity/detail/domain.*'), 'get', function (requestObj) {
return {
msg: 'OK',
code: 200,
data: {
malware: {
threatType: 'command and control',
malwareName: 'IcedID',
malwareAlias: 'BokBot,IceID',
mitreAttackDescription: '[IcedID](https://attack.mitre.org/software/S0483) is a modular banking malware designed to steal financial information that has been observed in the wild since at least 2017. [IcedID](https://attack.mitre.org/software/S0483) has been downloaded by [Emotet](https://attack.mitre.org/software/S0367) in multiple campaigns.(Citation: IBM IcedID November 2017)(Citation: Juniper IcedID June 2020)',
mitreAttackPlatforms: 'Windows',
mitreAttackTechniques: '[""Asymmetric Cryptography(T1573.002)"",""Asynchronous Procedure Call(T1055.004)"",""Browser Session Hijacking(T1185)"",""Domain Account(T1087.002)"",""Ingress Tool Transfer(T1105)"",""Malicious File(T1204.002)"",""Msiexec(T1218.007)"",""Native API(T1106)"",""Obfuscated Files or Information(T1027)"",""Permission Groups Discovery(T1069)"",""Registry Run Keys / Startup Folder(T1547.001)"",""Scheduled Task(T1053.005)"",""Software Packing(T1027.002)"",""Spearphishing Attachment(T1566.001)"",""Steganography(T1027.003)"",""System Information Discovery(T1082)"",""Visual Basic(T1059.005)"",""Web Protocols(T1071.001)"",""Windows Management Instrumentation(T1047)""]',
mitreAttackGroups: '[""TA551(G0127)""]',
confidenceLevel: 'high',
reference: ''
},
category: {
categoryName: '门户网站',
categoryGroup: '互联网',
reputationLevel: 'high'
}
}
}
})
Mock.mock(new RegExp(urlAndVersion + '/detection/security/entity/detail/app.*'), 'get', function (requestObj) {
return {
msg: 'OK',
code: 200,
data: {
category: {
appName: 'QQ',
appId: 333,
appCategory: '娱乐',
appSubcategory: '聊天',
appRisk: 'low',
appDescription: '聊天社交软件',
appLongname: 'Tencent qq',
appTechnology: 'socket',
appCompany: 'tencent',
appCompanyCategory: '互联网'
}
}
}
})
}

View File

@@ -3,3 +3,4 @@ import './linkMonitor'
import './dns' import './dns'
import './entity' import './entity'
import './detection' import './detection'
import './detectionList'

View File

@@ -123,7 +123,20 @@ export const api = {
listBasic: '/interface/detection/security/list/basic', listBasic: '/interface/detection/security/list/basic',
listCount: '/interface/detection/security/list/count', listCount: '/interface/detection/security/list/count',
overviewBasic: '/interface/detection/security/detail/overview/basic', overviewBasic: '/interface/detection/security/detail/overview/basic',
overviewEvent: '/interface/detection/security/detail/overview/event' overviewEvent: '/interface/detection/security/detail/overview/event',
securityList: apiVersion + '/detection/security/list', // 安全事件列表
timeDistribution: apiVersion + '/detection/security/severity/timedistribution', // 事件严重等级分布(顶部柱状图)
severityStatistics: apiVersion + '/detection/security/severity/statistics', // 事件严重等级统计(左侧filter事件严重等级和饼图)
statusStatistics: apiVersion + '/detection/security/status/statistics', // 事件状态统计
eventTypeStatistics: apiVersion + '/detection/security/event-type/statistics', // 事件类型统计
offenderIpStatistics: apiVersion + '/detection/security/offender-ip/statistics', // 攻击者IP统计
victimIpStatistics: apiVersion + '/detection/security/victim-ip/statistics', // 受害者IP统计
relationEvent: apiVersion + '/detection/security/ip/relation/event', // IP相关近期事件
securityCount: apiVersion + '/detection/security/count', // 安全事件总数
detail: apiVersion + '/detection/security/entity/detail', // 安全事件实体详情,后面得加上实体类型
ipDetail: apiVersion + '/detection/security/entity/detail/ip', // 安全事件实体详情ip响应
domainDetail: apiVersion + '/detection/security/entity/detail/domain', // 安全事件实体详情domain响应
appDetail: apiVersion + '/detection/security/entity/detail/app' // 安全事件实体详情app响应
}, },
performanceEvent: { performanceEvent: {
eventSeverityTrend: '/interface/detection/performance/filter/severityTrend', eventSeverityTrend: '/interface/detection/performance/filter/severityTrend',

View File

@@ -334,11 +334,91 @@ export const columnList1 = [
} }
} }
] ]
const securityEvent = [
{
name: 'event_type',
type: 'string',
label: 'event_type',
doc: {
constraints: {
operator_functions: '=,in,like'
}
}
},
{
name: 'event_name',
type: 'string',
label: 'event_name',
doc: {
constraints: {
operator_functions: '=,in,like'
}
}
},
{
name: 'severity',
type: 'string',
label: 'severity',
doc: {
constraints: {
operator_functions: '=,in,like'
}
}
},
{
name: 'offender_ip',
type: 'string',
label: 'offender Ip',
doc: {
constraints: {
operator_functions: '=,in,like'
}
}
},
{
name: 'victim_ip',
type: 'string',
label: 'victim Ip',
doc: {
constraints: {
operator_functions: '=,in,like'
}
}
},
{
name: 'domain',
type: 'string',
label: 'domain',
doc: {
constraints: {
operator_functions: '=,in,like'
}
}
},
{
name: 'app',
type: 'string',
label: 'app',
doc: {
constraints: {
operator_functions: '=,in,like'
}
}
}
]
let schemaEntityExplore = localStorage.getItem(storageKey.schemaEntityExplore) const schema = localStorage.getItem(storageKey.schemaEntityExplore)
schemaEntityExplore = schemaEntityExplore ? JSON.parse(schemaEntityExplore).entityMetadata.searchColumns : columnList1 const schemaEntityExplore = schema ? JSON.parse(schema).entityMetadata.searchColumns : columnList1
export const columnList = schemaEntityExplore export const columnList = schemaEntityExplore
let securityEventMetadata = securityEvent
if (schema) {
if (JSON.parse(schema).securityEventMetadata) {
securityEventMetadata = JSON.parse(schema).securityEventMetadata.searchColumns
}
}
export const schemaDetectionSecurity = securityEventMetadata
export const operatorList = ['=', '!=', /* '>', '<', '>=', '<=', */'IN', 'NOT IN', 'LIKE', 'NOT LIKE'] export const operatorList = ['=', '!=', /* '>', '<', '>=', '<=', */'IN', 'NOT IN', 'LIKE', 'NOT LIKE']
export const connectionList = [ export const connectionList = [
{ {

View File

@@ -3,12 +3,12 @@
<loading :loading="loading"></loading> <loading :loading="loading"></loading>
<div class="detection-list__content"> <div class="detection-list__content">
<div class="detection-list--list"> <div class="detection-list--list">
<div class="no-data" v-if="noData">{{ $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 <detection-row
style="margin-bottom: 10px" style="margin-bottom: 10px"
class="detection-border" class="detection-border"
v-for="(data, index) in listData" v-for="(data, index) in myListData"
:detection="data" :detection="data"
:page-type="pageType" :page-type="pageType"
:timeFilter="timeFilter" :timeFilter="timeFilter"
@@ -26,6 +26,8 @@
<script> <script>
import DetectionRow from '@/views/detections/DetectionRow' import DetectionRow from '@/views/detections/DetectionRow'
import Loading from '@/components/common/Loading' import Loading from '@/components/common/Loading'
import axios from 'axios'
import { api } from '@/utils/api'
export default { export default {
name: 'DetectionList', name: 'DetectionList',
components: { components: {
@@ -49,7 +51,8 @@ export default {
collapseIndex: 0, collapseIndex: 0,
tableId: 'detectionList', tableId: 'detectionList',
listDataCopy: [], listDataCopy: [],
noData: false noData: true,
myListData: [] // listData的克隆避免因为修改listData里的malWareName而触发watch监听
} }
}, },
mounted () { mounted () {
@@ -60,6 +63,23 @@ export default {
window.removeEventListener('mousewheel', this.handleScroll) window.removeEventListener('mousewheel', this.handleScroll)
}, },
methods: { methods: {
initData () {
this.myListData = []
this.listData.forEach((item, i) => {
this.myListData.push(this.$_.cloneDeep(item))
if (item.eventInfoObj) {
axios.get(`${api.detection.securityEvent.detail}/${item.eventInfoObj.ioc_type}/resource=${item.eventInfoObj.ioc_value}`).then(res => {
if (res.status === 200) {
this.myListData[i].malwareName = res.data.data.malware.malwareName
} else {
this.myListData[i].malwareName = '-'
}
}).catch(e => {
this.myListData[i].malwareName = '-'
})
}
})
},
switchCollapse (isCollapse, index) { switchCollapse (isCollapse, index) {
this.isCollapse = isCollapse this.isCollapse = isCollapse
this.collapseIndex = index this.collapseIndex = index
@@ -84,6 +104,7 @@ export default {
}, },
watch: { watch: {
listData: { listData: {
immediate: true,
deep: true, deep: true,
handler (n) { handler (n) {
if (!n || n.length === 0) { if (!n || n.length === 0) {
@@ -93,6 +114,7 @@ export default {
} else { } else {
clearTimeout(this.timeout) clearTimeout(this.timeout)
this.noData = false this.noData = false
this.initData()
} }
} }
} }

View File

@@ -9,14 +9,15 @@
</div> </div>
</div> </div>
<div class="cn-detection__case"> <div class="cn-detection__case">
<div class="cn-detection__icon" :style="`background-color: ${eventSeverityColor[detection.eventSecurity]}`"></div> <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" v-if="pageType === detectionPageType.securityEvent">
<span <span
class="detection-event-severity-color-block" class="detection-event-severity-color-block"
:style="`background-color: ${eventSeverityColor[detection.eventSeverity]}`"> :style="`background-color: ${eventSeverityColor[detection.eventSeverity]}`">
</span> </span>
<span class="detection-event-severity-block">{{ detection.securityType || '-' }}</span> <!-- <span class="detection-event-severity-block">{{ detection.securityType || '-' }}</span>-->
<span class="detection-event-severity-block">{{ detection.eventType || '-' }}</span>
<i class="cn-icon cn-icon-attacker" ></i>{{detection.offenderIp || '-'}} <i class="cn-icon cn-icon-attacker" ></i>{{detection.offenderIp || '-'}}
<div v-if="detection.domain" class="domain">{{detection.domain}}</div> <div v-if="detection.domain" class="domain">{{detection.domain}}</div>
<span class="line">-------</span> <span class="line">-------</span>
@@ -30,31 +31,31 @@
<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.eventSecurity"> <div class="basic-info__item" v-if="detection.severity">
<i class="cn-icon cn-icon-severity-level"></i> <i class="cn-icon cn-icon-severity-level"></i>
<span>{{$t('detection.list.security')}}&nbsp;:&nbsp;&nbsp;</span> <span>{{$t('detection.list.security')}}&nbsp;:&nbsp;&nbsp;</span>
<span>{{detection.eventSecurity || '-'}}</span> <span>{{detection.severity || '-'}}</span>
</div> </div>
<div class="basic-info__item" v-if="detection.eventSeverity"> <div class="basic-info__item" v-if="detection.eventSeverity">
<i class="cn-icon cn-icon-severity-level"></i> <i class="cn-icon cn-icon-severity-level"></i>
<span>{{$t('detections.severity')}}&nbsp;:&nbsp;&nbsp;</span> <span>{{$t('detections.severity')}}&nbsp;:&nbsp;&nbsp;</span>
<span>{{detection.eventSeverity || '-'}}</span> <span>{{detection.eventSeverity || '-'}}</span>
</div> </div>
<div class="basic-info__item" v-if="detection.eventType"> <!-- <div class="basic-info__item" v-if="detection.eventType">-->
<i class="cn-icon cn-icon-event-type"></i> <!-- <i class="cn-icon cn-icon-event-type"></i>-->
<span>{{$t('detections.eventType')}}&nbsp;:&nbsp;&nbsp;</span> <!-- <span>{{$t('detections.eventType')}}&nbsp;:&nbsp;&nbsp;</span>-->
<span>{{detection.eventType || '-'}}</span> <!-- <span>{{detection.eventType || '-'}}</span>-->
</div> <!-- </div>-->
<div class="basic-info__item" v-if="detection.malwareName"> <div class="basic-info__item" v-if="detection.eventName">
<i class="cn-icon cn-icon-trojan"></i> <i class="cn-icon cn-icon-trojan"></i>
<span>{{$t('detection.list.malwareName')}}&nbsp;:&nbsp;&nbsp;</span> <span>{{$t('detection.list.malwareName')}}&nbsp;:&nbsp;&nbsp;</span>
<span>{{detection.malwareName || '-'}}</span> <span>{{ $_.get(detection, 'malwareName', '-') || '-' }}</span>
</div>
<div class="basic-info__item" v-if="detection.cryptominingPool">
<i class="cn-icon cn-icon-mining-pool"></i>
<span>{{$t('detection.list.cryptominingPool')}}&nbsp;:&nbsp;&nbsp;</span>
<span>{{detection.cryptominingPool || '-'}}</span>
</div> </div>
<!-- <div class="basic-info__item" v-if="detection.cryptominingPool">-->
<!-- <i class="cn-icon cn-icon-mining-pool"></i>-->
<!-- <span>{{$t('detection.list.cryptominingPool')}}&nbsp;:&nbsp;&nbsp;</span>-->
<!-- <span>{{detection.cryptominingPool || '-'}}</span>-->
<!-- </div>-->
<div class="basic-info__item"> <div class="basic-info__item">
<i class="cn-icon cn-icon-time2"></i> <i class="cn-icon cn-icon-time2"></i>
<span>{{$t('detection.list.startTime')}}&nbsp;:&nbsp;&nbsp;</span> <span>{{$t('detection.list.startTime')}}&nbsp;:&nbsp;&nbsp;</span>
@@ -63,9 +64,11 @@
<div class="basic-info__item"> <div class="basic-info__item">
<i class="cn-icon cn-icon-duration"></i> <i class="cn-icon cn-icon-duration"></i>
<span>{{$t('overall.duration')}}&nbsp;:&nbsp;&nbsp;</span> <span>{{$t('overall.duration')}}&nbsp;:&nbsp;&nbsp;</span>
<span style="display: inline-block;height: 6px;width: 6px;border-radius: 50%;margin-right: 8px;" <span>
:style="pointColor(detection)"></span> {{ detection.matchTimes || '-'}} {{ $t('detection.list.times') }} /
<span>{{unitConvert(detection.durationMs, 'time', null, null, 0).join(' ') || '-'}}</span> {{unitConvert(detection.durationS, 'time', 's', null, 0).join(' ') || '-'}}
</span>
<div v-if="parseInt(detection.status)===0" class="margin-l-10 detection-row-active">Active</div>
</div> </div>
</div> </div>
</div> </div>
@@ -108,7 +111,7 @@
<script> <script>
import { eventSeverityColor, detectionPageType, entityType } from '@/utils/constants' import { eventSeverityColor, detectionPageType, entityType } from '@/utils/constants'
import { getMillisecond } 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 DetectionSecurityEventOverview from '@/views/detections/overview/DetectionSecurityEventOverview'
import DetectionPerformanceEventIpOverview from '@/views/detections/overview/DetectionPerformanceEventIpOverview' import DetectionPerformanceEventIpOverview from '@/views/detections/overview/DetectionPerformanceEventIpOverview'
@@ -177,6 +180,7 @@ export default {
methods: { methods: {
unitConvert, unitConvert,
getMillisecond, getMillisecond,
dateFormatByAppearance,
/* 切换折叠状态 */ /* 切换折叠状态 */
switchCollapse () { switchCollapse () {
this.isCollapse = !this.isCollapse this.isCollapse = !this.isCollapse
@@ -235,3 +239,17 @@ export default {
} }
} }
</script> </script>
<style>
.detection-row-active {
height: 20px;
line-height: 20px;
padding: 0 7px;
background: #E9EFE1;
border-radius: 2px;
font-family: NotoSansHans-Medium;
font-size: 12px;
color: #7E9F54;
font-weight: 500;
}
</style>

View File

@@ -5,16 +5,16 @@
<advanced-search <advanced-search
ref="search" ref="search"
:column-list="columnList[pageType]" :column-list="columnList[pageType]"
:operator-list="operatorList"
:connection-list="connectionList" :connection-list="connectionList"
:full-text="false"
class="advanced-search--show-list" class="advanced-search--show-list"
:full-text="true"
:show-list="showList"
@search="search" @search="search"
></advanced-search> ></advanced-search>
</div> </div>
<div class="search-symbol-inline"> <!-- <div class="search-symbol-inline">-->
<i class="cn-icon cn-icon-help"></i> <!-- <i class="cn-icon cn-icon-help"></i>-->
</div> <!-- </div>-->
</div> </div>
</div> </div>
</template> </template>
@@ -22,6 +22,7 @@
<script> <script>
import AdvancedSearch from '@/components/advancedSearch/Index' import AdvancedSearch from '@/components/advancedSearch/Index'
import { humpToLine } from '@/utils/tools' import { humpToLine } from '@/utils/tools'
import { schemaDetectionSecurity } from '@/utils/static-data'
export default { export default {
name: 'DetectionSearch', name: 'DetectionSearch',
props: { props: {
@@ -33,74 +34,7 @@ export default {
data () { data () {
return { return {
columnList: { columnList: {
securityEvent: [ securityEvent: schemaDetectionSecurity,
{
name: 'event_severity',
type: 'string',
// label: 'Event severity',
label: 'event_severity',
doc: {
constraints: {
operator_functions: '=,in'
}
}
},
{
name: 'security_type',
type: 'string',
// label: 'Security type',
label: 'security_type',
doc: {
constraints: {
operator_functions: '=,in'
}
}
},
{
name: 'victim_ip',
type: 'string',
// label: 'Victim IP'
label: 'victim_ip',
doc: {
constraints: {
operator_functions: '=,in'
}
}
},
{
name: 'victim_location_country',
type: 'string',
// label: 'Victim location'
label: 'victim_location_country',
doc: {
constraints: {
operator_functions: '=,in'
}
}
},
{
name: 'offender_ip',
type: 'string',
// label: 'Offender IP'
label: 'offender_ip',
doc: {
constraints: {
operator_functions: '=,in'
}
}
},
{
name: 'offender_location_country',
type: 'string',
// label: 'Offender location'
label: 'offender_location_country',
doc: {
constraints: {
operator_functions: '=,in'
}
}
}
],
performanceEvent: [ performanceEvent: [
{ {
name: 'event_severity', name: 'event_severity',
@@ -113,17 +47,6 @@ export default {
} }
} }
}, },
{
name: 'event_type',
type: 'string',
// label: 'Event type'
label: 'event_type',
doc: {
constraints: {
operator_functions: '=,in'
}
}
},
{ {
name: 'app_name', name: 'app_name',
type: 'string', type: 'string',
@@ -169,7 +92,8 @@ export default {
value: 'OR', value: 'OR',
label: 'OR' label: 'OR'
} }
] ],
showList: true
} }
}, },
emits: ['search'], emits: ['search'],

View File

@@ -54,39 +54,36 @@
<div class="detection__list-statistics detection-border"> <div class="detection__list-statistics detection-border">
<div class="statistics__severity"> <div class="statistics__severity">
<div class="chart-header"> <div class="chart-header">
<div class="chart-header__title">{{$t('detection.severity')}}</div> <div class="chart-header__title">{{$t('detections.severity')}}</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>
</template> </template>
<template v-else> <template v-else>
<div class="chart-content" :id="`eventSeverityPie${pageType}`"> <div class="chart-content" :id="`eventSeverityPie${pageType}`"></div>
</div>
</template> </template>
</div> </div>
<div class="statistics__category"> <div class="statistics__category">
<div class="chart-header"> <div class="chart-header">
<div class="chart-header__title">{{$t('detection.categoryProportion')}}</div> <div class="chart-header__title">{{$t('detections.eventType')}}</div>
</div> </div>
<template v-if="isStatisticsCategoryNoData"> <template v-if="isStatisticsCategoryNoData">
<div class="no-data chart-content" >{{ $t('npm.noData') }}</div> <div class="no-data chart-content" >{{ $t('npm.noData') }}</div>
</template> </template>
<template v-else> <template v-else>
<div class="chart-content" :id="`detectionCategoryPer${pageType}`"> <div class="chart-content" :id="`detectionCategoryPer${pageType}`"></div>
</div>
</template> </template>
</div> </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.activeAttacker') : $t('detections.activeEntity')}}</div> <div class="chart-header__title">{{pageType === detectionPageType.securityEvent ? $t('detection.activeOffender') : $t('detections.activeEntity')}}</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>
</template> </template>
<template v-else> <template v-else>
<div class="chart-content" style="padding-left: 5px;" :id="`detectionActiveAttacker${pageType}`"> <div class="chart-content" style="padding-left: 5px;" :id="`detectionActiveAttacker${pageType}`"></div>
</div>
</template> </template>
</div> </div>
</div> </div>
@@ -136,7 +133,7 @@ import {
} from '@/views/detections/options/detectionOptions' } from '@/views/detections/options/detectionOptions'
import { api, getData } from '@/utils/api' import { api, getData } from '@/utils/api'
import axios from 'axios' import axios from 'axios'
import { extensionEchartY, reverseSortBy } from '@/utils/tools' import { extensionEchartY, reverseSortBy, switchStatus } 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 ChartTabs from '@/components/common/ChartTabs'
@@ -160,18 +157,18 @@ export default {
i18n: 'entities.securityEvents', i18n: 'entities.securityEvents',
path: '/detection/securityEvent', path: '/detection/securityEvent',
icon: 'cn-icon cn-icon-a-SecurityEvent' icon: 'cn-icon cn-icon-a-SecurityEvent'
}, }
// { // {
// i18n: 'entities.regulatoryRiskEvents', // i18n: 'entities.regulatoryRiskEvents',
// path: '/detection/securityEvent', // path: '/detection/securityEvent',
// icon: 'cn-icon cn-icon-a-RegulatoryRiskEvent', // icon: 'cn-icon cn-icon-a-RegulatoryRiskEvent',
// disable: true // disable: true
// }, // },
{ // {
i18n: 'overall.performanceEvents', // i18n: 'overall.performanceEvents',
path: '/detection/performanceEvent', // path: '/detection/performanceEvent',
icon: 'cn-icon cn-icon-a-PerformanceEvent' // icon: 'cn-icon cn-icon-a-PerformanceEvent'
} // }
], ],
chartInit: [], chartInit: [],
pageObj: { pageObj: {
@@ -185,61 +182,73 @@ export default {
filterData: { filterData: {
securityEvent: [ securityEvent: [
{ {
title: this.$t('detections.eventSeverity'), title: this.$t('overall.status'),
column: 'eventSeverity', column: '',
topColumn: '', // todo schema暂无标识
collapse: false, collapse: false,
value: [], // value之间是or的关系 value: [], // value之间是or的关系
data: [] // 从接口动态获取,本项在获得数据后需要特殊处理左边框颜色 data: [] // 从接口动态获取,本项在获得数据后需要特殊处理左边框颜色
}, },
{ {
title: this.$t('detections.securityType'), title: this.$t('detections.severity'),
column: 'securityType', column: 'severity',
topColumn: 'severity',
collapse: false,
value: [], // value之间是or的关系
data: [] // 从接口动态获取,本项在获得数据后需要特殊处理左边框颜色
},
{
title: this.$t('detections.eventType'),
column: 'event_type',
topColumn: 'event_type',
collapse: false, collapse: false,
value: [], value: [],
data: [] // 从接口动态获取 data: [] // 从接口动态获取
}, },
{ {
title: this.$t('detections.victimIp'), title: this.$t('detections.victimIp'),
column: 'victimIp', column: 'victim_ip',
topColumn: 'victim_ip',
collapse: false, collapse: false,
value: [], value: [],
showMore: true, showMore: true,
showIndex: 9, showIndex: 9,
data: [] // 从接口动态获取 data: [] // 从接口动态获取
}, },
{ // {
title: this.$t('detections.victimLocation'), // title: this.$t('detections.victimLocation'),
column: 'victimLocationCountry', // column: 'victimLocationCountry',
collapse: false, // collapse: false,
value: [], // value: [],
showMore: false, // showMore: false,
showIndex: 9, // showIndex: 9,
data: [ // data: [
{ // {
label: 'China', // label: 'China',
value: 'china', // value: 'china',
count: 50 // count: 50
} // }
] // 从接口动态获取 // ] // 从接口动态获取
}, // },
{ {
title: this.$t('detections.offenderIp'), title: this.$t('detections.offenderIp'),
column: 'offenderIp', column: 'offender_ip',
collapse: false, topColumn: 'offender_ip',
value: [],
showMore: false,
showIndex: 9,
data: [] // 从接口动态获取
},
{
title: this.$t('detections.offenderLocation'),
column: 'offenderLocationCountry',
collapse: false, collapse: false,
value: [], value: [],
showMore: false, showMore: false,
showIndex: 9, showIndex: 9,
data: [] // 从接口动态获取 data: [] // 从接口动态获取
} }
// {
// title: this.$t('detections.offenderLocation'),
// column: 'offenderLocationCountry',
// collapse: false,
// value: [],
// showMore: false,
// showIndex: 9,
// data: [] // 从接口动态获取
// }
], ],
performanceEvent: [ performanceEvent: [
{ {
@@ -275,24 +284,31 @@ export default {
isStatisticsCategoryNoData: false, isStatisticsCategoryNoData: false,
isStatisticsActiveAttackNoData: false, isStatisticsActiveAttackNoData: false,
loading: false, loading: false,
oldActiveEntitySearchValue: '' oldActiveEntitySearchValue: '',
initFlag: true // 初始化标识初始化时保证mounted执行
} }
}, },
methods: { methods: {
// 初始化顶部大柱状图 initStatusData (params) {
getData(api.detection[this.pageType].statusStatistics, params).then(data => {
this.filterData[this.pageType][0].data = data.map(r => ({ label: this.$t(switchStatus(r.status)), value: r.status, count: r.count }))
})
},
/** 初始化顶部大柱状图 */
initEventSeverityTrendData (params) { initEventSeverityTrendData (params) {
this.loading = true this.loading = true
/* getData(api.detection[this.pageType].eventSeverityTrend, params).then(data => { // getData(api.detection[this.pageType].eventSeverityTrend, params).then(data => {
getData(api.detection[this.pageType].timeDistribution, params).then(data => {
this.eventSeverityData = data this.eventSeverityData = data
if (!this.$_.isEmpty(data)) { if (!this.$_.isEmpty(data)) {
const dataMap = new Map() const dataMap = new Map()
data.forEach(item => { data.forEach(item => {
if (item.eventSeverity) { if (item.severity) {
if (!dataMap.has(item.eventSeverity)) { if (!dataMap.has(item.severity)) {
const count = [[toTime(item.statTime), item.count]] const count = [[toTime(item.statTime), item.count]]
dataMap.set(item.eventSeverity, count) dataMap.set(item.severity, count)
} else { } else {
dataMap.get(item.eventSeverity).push([toTime(item.statTime), item.count]) dataMap.get(item.severity).push([toTime(item.statTime), item.count])
} }
} }
}) })
@@ -354,26 +370,18 @@ export default {
this.$nextTick(() => { this.$nextTick(() => {
this.loading = false this.loading = false
}) })
}) */ })
const timer = setTimeout(() => {
this.loading = false
this.isEventSeverityNoData = true
this.isStatisticsCategoryNoData = true
this.isStatisticsSeverityNoData = true
this.isStatisticsActiveAttackNoData = true
clearTimeout(timer)
}, 150)
}, },
/** 初始化左侧事件严重等级和第一个小饼图 */
// 初始化左侧事件严重等级和小饼图
initEventSeverityData (params) { initEventSeverityData (params) {
getData(api.detection[this.pageType].eventSeverity, params).then(data => { // getData(api.detection[this.pageType].eventSeverity, params).then(data => {
getData(api.detection[this.pageType].severityStatistics, params).then(data => {
this.statisticsSeverityData = data this.statisticsSeverityData = data
if (!this.$_.isEmpty(data)) { if (!this.$_.isEmpty(data)) {
this.filterData[this.pageType][0].data = data.map(r => ({ label: r.eventSeverity, value: r.eventSeverity, count: r.count })) this.filterData[this.pageType][1].data = data.map(r => ({ label: r.severity, value: r.severity, count: r.count }))
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: d.eventSeverity, itemStyle: { color: getSeverityColor(d.eventSeverity) } } return { value: d.count, name: d.severity, itemStyle: { color: getSeverityColor(d.severity) } }
}) })
const chartDom = document.getElementById(`eventSeverityPie${this.pageType}`) const chartDom = document.getElementById(`eventSeverityPie${this.pageType}`)
let detectionChart = echarts.getInstanceByDom(chartDom) let detectionChart = echarts.getInstanceByDom(chartDom)
@@ -401,7 +409,7 @@ export default {
getData(api.detection[this.pageType].eventType, params).then(data => { getData(api.detection[this.pageType].eventType, params).then(data => {
this.statisticsCategoryData = data this.statisticsCategoryData = data
if (!this.$_.isEmpty(data)) { if (!this.$_.isEmpty(data)) {
this.filterData[this.pageType][1].data = data.map(r => ({ this.filterData[this.pageType][2].data = data.map(r => ({
label: r.eventType, label: r.eventType,
value: r.eventType, value: r.eventType,
count: r.count count: r.count
@@ -429,13 +437,15 @@ export default {
console.log(error) console.log(error)
}) })
}, },
/** 第二个饼图和左侧filter的eventType */
initSecurityTypeData (params) { initSecurityTypeData (params) {
getData(api.detection[this.pageType].securityType, params).then(data => { // getData(api.detection[this.pageType].securityType, params).then(data => {
getData(api.detection[this.pageType].eventTypeStatistics, params).then(data => {
this.statisticsCategoryData = data this.statisticsCategoryData = data
if (!this.$_.isEmpty(data)) { if (!this.$_.isEmpty(data)) {
this.filterData[this.pageType][1].data = data.map(r => ({ this.filterData[this.pageType][2].data = data.map(r => ({
label: r.securityType, label: r.eventType,
value: r.securityType, value: r.eventType,
count: r.count count: r.count
})) }))
const chartDom = document.getElementById(`detectionCategoryPer${this.pageType}`) const chartDom = document.getElementById(`detectionCategoryPer${this.pageType}`)
@@ -447,7 +457,7 @@ export default {
this.chartInit.push(shallowRef(detectionChart)) this.chartInit.push(shallowRef(detectionChart))
const securityTypeOption = this.$_.cloneDeep(pieForSeverity) const securityTypeOption = this.$_.cloneDeep(pieForSeverity)
securityTypeOption.series[0].data = data.map(d => { securityTypeOption.series[0].data = data.map(d => {
return { value: d.count, name: d.securityType, itemStyle: { color: getAttackColor(d.securityType) } } return { value: d.count, name: d.eventType, itemStyle: { color: getAttackColor(d.eventType) } }
}) })
detectionChart.setOption(securityTypeOption) detectionChart.setOption(securityTypeOption)
@@ -461,8 +471,10 @@ export default {
console.log(error) console.log(error)
}) })
}, },
/** 横向柱状图和左侧filter的offenderIp */
initOffenderIpData (params) { initOffenderIpData (params) {
getData(api.detection[this.pageType].offenderIp, params).then(data => { // getData(api.detection[this.pageType].offenderIp, params).then(data => {
getData(api.detection[this.pageType].offenderIpStatistics, params).then(data => {
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[this.pageType][4].data = data.map(r => ({
@@ -501,11 +513,12 @@ export default {
}, },
initVictimIpData (params) { initVictimIpData (params) {
getData(api.detection[this.pageType].victimIp, params).then(data => { // getData(api.detection[this.pageType].victimIp, params).then(data => {
this.filterData[this.pageType][2].data = data.map(r => ({ label: r.victimIp, value: r.victimIp, count: r.count })) getData(api.detection[this.pageType].victimIpStatistics, params).then(data => {
const { showMore, showIndex } = this.computeFilterPage(this.filterData[this.pageType][2].data) this.filterData[this.pageType][3].data = data.map(r => ({ label: r.victimIp, value: r.victimIp, count: r.count }))
this.filterData[this.pageType][2].showMore = showMore const { showMore, showIndex } = this.computeFilterPage(this.filterData[this.pageType][3].data)
this.filterData[this.pageType][2].showIndex = showIndex this.filterData[this.pageType][3].showMore = showMore
this.filterData[this.pageType][3].showIndex = showIndex
}).catch(error => { }).catch(error => {
console.log(error) console.log(error)
}) })
@@ -610,15 +623,15 @@ export default {
showIndex: 9 showIndex: 9
} }
}, },
queryList () { queryList (q) {
const params = { const params = {
startTime: getSecond(this.timeFilter.startTime), startTime: getSecond(this.timeFilter.startTime),
endTime: getSecond(this.timeFilter.endTime), endTime: getSecond(this.timeFilter.endTime),
q: this.q, resource: q,
pageSize: this.pageObj.pageSize, pageSize: this.pageObj.pageSize,
pageNo: this.pageObj.pageNo pageNo: this.pageObj.pageNo
} }
/* axios.get(api.detection[this.pageType].listBasic, { params }).then(response => { axios.get(api.detection[this.pageType].securityList, { params }).then(response => {
if (response.status === 200) { if (response.status === 200) {
this.listData = response.data.data.result this.listData = response.data.data.result
} else { } else {
@@ -627,11 +640,12 @@ export default {
this.$message.error(response.data.message) this.$message.error(response.data.message)
} }
}) })
getData(api.detection[this.pageType].listCount, params).then(data => { // getData(api.detection[this.pageType].listCount, params).then(data => {
getData(api.detection[this.pageType].securityCount, params).then(data => {
this.pageObj.total = data this.pageObj.total = data
}).catch(error => { }).catch(error => {
console.log(error) console.log(error)
}) */ })
}, },
timeRefreshChange () { timeRefreshChange () {
this.initNoData() this.initNoData()
@@ -674,8 +688,15 @@ export default {
} else { } else {
this.pageObj.resetPageNo = true this.pageObj.resetPageNo = true
} }
this.queryFilter() // 参数q避免切换页码时地址栏参数q为空
this.queryList() let urlQ = ''
if (param.str) {
urlQ = encodeURI(param.str)
} else if (this.q) {
urlQ = encodeURI(this.q)
}
this.queryFilter(urlQ)
this.queryList(urlQ)
}, },
resetFilterData () { resetFilterData () {
this.filterData.securityEvent.forEach(d => { this.filterData.securityEvent.forEach(d => {
@@ -685,22 +706,23 @@ export default {
d.data = [] d.data = []
}) })
}, },
queryFilter () { queryFilter (q) {
this.resetFilterData() this.resetFilterData()
const params = { const params = {
startTime: getSecond(this.timeFilter.startTime), startTime: getSecond(this.timeFilter.startTime),
endTime: getSecond(this.timeFilter.endTime), endTime: getSecond(this.timeFilter.endTime),
q: this.q resource: q
} }
this.listData = [] this.listData = []
this.initStatusData(params)
this.initEventSeverityTrendData(params) this.initEventSeverityTrendData(params)
// this.initEventSeverityData(params) this.initEventSeverityData(params)
if (this.pageType === detectionPageType.securityEvent) { if (this.pageType === detectionPageType.securityEvent) {
// this.initOffenderIpData(params) this.initOffenderIpData(params)
// this.initOffenderLocationData(params) // this.initOffenderLocationData(params)
// this.initVictimIpData(params) this.initVictimIpData(params)
// this.initVictimLocationData(params) // this.initVictimLocationData(params)
// this.initSecurityTypeData(params) this.initSecurityTypeData(params)
} else if (this.pageType === detectionPageType.performanceEvent) { } else if (this.pageType === detectionPageType.performanceEvent) {
// this.initActiveEntity(params) // this.initActiveEntity(params)
// this.initEventTypeData(params) // this.initEventTypeData(params)
@@ -713,7 +735,11 @@ export default {
pageNo (val) { pageNo (val) {
this.pageObj.pageNo = val || 1 this.pageObj.pageNo = val || 1
this.pageObj.resetPageNo = false this.pageObj.resetPageNo = false
this.search(this.metaList, this.q) // 初始化时mounted和pageNo都会调用列表接口且pageNo先执行
// 初始化保证mounted执行后续pageNo变动则不影响接口调用
if (!this.initFlag) {
this.search(this.metaList, this.q)
}
}, },
// 点击上一页箭头 // 点击上一页箭头
prev () { prev () {
@@ -748,6 +774,7 @@ export default {
}, },
mounted () { mounted () {
this.queryFilter() this.queryFilter()
this.initFlag = false
this.queryList() this.queryList()
this.debounceFunc = this.$_.debounce(this.resize, 300) this.debounceFunc = this.$_.debounce(this.resize, 300)
window.addEventListener('resize', this.debounceFunc) window.addEventListener('resize', this.debounceFunc)

View File

@@ -3,162 +3,148 @@
<div class="overview__left"> <div class="overview__left">
<div class="overview__title">{{ $t('overall.remark') }}</div> <div class="overview__title">{{ $t('overall.remark') }}</div>
<div class="overview__row"> <div class="overview__row">
<div class="row__content"> <div class="row__content1" v-if="basicInfo.malwareInfo">
<template v-if="detection.securityType === 'command and control'"> <template v-if="basicInfo.malwareInfo.threatType === 'command and control'">
<span <span
class="row__content--link" class="row__content--link"
@click="goDetail('ip', detection.victimIp)" @click="goDetail('ip', detection.victimIp)">
>{{ detection.victimIp }}</span>&nbsp; {{ detection.victimIp }}
<span> </span>&nbsp;
{{$t('detection.commandAndControl')}} <span>{{$t('detection.commandAndControl')}}</span>
<span class="row__content--link" @click="goDetail('ip', detection.eventInfoObj.ioc_value)">
{{ detection.eventInfoObj.ioc_value || '-' }}
</span> </span>
<span class="row__content--link" @click="goDetail('ip', basicInfo.iocValue)">{{basicInfo.iocValue || '-'}}</span></template </template>
>
<template v-else> <template v-else>
<span <span
class="row__content--link" class="row__content--link"
@click="goDetail('ip', detection.victimIp)" @click="goDetail('ip', detection.victimIp)">
>{{ detection.victimIp }}</span {{ detection.victimIp }}
>&nbsp; </span>&nbsp;
<span> <span>{{$t('detection.payloadAndDelivery')}}</span>
{{$t('detection.payloadAndDelivery')}} <span
class="row__content--link"
@click="goDetail('ip', detection.eventInfoObj.ioc_value)">
{{ detection.eventInfoObj.ioc_value }}
</span> </span>
<span class="row__content--link" </template>
@click="goDetail('ip', basicInfo.iocValue)"
>{{
basicInfo.iocValue
}}</span></template>
</div> </div>
</div> </div>
<div class="overview__title">Fields</div> <div class="overview__title">Fields</div>
<div class="overview__row"> <div class="overview__row">
<div class="row__label">{{ $t('detection.list.startTime') }}</div> <div class="row__label">{{ $t('detection.list.startTime') }}</div>
<div class="row__content"> <div class="row__content">
{{ <i class="cn-icon cn-icon-time2 row__content__icon"></i>
basicInfo.startTime {{ detection.startTime ? dateFormatByAppearance(detection.startTime) : '-' }}
? dateFormatByAppearance(basicInfo.startTime)
: '-'
}}
</div> </div>
</div> </div>
<div class="overview__row"> <div class="overview__row">
<div class="row__label">{{ $t('detections.victimIp') }}</div> <div class="row__label">{{ $t('detections.victimIp') }}</div>
<div class="row__content">{{ basicInfo.victimIp || '-' }}</div> <div class="row__content">{{ detection.victimIp || '-' }}</div>
</div> </div>
<div class="overview__row"> <div class="overview__row">
<div class="row__label">{{ $t('detections.victimLocation') }}</div> <div class="row__label">{{ $t('detections.victimLocation') }}</div>
<div class="row__content"> <div class="row__content">{{ $_.get(basicInfo, 'victimInfo.location.country', '-') || '-' }}</div>
{{ basicInfo.victimLocationCountry || '-' }}
</div>
</div> </div>
<div class="overview__row"> <div class="overview__row">
<div class="row__label">{{ $t('detections.victimAsn') }}</div> <div class="row__label">{{ $t('detections.victimAsn') }}</div>
<div class="row__content">{{ basicInfo.victimAsn || '-' }}</div> <div class="row__content">{{ $_.get(basicInfo, 'victimInfo.asn.asn', '-') || '-' }}</div>
</div> </div>
<div class="overview__row"> <div class="overview__row">
<div class="row__label">{{ $t('detections.offenderIp') }}</div> <div class="row__label">{{ $t('detections.offenderIp') }}</div>
<div class="row__content">{{ basicInfo.offenderIp || '-' }}</div> <div class="row__content">{{ detection.offenderIp || '-' }}</div>
</div> </div>
<div class="overview__row"> <div class="overview__row">
<div class="row__label">{{ $t('detections.offenderLocation') }}</div> <div class="row__label">{{ $t('detections.offenderLocation') }}</div>
<div class="row__content"> <div class="row__content">{{ $_.get(basicInfo, 'offenderInfo.location.country', '-') || '-' }}</div>
{{ basicInfo.offenderLocationCountry || '-' }}
</div>
</div> </div>
<div class="overview__row"> <div class="overview__row">
<div class="row__label">{{ $t('detections.offenderAsn') }}</div> <div class="row__label">{{ $t('detections.offenderAsn') }}</div>
<div class="row__content">{{ basicInfo.offenderAsn || '-' }}</div> <div class="row__content">{{ $_.get(basicInfo, 'offenderInfo.asn.asn', '-') || '-' }}</div>
</div> </div>
<div class="overview__row"> <div class="overview__row">
<div class="row__label">{{ $t('overall.domain') }}</div> <div class="row__label">{{ $t('overall.domain') }}</div>
<div class="row__content">{{ basicInfo.domain || '-' }}</div> <div class="row__content">{{ detection.domain || '-' }}</div>
</div> </div>
<div class="overview__row"> <div class="overview__row">
<div class="row__label">{{ $t('entities.domainCategory') }}</div> <div class="row__label">{{ $t('entities.domainCategory') }}</div>
<div class="row__content"> <div class="row__content">{{ $_.get(basicInfo, 'domainInfo.category.categoryName', '-') || '-' }}</div>
{{ basicInfo.domainCategoryName || '-' }}
</div>
</div> </div>
<div class="overview__row"> <div class="overview__row">
<div class="row__label"> <div class="row__label">{{ $t('entities.domainDetail.categoryGroup') }}</div>
{{ $t('entities.domainDetail.categoryGroup') }} <div class="row__content">{{ $_.get(basicInfo, 'domainInfo.category.categoryGroup', '-') || '-' }}</div>
</div>
<div class="row__content">
{{ basicInfo.domainCategoryGroup || '-' }}
</div>
</div> </div>
<div class="overview__row"> <div class="overview__row">
<div class="row__label">{{ $t('entities.reputationLevel') }}</div> <div class="row__label">{{ $t('entities.reputationLevel') }}</div>
<div class="row__content"> <div class="row__content" v-if="$_.get(basicInfo, 'domainInfo.category.reputationLevel')">
<div <div
class="row__tag" class="row__tag row__tag__level"
:style="`background-color:${eventSeverityColor[basicInfo.domainReputationLevel]}`" :style="`background-color:${eventSeverityColor[basicInfo.domainInfo.category.reputationLevel]}`">
> {{ basicInfo.domainInfo.category.reputationLevel }}
{{ basicInfo.domainReputationLevel || '-' }}
</div> </div>
</div> </div>
<div class="row__content" v-else>-</div>
</div> </div>
<div class="overview__row"> <div class="overview__row">
<div class="row__label">APP</div> <div class="row__label">APP</div>
<div class="row__content">{{ basicInfo.appName || '-' }}</div> <div class="row__content">{{ $_.get(basicInfo, 'appInfo.category.appName', '-') || '-' }}</div>
</div> </div>
<div class="overview__row"> <div class="overview__row">
<div class="row__label">APP {{ $t('entities.category') }}</div> <div class="row__label">APP {{ $t('entities.category') }}</div>
<div class="row__content">{{ basicInfo.appCategory || '-' }}</div> <div class="row__content">{{ $_.get(basicInfo, 'appInfo.category.appCategory', '-') || '-' }}</div>
</div> </div>
<div class="overview__row"> <div class="overview__row">
<div class="row__label">APP {{ $t('entities.subcategory') }}</div> <div class="row__label">APP {{ $t('entities.subcategory') }}</div>
<div class="row__content">{{ basicInfo.appSubcategory || '-' }}</div> <div class="row__content">{{ $_.get(basicInfo, 'appInfo.category.appSubcategory', '-') || '-' }}</div>
</div> </div>
<div class="overview__row"> <div class="overview__row">
<div class="row__label">{{ $t('overall.appRisk') }}</div> <div class="row__label">{{ $t('overall.appRisk') }}</div>
<div class="row__content"> <div class="row__content" v-if="$_.get(basicInfo, 'appInfo.category.appRisk')">
<div <div
class="row__tag" class="row__tag row__tag__level"
:style="`background-color:${eventSeverityColor[basicInfo.appRisk]}`" :style="`background-color:${eventSeverityColor[basicInfo.appInfo.category.appRisk]}`">
> {{ basicInfo.appInfo.category.appRisk }}
{{ basicInfo.appRisk || '-' }}
</div> </div>
</div> </div>
<div class="row__content" v-else>-</div>
</div> </div>
<div class="overview__row"> <div class="overview__row">
<div class="row__label">{{ $t('detections.malware') }}</div> <div class="row__label">{{ $t('detections.malware') }}</div>
<div class="row__content">{{ basicInfo.malwareName || '-' }}</div> <div class="row__content">{{ $_.get(basicInfo, 'malwareInfo.malwareName', '-') || '-' }}</div>
</div> </div>
<div class="overview__row"> <div class="overview__row">
<div class="row__label">{{ $t('detections.malwareAlias') }}</div> <div class="row__label">{{ $t('detections.malwareAlias') }}</div>
<div class="row__content">{{ basicInfo.malwareAlias || '-' }}</div> <div class="row__content">{{ $_.get(basicInfo, 'malwareInfo.malwareAlias', '-') || '-' }}</div>
</div> </div>
<div class="overview__row"> <div class="overview__row">
<div class="row__label">{{ $t('detections.malwareDescription') }}</div> <div class="row__label">{{ $t('detections.malwareDescription') }}</div>
<div class="row__content"> <div class="row__content">{{ $_.get(basicInfo, 'malwareInfo.mitreAttackDescription', '-') || '-' }}</div>
{{ basicInfo.malwareDescription || '-' }}
</div>
</div> </div>
<div class="overview__row"> <div class="overview__row">
<div class="row__label">{{ $t('detections.malwarePlatforms') }}</div> <div class="row__label">{{ $t('detections.malwarePlatforms') }}</div>
<div class="row__content">{{ basicInfo.malwarePlatforms || '-' }}</div> <div class="row__content" v-if="$_.get(basicInfo, 'malwareInfo.mitreAttackPlatforms')">
<svg class="icon item-popover-up row__content__svg" aria-hidden="true">
<use xlink:href="#cn-icon-windows"></use>
</svg>
{{ basicInfo.malwareInfo.mitreAttackPlatforms }}
</div>
<div class="row__content" v-else>-</div>
</div> </div>
<div class="overview__row"> <div class="overview__row">
<div class="row__label">{{ $t('detections.malwareTechniques') }}</div> <div class="row__label">{{ $t('detections.malwareTechniques') }}</div>
<div class="row__content"> <div class="row__content">{{ $_.get(basicInfo, 'malwareInfo.mitreAttackTechniques', '-') || '-' }}</div>
{{ basicInfo.malwareTechniques || '-' }}
</div>
</div> </div>
<div class="overview__row"> <div class="overview__row">
<div class="row__label">{{ $t('detections.malwareGroups') }}</div> <div class="row__label">{{ $t('detections.malwareGroups') }}</div>
<div class="row__content"> <div class="row__content">{{ $_.get(basicInfo, 'malwareInfo.mitreAttackGroups', '-') || '-' }}</div>
{{ basicInfo.malwareGroups || '-' }}
</div>
</div> </div>
<div class="overview__row"> <div class="overview__row">
<div class="row__label">{{ $t('detections.reference') }}</div> <div class="row__label">{{ $t('detections.reference') }}</div>
<div class="row__content row__content--link"> <div class="row__content row__content--link" v-if="$_.get(basicInfo, 'malwareInfo.reference')">
{{ reference || '-' }} {{ basicInfo.malwareInfo.reference }}
</div> </div>
<div class="row__content">-</div>
</div> </div>
<!-- <template v-if="detection.securityType === 'command and control' || detection.securityType === 'payload_delivery'">
</template>-->
</div> </div>
<div class="overview__right"> <div class="overview__right">
<div class="overview__title">{{ $t('detections.goToVictim') }}</div> <div class="overview__title">{{ $t('detections.goToVictim') }}</div>
@@ -167,7 +153,7 @@
<span class="row__content--span">{{ $t('detections.viewDetailOf') }}</span> &nbsp; <span class="row__content--span">{{ $t('detections.viewDetailOf') }}</span> &nbsp;
<span <span
class="row__content--link" class="row__content--link"
@click="goDetail('ip', basicInfo.victimIp)">{{ basicInfo.victimIp }}</span> @click="goDetail('ip', detection.victimIp)">{{ detection.victimIp }}</span>
</div> </div>
</div> </div>
<div class="overview__title">{{ $t('detections.goToOffender') }}</div> <div class="overview__title">{{ $t('detections.goToOffender') }}</div>
@@ -176,22 +162,22 @@
<span class="row__content--span">{{ $t('detections.viewDetailOf') }}</span> &nbsp; <span class="row__content--span">{{ $t('detections.viewDetailOf') }}</span> &nbsp;
<span <span
class="row__content--link" class="row__content--link"
@click="goDetail('ip', basicInfo.offenderIp)" @click="goDetail('ip', detection.offenderIp)"
>{{ basicInfo.offenderIp }}</span >{{ detection.offenderIp }}</span
>&nbsp;&nbsp; >&nbsp;&nbsp;
<span <span
class="row__content--link" class="row__content--link"
@click="goDetail('domain', basicInfo.domain)" @click="goDetail('domain', detection.domain)"
>{{ basicInfo.domain }}</span >{{ detection.domain }}</span
> >
</div> </div>
</div> </div>
<div class="overview__title">{{ $t('detections.goToHunt') }}</div> <!-- <div class="overview__title">{{ $t('detections.goToHunt') }}</div>-->
<div class="overview__row"> <!-- <div class="overview__row">-->
<div class="row__content row__content--link"> <!-- <div class="row__content row__content&#45;&#45;link">-->
{{ $t('detections.viewAllRelated') }} <!-- {{ $t('detections.viewAllRelated') }}-->
</div> <!-- </div>-->
</div> <!-- </div>-->
<div class="overview__title"> <div class="overview__title">
{{ $t('detections.relatedDetections') }} {{ $t('detections.relatedDetections') }}
</div> </div>
@@ -221,18 +207,12 @@
<div class="timeline__severity timeline__severity--high"> <div class="timeline__severity timeline__severity--high">
<i <i
class="cn-icon cn-icon-alert-level" class="cn-icon cn-icon-alert-level"
:style="`color:${eventSeverityColor[event.eventSeverity]}`" :style="`color:${eventSeverityColor[event.severity]}`"
></i> ></i>
<span>{{ event.eventSeverity }}</span> <span>{{ event.severity }}</span>
</div>
<div class="timeline__security-type">
{{ event.securityType }}
</div>
<div class="timeline__start-time">
{{
dateFormatByAppearance(event.startTime)
}}
</div> </div>
<div class="timeline__security-type">{{ event.eventType }}</div>
<div class="timeline__start-time">{{ dateFormatByAppearance(event.startTime) }}</div>
</div> </div>
</div> </div>
<div class="row-timeline__foot"> <div class="row-timeline__foot">
@@ -240,7 +220,7 @@
class="detection-ip" class="detection-ip"
:class="{ :class="{
'detection-ip__current': 'detection-ip__current':
[basicInfo.offenderIp, basicInfo.victimIp].indexOf( [detection.offenderIp, detection.victimIp].indexOf(
event.offenderIp, event.offenderIp,
) > -1, ) > -1,
}" }"
@@ -252,7 +232,7 @@
class="detection-ip" class="detection-ip"
:class="{ :class="{
'detection-ip__current': 'detection-ip__current':
[basicInfo.offenderIp, basicInfo.victimIp].indexOf( [detection.offenderIp, detection.victimIp].indexOf(
event.victimIp, event.victimIp,
) > -1, ) > -1,
}" }"
@@ -270,7 +250,7 @@
<script> <script>
import axios from 'axios' import axios from 'axios'
import { api } from '@/utils/api' import { api } from '@/utils/api'
import { getMillisecond } from '@/utils/date-util' import { getMillisecond, dateFormatByAppearance } from '@/utils/date-util'
import { eventSeverityColor, unitTypes } from '@/utils/constants' import { eventSeverityColor, unitTypes } from '@/utils/constants'
import unitConvert from '@/utils/unit-convert' import unitConvert from '@/utils/unit-convert'
import _ from 'lodash' import _ from 'lodash'
@@ -291,25 +271,15 @@ export default {
formatT0 () { formatT0 () {
const vm = this const vm = this
return function (event) { return function (event) {
const diffSeconds = event.diffSeconds const diffSeconds = this.detection.startTime - event.startTime
if (diffSeconds === 0) { if (diffSeconds === 0) {
return 'T0' return 'T0'
} }
const eventStartTime = event.startTime const eventStartTime = event.startTime
const entityStartTime = vm.detection.startTime const entityStartTime = vm.detection.startTime
if ( if (_.isNumber(diffSeconds) && _.isNumber(eventStartTime) && _.isNumber(entityStartTime)) {
_.isNumber(diffSeconds) && const suffix = unitConvert(diffSeconds, unitTypes.time, 's', null, 0).join('')
_.isNumber(eventStartTime) &&
_.isNumber(entityStartTime)
) {
const suffix = unitConvert(
diffSeconds,
unitTypes.time,
's',
null,
0
).join('')
if (eventStartTime > entityStartTime) { if (eventStartTime > entityStartTime) {
return `T0+${suffix}` return `T0+${suffix}`
} else if (eventStartTime < entityStartTime) { } else if (eventStartTime < entityStartTime) {
@@ -322,58 +292,70 @@ export default {
}, },
methods: { methods: {
getMillisecond, getMillisecond,
query () { dateFormatByAppearance,
Promise.all([this.queryBasic(), this.queryEvent()]).then((responses) => { /** 初始化实体详情 */
responses[0].malwareTechniques = responses[0].malwareTechniques.length > 2 ? responses[0].malwareTechniques.replace('[', '').replace(']', '').split(',', 5).join(', ') : '' initEntityDetail () {
responses[0].malwareGroups = responses[0].malwareGroups.length > 2 ? responses[0].malwareGroups.replace('[', '').replace(']', '').split(',', 5).join(', ') : '' // 为完整填充IP信息攻击者ip、受害者ip都进行调用
responses[0].malwarePlatforms = responses[0].malwarePlatforms.length > 1 ? responses[0].malwarePlatforms : '' // 根据detection的eventInfo对象的ioc_type进行判断若为domainmalware信息从domain详情中获取并填充domain信息
responses[0].malwareDescription = responses[0].malwareDescription.length > 1 ? responses[0].malwareDescription : '' // 若ioc_type为ip则调用ip接口填充malware信息
responses[0] && (this.basicInfo = responses[0]) // 最后调用app填充app信息。经上获取完整实体详情则最少需要调用4次接口
responses[1] && const offenderIPDetail = axios.get(`${api.detection.securityEvent.ipDetail}/resource=${this.detection.offenderIp}`)
(this.events = responses[1].sort( const victimIPDetail = axios.get(`${api.detection.securityEvent.ipDetail}/resource=${this.detection.victimIp}`)
(e1, e2) => e1.startTime - e2.startTime let ipDetail
)) let domainDetail
}) let promiseList = []
}, const appDetail = axios.get(`${api.detection.securityEvent.appDetail}/resource=${this.detection.app}`)
queryBasic () { if (this.detection.eventInfoObj.ioc_type.toLowerCase() === 'domain') {
return new Promise((resolve, reject) => { domainDetail = axios.get(`${api.detection.securityEvent.domainDetail}/resource=${this.detection.eventInfoObj.ioc_value}`)
try { promiseList = [offenderIPDetail, victimIPDetail, domainDetail, appDetail]
axios.get(api.detection.securityEvent.overviewBasic, { }
params: { if (this.detection.eventInfoObj.ioc_type.toLowerCase() === 'ip') {
eventId: this.detection.eventId, ipDetail = axios.get(`${api.detection.securityEvent.ipDetail}/resource=${this.detection.eventInfoObj.ioc_value}`)
startTime: this.detection.startTime, domainDetail = axios.get(`${api.detection.securityEvent.domainDetail}/resource=${this.detection.domain}`)
endTime: this.detection.endTime promiseList = [offenderIPDetail, victimIPDetail, domainDetail, appDetail, ipDetail]
}
Promise.all(promiseList).then((responses) => {
responses.forEach((res, i) => {
if (res.status === 200) {
if (i === 0) {
this.basicInfo.offenderInfo = res.data.data
} }
}).then((response) => { if (i === 1) {
if (response.status === 200) { this.basicInfo.victimInfo = res.data.data
resolve(response.data.data.result[0])
} else {
reject(response.data)
} }
}) if (i === 2) {
} catch (e) { this.basicInfo.domainInfo = res.data.data
reject(e) }
if (i === 3) {
this.basicInfo.appInfo = res.data.data
}
if (i === 4) {
this.basicInfo.malwareInfo = res.data.data.malwareInfo
}
} else {
console.error(res)
}
})
if (this.detection.eventInfoObj.ioc_type.toLowerCase() === 'domain') {
this.basicInfo.malwareInfo = this.basicInfo.domainInfo.malware || {}
} }
}) })
}, },
queryEvent () { queryEvent () {
return new Promise((resolve, reject) => { axios.get(api.detection.securityEvent.relationEvent, {
try { params: {
axios.get(api.detection.securityEvent.overviewEvent, { // startTime: this.detection.startTime,
params: { unbiasedTime: this.detection.startTime,
startTime: this.detection.startTime, offenderIp: this.detection.offenderIp,
offenderIp: this.detection.offenderIp, victimIp: this.detection.victimIp,
victimIp: this.detection.victimIp biasSecond: 3600
} }
}).then((response) => { }).then((response) => {
if (response.status === 200) { if (response.status === 200) {
resolve(response.data.data.result) this.events = response.data.data.result.sort((e1, e2) => e1.startTime - e2.startTime)
} else { } else {
reject(response.data) this.events = []
}
})
} catch (e) {
reject(e)
} }
}) })
}, },
@@ -391,7 +373,17 @@ export default {
} }
}, },
mounted () { mounted () {
this.query() this.initEntityDetail()
this.queryEvent()
} }
} }
</script> </script>
<style scoped>
.row__label {
width: 176px;
}
.row__content {
width: calc(100% - 176px);
}
</style>