CN-1173 fix: 检测功能UI开发与接口对接

This commit is contained in:
刘洪洪
2023-10-16 17:53:46 +08:00
parent 85db8cd745
commit 0e82bebaac
19 changed files with 725 additions and 437 deletions

View File

@@ -208,6 +208,15 @@
font-weight: 500; font-weight: 500;
padding: 0 14px; padding: 0 14px;
} }
.btn1 {
margin-right: 10px;
.el-button {
background: #F5F6F7;
border: 1px solid rgba(215,215,215,1);
color: #353636;
}
}
} }
.form-setting__btn1 { .form-setting__btn1 {

View File

@@ -15,7 +15,7 @@
background-color: rgba(0, 0, 0, 0.16) !important; background-color: rgba(0, 0, 0, 0.16) !important;
} }
.detection-drawer-title, .basic-function-value, basic-description-value { .detection-drawer-title, .basic-function-value, basic-description-value, .drawer-trigger-minutes {
font-family: NotoSansSChineseRegular; font-family: NotoSansSChineseRegular;
font-size: 14px; font-size: 14px;
color: #717171; color: #717171;
@@ -58,6 +58,10 @@
color: #046ECA; color: #046ECA;
} }
.drawer-trigger-minutes {
color: #353636;
}
.detection-drawer-collapse { .detection-drawer-collapse {
background: #FFFFFF; background: #FFFFFF;
border: 1px solid rgba(226, 229, 236, 1); border: 1px solid rgba(226, 229, 236, 1);

View File

@@ -18,7 +18,7 @@
</div> </div>
</div> </div>
<div class="block-mode"> <div class="block-mode" style="cursor: no-drop;">
<!--todo 图标没有后期换--> <!--todo 图标没有后期换-->
<div class="block-mode-left"> <div class="block-mode-left">
<i class="cn-icon cn-icon-setting2 block-mode-icon"></i> <i class="cn-icon cn-icon-setting2 block-mode-icon"></i>
@@ -29,40 +29,44 @@
<div class="block-mode-content"> <div class="block-mode-content">
Aggregate query results to detect when number of matches exceeds threshold. Aggregate query results to detect when number of matches exceeds threshold.
</div> </div>
<div :class="settingObj.ruleType===detectionRuleType.threshold?'block-mode-btn-active':'block-mode-btn'" <!--todo 当前版本暂时不可选择-->
@click="selectMode(detectionRuleType.threshold)">select <div style="cursor: no-drop;" :class="settingObj.ruleType===detectionRuleType.threshold?'block-mode-btn-active':'block-mode-btn'"
>select
</div> </div>
<!-- <div :class="settingObj.ruleType===detectionRuleType.threshold?'block-mode-btn-active':'block-mode-btn'"-->
<!-- @click="selectMode(detectionRuleType.threshold)">select-->
<!-- </div>-->
</div> </div>
</div> </div>
</div> </div>
<!--category--> <!--category-->
<el-form ref="form" :model="settingObj" label-position="top" :rules="rules"> <el-form ref="form" :model="settingObj" label-position="top" :rules="rules">
<el-form-item label="Category" prop="category" class="form-setting__block margin-b-20"> <el-form-item :label="$t('overall.category')" prop="category" class="form-setting__block margin-b-20">
<el-select v-model="settingObj.category" class="form-setting__select" placeholder=" " size="mini" @change="changeEditFlag"> <el-select :disabled="settingObj.ruleId" v-model="settingObj.category" class="form-setting__select" placeholder=" " size="mini" @change="changeEditFlag">
<el-option <el-option
v-for="item in categoryList" v-for="item in categoryList"
:key="item.value" :key="item.name"
:label="item.label" :label="item.name"
:value="item.value" :value="item.name"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<!--type--> <!--type-->
<el-form-item label="Type" prop="eventType" class="form-setting__block margin-b-20"> <el-form-item :label="$t('overall.type')" prop="eventType" class="form-setting__block margin-b-20">
<el-select v-model="settingObj.eventType" placeholder=" " size="mini" class="form-setting__select" @change="changeEditFlag"> <el-select v-model="settingObj.eventType" placeholder=" " size="mini" class="form-setting__select" @change="changeEditFlag">
<el-option <el-option
v-for="item in typeList" v-for="item in eventTypeList"
:key="item.value" :key="item.name"
:label="item.label" :label="item.name"
:value="item.value" :value="item.name"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<!--name--> <!--name-->
<el-form-item label="Name" prop="name" class="form-setting__block margin-b-20"> <el-form-item :label="$t('overall.name')" prop="name" class="form-setting__block margin-b-20">
<el-input <el-input
maxlength="64" maxlength="64"
show-word-limit show-word-limit
@@ -74,7 +78,7 @@
<!--Description--> <!--Description-->
<div class="form-setting__block margin-b-20"> <div class="form-setting__block margin-b-20">
<div class="block-title">Description</div> <div class="block-title">{{ $t('config.dataSet.description') }}</div>
<el-input <el-input
maxlength="255" maxlength="255"
show-word-limit show-word-limit
@@ -87,7 +91,7 @@
</el-form> </el-form>
<div class="form-setting__block margin-b-20"> <div class="form-setting__block margin-b-20">
<div class="block-title">Policy Status</div> <div class="block-title">{{ $t('detection.create.policyStatus') }}</div>
<el-switch <el-switch
v-model="settingObj.status" v-model="settingObj.status"
@change="changeEditFlag" @change="changeEditFlag"
@@ -95,11 +99,11 @@
inactive-color="#C0CEDB" inactive-color="#C0CEDB"
:active-value="1" :active-value="1"
:inactive-value="0" :inactive-value="0"
:active-text="switchStatus(settingObj.status)"/> :active-text="$t(switchStatus(settingObj.status))"/>
</div> </div>
<div class="form-setting__btn"> <div class="form-setting__btn">
<el-button @click="onContinue">Continue</el-button> <el-button @click="onContinue">{{ $t('detection.create.continue') }}</el-button>
</div> </div>
</div> </div>
</template> </template>
@@ -107,18 +111,24 @@
<script> <script>
import { detectionRuleType } from '@/utils/constants' import { detectionRuleType } from '@/utils/constants'
import { switchStatus } from '@/utils/tools' import { switchStatus } from '@/utils/tools'
import axios from 'axios' import { detectionUnitList } from '@/utils/static-data'
import axios from '_axios@0.21.4@axios'
import { api } from '@/utils/api' import { api } from '@/utils/api'
export default { export default {
name: 'GeneralSettings', name: 'GeneralSettings',
props: {
editObj: {
type: Object
}
},
data () { data () {
return { return {
detectionRuleType, detectionRuleType,
categoryList: [], categoryList: [],
typeList: [], eventTypeList: [],
settingObj: { settingObj: {
ruleType: detectionRuleType.threshold, ruleType: detectionRuleType.indicator,
category: '', category: '',
eventType: '', eventType: '',
name: '', name: '',
@@ -152,20 +162,46 @@ export default {
} }
} }
}, },
watch: {
editObj (newVal) {
if (newVal.ruleId) {
this.settingObj = JSON.parse(JSON.stringify({ ...newVal }))
this.settingObj.editFlag = false
this.settingObj.saveFlag = true
}
}
},
mounted () { mounted () {
this.initData() this.initData()
}, },
methods: { methods: {
switchStatus, switchStatus,
initData () { initData () {
axios.get(api.detection.statistics, { params: { pageSize: -1 } }).then(response => { this.categoryList = detectionUnitList.categoryList || []
this.eventTypeList = detectionUnitList.eventTypeList || []
axios.get(api.detection.statistics).then(response => {
if (response.status === 200) { if (response.status === 200) {
this.categoryList = response.data.data.categoryList || [] const data = response.data.data
this.typeList = response.data.data.typeList || [] if (data.categoryList) {
this.categoryList = data.categoryList
} else {
this.categoryList = []
}
if (data.eventTypeList) {
this.eventTypeList = data.eventTypeList
} else {
this.eventTypeList = []
}
} else { } else {
console.error(response.data) console.error(response.data)
this.categoryList = []
this.eventTypeList = []
} }
}).finally(() => { }).catch((e) => {
console.error(e)
this.categoryList = []
this.eventTypeList = []
}) })
}, },
selectMode (ruleType) { selectMode (ruleType) {
@@ -188,7 +224,3 @@ export default {
} }
} }
</script> </script>
<style lang="scss">
</style>

View File

@@ -9,8 +9,7 @@
<div class="key-search"> <div class="key-search">
<el-input v-model="searchKey" @keyup.enter="onSearch" size="mini" placeholder="Search for"> <el-input v-model="searchKey" @keyup.enter="onSearch" size="mini" placeholder="Search for">
<template #prefix> <template #prefix>
<!--todo 该图标名称错误已在iconfont修改后续记得改过来--> <i class="cn-icon cn-icon-search key-search-icon"></i>
<i class="cn-icon cn-icon-serach key-search-icon"></i>
</template> </template>
</el-input> </el-input>
@@ -24,7 +23,7 @@
<div class="key-table"> <div class="key-table">
<loading :loading="loading"></loading> <loading :loading="loading"></loading>
<el-table :data="tableData" style="width: 100%" @row-click="rowClick"> <el-table :data="tableData" style="width: 100%" :row-class-name="tableRowClassName" @row-click="rowClick">
<el-table-column <el-table-column
v-for="(item, index) in tableTitle" v-for="(item, index) in tableTitle"
:key="`col-${index}`" :key="`col-${index}`"
@@ -73,6 +72,9 @@ export default {
showDrawer: { showDrawer: {
type: Boolean, type: Boolean,
default: false default: false
},
delKeyId: {
type: String
} }
}, },
components: { components: {
@@ -116,6 +118,16 @@ export default {
this.myDrawer = this.showDrawer this.myDrawer = this.showDrawer
this.getTopKeysData() this.getTopKeysData()
}, },
watch: {
delKeyId (newVal) {
if (newVal) {
const obj = this.tableData.find(d => d.keyId === newVal)
if (obj) {
obj.filterKey = false
}
}
}
},
methods: { methods: {
unitConvert, unitConvert,
dateFormatByAppearance, dateFormatByAppearance,
@@ -150,6 +162,7 @@ export default {
}, },
/** 单击topKeys弹窗某一项 */ /** 单击topKeys弹窗某一项 */
rowClick (data) { rowClick (data) {
data.filterKey = true
this.$emit('keyRowClick', data) this.$emit('keyRowClick', data)
}, },
onRefresh () { onRefresh () {
@@ -161,6 +174,11 @@ export default {
httpError (e) { httpError (e) {
this.showError = true this.showError = true
this.errorMsg = this.errorMsgHandler(e) this.errorMsg = this.errorMsgHandler(e)
},
tableRowClassName (row) {
if (row.row.filterKey) {
return 'key-click-row'
}
} }
} }
} }
@@ -231,4 +249,8 @@ export default {
margin-left: 4px; margin-left: 4px;
} }
} }
.el-table .key-click-row {
background: #F5F7FA !important;
}
</style> </style>

View File

@@ -4,7 +4,7 @@
<div> <div>
<el-form ref="form" :model="thresholdRuleObj" label-position="top" :rules="rules"> <el-form ref="form" :model="thresholdRuleObj" label-position="top" :rules="rules">
<!--source--> <!--source-->
<el-form-item label="Source" prop="dataSource" class="form-setting__block margin-b-20"> <el-form-item :label="$t('config.user.source')" prop="dataSource" class="form-setting__block margin-b-20">
<el-select v-model="thresholdRuleObj.dataSource" placeholder=" " size="mini" class="form-setting__select"> <el-select v-model="thresholdRuleObj.dataSource" placeholder=" " size="mini" class="form-setting__select">
<el-option <el-option
v-for="item in sourceList" v-for="item in sourceList"
@@ -18,7 +18,7 @@
<!--Dimensions--> <!--Dimensions-->
<div class="form-setting__block margin-b-20"> <div class="form-setting__block margin-b-20">
<div class="block-title">Dimensions</div> <div class="block-title">{{ $t('detection.create.dimensions') }}</div>
<div class="block-dimension"> <div class="block-dimension">
<div class="block-dimension-tag" v-for="(ite, ind) in dimensionList" :key="ind">{{ ite.label }}</div> <div class="block-dimension-tag" v-for="(ite, ind) in dimensionList" :key="ind">{{ ite.label }}</div>
</div> </div>
@@ -52,7 +52,7 @@
size="mini" size="mini"
oninput="value=value.replace(/[^\d]/g,'')" oninput="value=value.replace(/[^\d]/g,'')"
v-model="item.value"></el-input> v-model="item.value"></el-input>
<i class="cn-icon cn-icon-close" @click="delFilterItem(index)"></i> <i class="cn-icon cn-icon-close" @click="delFilterItem(index, item)"></i>
</div> </div>
<div style="height: 10px;"></div> <div style="height: 10px;"></div>
@@ -65,7 +65,7 @@
<!--Condition模块--> <!--Condition模块-->
<div class="form-setting__block margin-b-20"> <div class="form-setting__block margin-b-20">
<div class="block-title">Condition</div> <div class="block-title">{{ $t('detection.create.condition') }}</div>
<el-form ref="form2" :model="thresholdRuleObj" label-position="top"> <el-form ref="form2" :model="thresholdRuleObj" label-position="top">
<div class="definition-condition-block" v-for="(item, index) in thresholdRuleObj.conditionData" :key="index"> <div class="definition-condition-block" v-for="(item, index) in thresholdRuleObj.conditionData" :key="index">
@@ -97,7 +97,7 @@
v-for="item in metricList" v-for="item in metricList"
:key="item.label" :key="item.label"
:label="item.label" :label="item.label"
:value="item.value" :value="item.label"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
@@ -144,7 +144,7 @@
</el-form> </el-form>
<div class="condition-add" @click="addCondition"> <div class="condition-add" @click="addCondition">
<i class="cn-icon cn-icon-add"></i>Add Condition <i class="cn-icon cn-icon-add"></i>{{ $t('detection.create.addCondition') }}
</div> </div>
</div> </div>
</div> </div>
@@ -154,6 +154,7 @@
<history-top-keys <history-top-keys
v-if="showDrawer" v-if="showDrawer"
:showDrawer="showDrawer" :showDrawer="showDrawer"
:delKeyId="delKeyId"
@closeDrawer="onCloseDrawer" @closeDrawer="onCloseDrawer"
@keyRowClick="getRowClick" @keyRowClick="getRowClick"
></history-top-keys> ></history-top-keys>
@@ -163,7 +164,7 @@
<div v-if="mySettingObj.ruleType===detectionRuleType.indicator"> <div v-if="mySettingObj.ruleType===detectionRuleType.indicator">
<el-form ref="form" :model="indicatorRuleObj" label-position="top" :rules="rules"> <el-form ref="form" :model="indicatorRuleObj" label-position="top" :rules="rules">
<!--Source--> <!--Source-->
<el-form-item label="Source" prop="dataSource" class="form-setting__block margin-b-20"> <el-form-item :label="$t('config.user.source')" prop="dataSource" class="form-setting__block margin-b-20">
<el-select v-model="indicatorRuleObj.dataSource" class="form-setting__select" placeholder=" " size="mini"> <el-select v-model="indicatorRuleObj.dataSource" class="form-setting__select" placeholder=" " size="mini">
<el-option <el-option
v-for="item in sourceList" v-for="item in sourceList"
@@ -175,13 +176,13 @@
</el-form-item> </el-form-item>
<!--Library--> <!--Library-->
<el-form-item label="Library" prop="knowledgeId" class="form-setting__block margin-b-20"> <el-form-item :label="$t('detection.library')" prop="knowledgeId" class="form-setting__block margin-b-20">
<el-select v-model="indicatorRuleObj.knowledgeId" class="form-setting__select" placeholder=" " size="mini"> <el-select v-model="indicatorRuleObj.name" class="form-setting__select" placeholder=" " size="mini">
<el-option <el-option
v-for="item in libraryList" v-for="item in libraryList"
:key="item.knowledgeId" :key="item.name"
:label="item.label" :label="item.name"
:value="item.knowledgeId" :value="item.name"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
@@ -207,22 +208,27 @@
</div> </div>
<div class="form-setting__btn"> <div class="form-setting__btn">
<el-button @click="onContinue">Continue</el-button> <el-button @click="onContinue">{{ $t('detection.create.continue') }}</el-button>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import axios from 'axios'
import { api } from '@/utils/api'
import HistoryTopKeys from '@/components/table/detection/HistoryTopKeys' import HistoryTopKeys from '@/components/table/detection/HistoryTopKeys'
import { eventSeverityColor, detectionRuleType } from '@/utils/constants' import { eventSeverityColor, detectionRuleType } from '@/utils/constants'
import { detectionUnitList } from '@/utils/static-data'
import axios from '_axios@0.21.4@axios'
import _ from 'lodash'
import { api } from '@/utils/api'
export default { export default {
name: 'RuleDefinition', name: 'RuleDefinition',
props: { props: {
settingObj: { settingObj: {
type: Object type: Object
},
editObj: {
type: Object
} }
}, },
components: { components: {
@@ -232,11 +238,26 @@ export default {
settingObj: { settingObj: {
immediate: true, immediate: true,
deep: true, deep: true,
handler (newVal, oldVal) { handler (newVal) {
if (!newVal.editFlag && newVal.saveFlag) { if (!newVal.editFlag && newVal.saveFlag) {
this.mySettingObj = JSON.parse(JSON.stringify(newVal)) this.mySettingObj = JSON.parse(JSON.stringify(newVal))
} }
} }
},
editObj: {
immediate: true,
deep: true,
handler (newVal) {
if (newVal) {
if (newVal.ruleType === detectionRuleType.indicator) {
this.indicatorRuleObj = this.$_.cloneDeep(newVal.ruleConfigObj)
this.indicatorRuleObj.knowledgeId = newVal.ruleConfigObj.knowledgeBase.knowledgeId
}
if (newVal.ruleType === detectionRuleType.threshold) {
this.thresholdRuleObj = this.$_.cloneDeep(newVal.ruleConfigObj)
}
}
}
} }
}, },
data () { data () {
@@ -244,7 +265,7 @@ export default {
eventSeverityColor, eventSeverityColor,
detectionRuleType, detectionRuleType,
mySettingObj: { mySettingObj: {
ruleType: detectionRuleType.threshold ruleType: detectionRuleType.indicator
}, },
dimensionList: [], // Dimensions数据 dimensionList: [], // Dimensions数据
// ruleType为Indicator时表单数据 // ruleType为Indicator时表单数据
@@ -327,7 +348,8 @@ export default {
than: '>', than: '>',
less: '<', less: '<',
equal: '=' equal: '='
} },
delKeyId: '' // 删除的keyId
} }
}, },
mounted () { mounted () {
@@ -341,18 +363,28 @@ export default {
}, },
methods: { methods: {
initData () { initData () {
axios.get(api.detection.statistics, { params: { pageSize: -1 } }).then(response => { this.sourceList = detectionUnitList.sourceList || []
if (response.status === 200) { this.levelList = detectionUnitList.levelList || []
this.sourceList = response.data.data.sourceList || [] // threshold模式还没确定所以数据暂时静态数据后续根据需要修改
this.levelList = response.data.data.levelList || [] this.conditionList = detectionUnitList.conditionList || []
this.conditionList = response.data.data.conditionList || [] this.metricList = detectionUnitList.metricList || []
this.metricList = response.data.data.metricList || []
this.libraryList = response.data.data.libraryList || [] if (this.mySettingObj.ruleType === this.detectionRuleType.indicator) {
} else { axios.get(api.knowledgeBaseList, { params: this.searchLabel }).then(response => {
console.error(response.data) if (response.status === 200) {
} this.libraryList = _.get(response, 'data.data.list', [])
}).finally(() => { } else {
}) this.libraryList = []
if (response.data.message) {
this.$message.error(response.data.message)
} else {
this.$message.error(this.$t('tip.somethingWentWrong'))
}
}
}).catch(() => {
this.libraryList = []
})
}
}, },
/** 单击History Top Keys列表某行filter添加数据 */ /** 单击History Top Keys列表某行filter添加数据 */
getRowClick (data) { getRowClick (data) {
@@ -364,6 +396,7 @@ export default {
}, },
/** filter模块点击add按钮 */ /** filter模块点击add按钮 */
addFilter (data) { addFilter (data) {
this.delKeyId = ''
this.showFilter = true this.showFilter = true
if (this.selectList.length === 0) { if (this.selectList.length === 0) {
this.getFilterList() this.getFilterList()
@@ -445,7 +478,8 @@ export default {
] ]
}, },
/** 删除filter某一项 */ /** 删除filter某一项 */
delFilterItem (i) { delFilterItem (i, item) {
this.delKeyId = item.keyId
this.thresholdRuleObj.filterList.splice(i, 1) this.thresholdRuleObj.filterList.splice(i, 1)
}, },
/** 点击继续,展开第三步 */ /** 点击继续,展开第三步 */

View File

@@ -110,64 +110,26 @@ export default {
if (this.listUrl) { if (this.listUrl) {
listUrl = this.listUrl listUrl = this.listUrl
} }
// todo 此段是为了避免mock没开启打开detection界面报错提示后续再开发detection时删除 axios.get(listUrl, { params: this.searchLabel }).then(response => {
if (listUrl === api.detection.list) { if (response.status === 200) {
const list = [] this.tableData = _.get(response, 'data.data.list', [])
for (let i = 0; i < 20; i++) { this.pageObj.total = _.get(response, 'data.data.total', 0)
const obj = { this.isNoData = !this.tableData || this.tableData.length === 0
ruleId: 100000 + i, } else {
ruleType: 'indicator_match', console.error(response)
status: 1,
name: 'name123',
category: 'Security Event',
eventType: 'C&C',
description: 'Built-in darkweb IoC',
ruleConfig: {
knowledge: {
name: 'VPN Server IP',
category: 'user_defined'
}
}
}
if (i % 2 === 0) {
obj.ruleType = 'threshold'
obj.ruleConfig = {
dimensions: 'Destination IP/CIDR'
}
obj.description = 'abuse.ch is providing community driven threat intelligence on \n' +
'cyber threats. It is the home of a couple of projects that are \n' +
'helping internet service providers and network operators protect …'
} else {
obj.status = 0
}
list.push(obj)
}
this.tableData = list
this.pageObj.total = list.length
this.loading = false
} else {
axios.get(listUrl, { params: this.searchLabel }).then(response => {
if (response.status === 200) {
this.tableData = _.get(response, 'data.data.list', [])
this.pageObj.total = _.get(response, 'data.data.total', 0)
this.isNoData = !this.tableData || this.tableData.length === 0
} else {
console.error(response)
this.isNoData = true
if (response.data.message) {
this.$message.error(response.data.message)
} else {
this.$message.error(this.$t('tip.somethingWentWrong'))
}
}
}).catch(() => {
this.isNoData = true this.isNoData = true
}).finally(() => { if (response.data.message) {
this.toggleLoading(false) this.$message.error(response.data.message)
this.loading = false } else {
}) this.$message.error(this.$t('tip.somethingWentWrong'))
} }
}
}).catch(() => {
this.isNoData = true
}).finally(() => {
this.toggleLoading(false)
this.loading = false
})
}, },
del (row) { del (row) {
this.$confirm(this.$t('tip.confirmDelete'), { this.$confirm(this.$t('tip.confirmDelete'), {
@@ -400,10 +362,23 @@ export default {
this.searchLabel.orderBy = orderBy this.searchLabel.orderBy = orderBy
this.getTableData() this.getTableData()
}, },
search (params) { search (params, flag, list) {
this.pageObj.pageNo = 1 this.pageObj.pageNo = 1
delete this.searchLabel.category if (flag !== 'detection') {
delete this.searchLabel.source delete this.searchLabel.category
delete this.searchLabel.source
}
if (list && list.length > 0) {
if (list.indexOf('status') > -1) {
delete this.searchLabel.status
}
if (list.indexOf('category') > -1) {
delete this.searchLabel.category
}
if (list.indexOf('eventType') > -1) {
delete this.searchLabel.eventType
}
}
this.getTableData(params) this.getTableData(params)
}, },
getTimeString () { getTimeString () {

View File

@@ -7,7 +7,7 @@ if (openMock) {
const list = [] const list = []
for (let i = 0; i < 20; i++) { for (let i = 0; i < 20; i++) {
const obj = { const obj = {
ruleId: 100000 + i, ruleId: 163 + i,
ruleType: 'indicator_match', ruleType: 'indicator_match',
status: 1, status: 1,
name: 'name123', name: 'name123',
@@ -50,51 +50,17 @@ if (openMock) {
Mock.mock(new RegExp(urlAndVersion + '/detection/statistics.*'), 'get', function (requestObj) { Mock.mock(new RegExp(urlAndVersion + '/detection/statistics.*'), 'get', function (requestObj) {
const data = { const data = {
statusList: [ statusList: [
{ status: 1 }, { status: 1, count: 34 },
{ status: 0 } { status: 0, count: 28 }
], ],
categoryList: [ categoryList: [
{ value: 'security', label: 'Security Event' }, { name: 'Security Event', count: 32 },
{ value: 'performance', label: 'Performance Event' }, { name: 'Performance Event', count: 28 }
{ value: 'regulatory_risk', label: 'Regulatory Risk Event' }
], ],
typeList: [ eventTypeList: [
{ value: 'c&c', label: 'C&C' }, { name: 'DDos', count: 15 },
{ value: 'ddos', label: 'DDos' }, { name: 'Lateral movement', count: 17 },
{ value: 'lateral_movement', label: 'Lateral movement' }, { name: 'Brute force', count: 12 }
{ value: 'brute_force', label: 'Brute force' }
],
sourceList: [
{ value: 'ip_metric', label: 'IP metric' },
{ value: 'performance_event', label: 'performance event' }
],
levelList: [
{ value: 'critical', label: 'Critical' },
{ value: 'high', label: 'High' },
{ value: 'medium', label: 'Medium' },
{ value: 'low', label: 'Low' },
{ value: 'info', label: 'Info' }
],
metricList: [
{ value: 'tcp_lostlen_ratio', label: 'Bits/second' },
{ value: 's2c_byte_retrans_ratio', label: 'Packets/second' },
{ value: 's2c_byte_retrans_ratio1', label: 'Sessions/second' }
],
conditionList: [
{ value: 'than', label: 'Greater Than' },
{ value: 'less', label: 'Greater Less' },
{ value: 'equal', label: 'Greater Equal' }
],
libraryList: [
{ value: 'library name2', knowledgeId: '101', label: 'Library name' },
{ value: 'library name1', knowledgeId: '102', label: 'Library name1' },
{ value: 'library name2', knowledgeId: '103', label: 'Library name2' }
],
intervalList: [
{ value: 'minutes', label: 'minutes' },
{ value: 'hours', label: 'hours' },
{ value: 'days', label: 'days' },
{ value: 'weeks', label: 'weeks' }
] ]
} }
@@ -129,28 +95,50 @@ if (openMock) {
const ruleId = getLastValue(requestObj.url) const ruleId = getLastValue(requestObj.url)
const data = { const data = {
name: 'name123', name: 'name123',
category: 'Security Event', category: 'security_event',
ruleType: 'indicator_match', ruleType: 'indicator_match',
eventType: 'C&C', eventType: 'C&C',
description: 'Built-in darkweb IoC', description: 'Built-in darkweb IoC',
status: 1, status: 1,
ruleConfig: { ruleConfig: {
dataSource: 'VPN Server IP', dataSource: 'VPN Server IP',
knowledgeId: 10, knowledgeBase: {
level: 10 knowledgeId: 10,
name: 'cn_ioc_darkweb',
category: 'websketch',
source: 'cn_ioc_darkweb'
},
level: 'critical'
}, },
trigger: { ruleConfigObj: {
dataSource: 'VPN Server IP',
knowledgeBase: {
knowledgeId: '101',
name: 'cn_ioc_darkweb',
category: 'websketch',
source: 'cn_ioc_darkweb'
},
level: 'critical'
},
ruleTrigger: {
atLeast: 1,
interval: 'PT5M',
resetInterval: 'PT10M'
},
ruleTriggerObj: {
atLeast: 1, atLeast: 1,
interval: 'PT5M', interval: 'PT5M',
resetInterval: 'PT10M' resetInterval: 'PT10M'
} }
} }
data.ruleConfig = JSON.stringify(data.ruleConfig)
data.trigger = JSON.stringify(data.trigger)
if (ruleId % 2 === 0) { if (ruleId % 2 === 0) {
data.ruleType = 'threshold' data.ruleType = 'threshold'
data.status = 1
} else {
data.status = 0 data.status = 0
} else {
data.status = 1
} }
return { return {

View File

@@ -103,6 +103,10 @@ const routes = [
{ {
path: '/detection/policies/create', path: '/detection/policies/create',
component: () => import('@/views/detectionsNew/DetectionForm') component: () => import('@/views/detectionsNew/DetectionForm')
},
{
path: '/detection/policies/edit',
component: () => import('@/views/detectionsNew/DetectionForm')
} }
] ]
} }

View File

@@ -139,13 +139,13 @@ export const api = {
}, },
list: apiVersion + '/rule/detection/list', // 检测规则列表 list: apiVersion + '/rule/detection/list', // 检测规则列表
detail: apiVersion + '/rule/detection', // 检测规则详情 detail: apiVersion + '/rule/detection', // 检测规则详情
delete: apiVersion + '/rule', // 检测规则删除 delete: apiVersion + '/rule/detection', // 检测规则删除
// 获取单位列表如source、type、metric等 // 获取单位列表如source、type、metric等
statistics: apiVersion + '/detection/statistics', statistics: apiVersion + '/rule/detection/statistics',
// 规则新建模块 // 规则新建模块
create: { create: {
topKeys: apiVersion + '/detection/topKeys', // topKeys列表 topKeys: apiVersion + '/detection/topKeys', // topKeys列表
create: apiVersion + '/rule/detection/create' // todo 规则新建编辑此api为模拟后续需要修改 create: apiVersion + '/rule/detection'
} }
}, },
// Dashboard // Dashboard

View File

@@ -2479,8 +2479,8 @@ export const psiphon3IpType = [
] ]
// detection新增页的第一步选择mode // detection新增页的第一步选择mode
export const detectionRuleType = { export const detectionRuleType = {
indicator: 'Indicator Match', indicator: 'indicator_match',
threshold: 'Threshold' threshold: 'threshold'
} }
// 顶级域名列表 // 顶级域名列表

View File

@@ -137,3 +137,73 @@ export const xAxisTimeRich = {
fontWeight: 'bold' fontWeight: 'bold'
} }
} }
function switchDateTypeByStr (str) {
switch (str) {
case 'Y':
return 'years'
case 'M':
return 'months'
case 'W':
return 'weeks'
case 'D':
return 'days'
case 'H':
return 'hours'
case 'm':
return 'minutes'
case 'S':
return 'seconds'
}
}
function switchValueByDateType (str) {
switch (str) {
case 'years':
return 'Y'
case 'months':
return 'M'
case 'weeks':
return 'W'
case 'days':
return 'D'
case 'hours':
return 'H'
case 'minutes':
return 'm'
case 'seconds':
return 'S'
}
}
export function getTimeByDurations (str) {
if (str && str.indexOf('P') === 0) {
const obj = {
type: '',
value: ''
}
for (let i = 1; i < str.length; i++) {
const item = str[i]
// P5M表示持续5个月PT5M持续5分钟T是位于时间分量之前的时间指示符即T之前是年月周日T之后是时分秒
if (isNaN(item) && item !== 'T') {
if (item === 'M' && i < str.indexOf('T')) {
obj.type = switchDateTypeByStr('m')
} else {
obj.type = switchDateTypeByStr(item)
}
} else if (!isNaN(item)) {
obj.value += item
}
}
return obj
}
}
export function getDurationsTimeByType (number, type) {
let T = ''
if (['hours', 'minutes', 'seconds'].indexOf(type) > -1) {
T = 'T'
}
return `P${T}${number}${switchValueByDateType(type)}`
}

View File

@@ -350,3 +350,53 @@ export const connectionList = [
// label: 'OR' // label: 'OR'
// } // }
] ]
export const detectionUnitList = {
statusList: [
{ status: 1 },
{ status: 0 }
],
categoryList: [
{ value: 'security_event', label: 'Security Event' },
{ value: 'performance_event', label: 'Performance Event' }
],
eventTypeList: [
{ value: 'ddos', label: 'DDos' },
{ value: 'lateral_movement', label: 'Lateral movement' },
{ value: 'brute_force', label: 'Brute force' }
],
sourceList: [
{ value: 'session_record', label: 'session_record' }
],
levelList: [
{ value: 'critical', label: 'Critical' },
{ value: 'high', label: 'High' },
{ value: 'medium', label: 'Medium' },
{ value: 'low', label: 'Low' },
{ value: 'info', label: 'Info' }
],
metricList: [
{ value: 'tcp_lostlen_ratio', label: 'Bits/second' },
{ value: 's2c_byte_retrans_ratio', label: 'Packets/second' },
{ value: 's2c_byte_retrans_ratio1', label: 'Sessions/second' }
],
conditionList: [
{ value: 'than', label: 'Greater Than' },
{ value: 'less', label: 'Greater Less' },
{ value: 'equal', label: 'Greater Equal' }
],
libraryList: [
{ value: 'library name', knowledgeId: 7, label: 'Library name' },
{ value: 'library name1', knowledgeId: 8, label: 'Library name1' },
{ value: 'library name2', knowledgeId: 9, label: 'Library name2' }
],
intervalList: [
{ value: 'year', label: 'years' },
{ value: 'months', label: 'months' },
{ value: 'weeks', label: 'weeks' },
{ value: 'days', label: 'days' },
{ value: 'hours', label: 'hours' },
{ value: 'minutes', label: 'minutes' },
{ value: 'seconds', label: 'seconds' }
]
}

View File

@@ -1320,9 +1320,9 @@ export function numberWithCommas (num) {
export function switchStatus (status) { export function switchStatus (status) {
switch (status) { switch (status) {
case 0: case 0:
return 'Disabled' return 'detection.create.disabled'
case 1: case 1:
return 'Enabled' return 'detection.create.enabled'
} }
} }

View File

@@ -3,62 +3,62 @@
<div class="drawer-basic"> <div class="drawer-basic">
<div class="drawer-basic-header"> <div class="drawer-basic-header">
<div class="drawer-basic-id">ID: {{ drawerInfo.ruleId }}</div> <div class="drawer-basic-id">ID: {{ drawerInfo.ruleId }}</div>
<div :class="`detection-tag-status${detailData.status}`"> <div :class="`detection-tag-status${drawerInfo.status}`">
{{ switchStatus(detailData.status) }} {{ $t(switchStatus(drawerInfo.status)) }}
</div> </div>
</div> </div>
<div class="drawer-basic-function"> <div class="drawer-basic-function">
<div class="detection-drawer-title">Name</div> <div class="detection-drawer-title">{{ $t('overall.name') }}</div>
<div class="basic-function-value">{{ detailData.name }}</div> <div class="basic-function-value">{{ detailData.name }}</div>
</div> </div>
<div class="drawer-basic-function"> <div class="drawer-basic-function">
<div class="detection-drawer-title">Type</div> <div class="detection-drawer-title">{{ $t('overall.type') }}</div>
<div class="basic-function-value">{{ detailData.eventType }}</div> <div class="basic-function-value">{{ detailData.eventType }}</div>
</div> </div>
<div class="drawer-basic-description"> <div class="drawer-basic-description">
<div class="detection-drawer-title">Description</div> <div class="detection-drawer-title">{{ $t('config.dataSet.description') }}</div>
<div class="basic-description-value">{{ detailData.description }}</div> <div class="basic-description-value">{{ detailData.description }}</div>
</div> </div>
</div> </div>
<div class="detection-drawer-collapse"> <div class="detection-drawer-collapse">
<el-collapse v-model="activeRule"> <el-collapse v-model="activeRule">
<el-collapse-item title="Rule Definitcm" name="rule"> <el-collapse-item :title="$t('detection.ruleDefinition')" name="rule">
<div class="drawer-collapse-content"> <div class="drawer-collapse-content">
<div class="drawer-basic-function"> <div class="drawer-basic-function">
<div class="detection-drawer-title">Source</div> <div class="detection-drawer-title">{{ $t('config.user.source') }}</div>
<div class="basic-function-value">{{ detailData.category }}</div> <div class="basic-function-value">{{ detailData.category }}</div>
</div> </div>
<div v-if="detailData.ruleType==='indicator_match'"> <div v-if="detailData.ruleType==='indicator_match'">
<div class="drawer-basic-function"> <div class="drawer-basic-function">
<div class="detection-drawer-title">Library</div> <div class="detection-drawer-title">{{ $t('detection.library') }}</div>
<span class="basic-function-value">{{ detailData.library }}</span> <span class="basic-function-value">{{ detailData.ruleConfigObj.knowledgeBase.name }}</span>
</div> </div>
<div class="drawer-basic-function"> <div class="drawer-basic-function">
<div class="detection-drawer-title">Level</div> <div class="detection-drawer-title">{{ $t('detection.level') }}</div>
<div class="detection-drawer-title"> <div class="detection-drawer-title">
<div class="detection__icon" :style="`background-color: ${eventSeverityColor['critical']}`"></div> <div class="detection__icon" :style="`background-color: ${eventSeverityColor[detailData.ruleConfigObj.level]}`"></div>
<div class="basic-function-value">Critical</div> <div class="basic-function-value">{{ detailData.ruleConfigObj.level }}</div>
</div> </div>
</div> </div>
</div> </div>
<div v-else> <div v-else>
<div class="drawer-basic-function"> <div class="drawer-basic-function">
<div class="detection-drawer-title">Dimensions</div> <div class="detection-drawer-title">{{ $t('detection.create.dimensions') }}</div>
<span class="detection-tag-blue">{{ detailData.dimensions }}</span> <span class="detection-tag-blue">{{ detailData.dimensions }}</span>
</div> </div>
<div class="drawer-basic-function"> <div class="drawer-basic-function">
<div class="detection-drawer-title">Filters</div> <div class="detection-drawer-title">{{ $t('detections.filters') }}</div>
<span class="detection-tag-blue">source</span> <span class="detection-tag-blue">Source Port</span>
<span style="margin: 0 6px;">equal</span><span>19890</span> <span style="margin: 0 6px;">{{ $t('detections.equal') }}</span><span>19890</span>
</div> </div>
<div class="drawer-basic-function" v-for="item in severityList" :key="item.severity" <div class="drawer-basic-function" v-for="item in severityList" :key="item.severity"
@@ -67,7 +67,7 @@
<div class="detection__icon" :style="`background-color: ${eventSeverityColor[item.severity]}`"></div> <div class="detection__icon" :style="`background-color: ${eventSeverityColor[item.severity]}`"></div>
<div>{{ toUpperCaseByString(item.severity) }}</div> <div>{{ toUpperCaseByString(item.severity) }}</div>
</div> </div>
<div class="detection-drawer-title">Conditions</div> <div class="detection-drawer-title">{{ $t('detections.conditions') }}</div>
<div> <div>
<div class="detection-tag-gray margin-r-10">> 60 Kpackets/s</div> <div class="detection-tag-gray margin-r-10">> 60 Kpackets/s</div>
<div class="detection-tag-gray">> 50 Unique Src IPs</div> <div class="detection-tag-gray">> 50 Unique Src IPs</div>
@@ -81,22 +81,25 @@
<div class="detection-drawer-collapse" style="margin: 20px 0"> <div class="detection-drawer-collapse" style="margin: 20px 0">
<el-collapse v-model="activeTrigger"> <el-collapse v-model="activeTrigger">
<el-collapse-item title="Trigger" name="trigger"> <el-collapse-item :title="$t('detection.create.trigger')" name="trigger">
<div class="drawer-collapse-content"> <div class="drawer-collapse-content">
<div class="drawer-collapse-trigger"> <div class="drawer-collapse-trigger">
Triggered when conditions occur at least Triggered when conditions occur at least
<span style="color: #046ECA" v-if="detailData.trigger"> <span style="color: #046ECA">
{{ detailData.trigger.atLeast }} time {{ $_.get(detailData, 'ruleTriggerObj.atLeast', '-') || '-' }} time
</span> in </span> in
<span style="color: #046ECA" v-if="detailData.trigger"> <span style="color: #046ECA">
<!--todo 此处返回的是PT5M具体时间处理根据后续字段来看--> {{ getNumberFromStr($_.get(detailData, 'ruleTriggerObj.interval', '0')) || '-' }}
{{ getNumberFromStr(detailData.trigger.interval) }} minutes {{ $_.get(detailData, 'ruleTriggerObj.intervalVal', '-') || '-' }}
</span> </span>
</div> </div>
<div class="drawer-basic-function"> <div class="drawer-basic-function">
<div class="detection-drawer-title">Evaluation Frequency</div> <div class="detection-drawer-title">{{ $t('detection.evaluationFrequency') }}</div>
<div class="basic-function-value" v-if="detailData.trigger">{{ getNumberFromStr(detailData.trigger.resetInterval) }} minutes</div> <div class="drawer-trigger-minutes">
{{ getNumberFromStr($_.get(detailData, 'ruleTriggerObj.resetInterval', '0')) || '-' }}
{{ $_.get(detailData, 'ruleTriggerObj.intervalVal', '-') || '-' }}
</div>
</div> </div>
</div> </div>
</el-collapse-item> </el-collapse-item>
@@ -156,7 +159,8 @@ export default {
axios.get(`${api.detection.detail}/${this.drawerInfo.ruleId}`).then(res => { axios.get(`${api.detection.detail}/${this.drawerInfo.ruleId}`).then(res => {
if (res.status === 200) { if (res.status === 200) {
this.detailData = res.data.data const data = res.data.data
this.detailData = data
} }
}).catch(err => { }).catch(err => {
console.error(err) console.error(err)

View File

@@ -8,8 +8,8 @@
<div class="new-filter-content-title">{{ $t('overall.status') }}</div> <div class="new-filter-content-title">{{ $t('overall.status') }}</div>
<div class="new-filter-content-content"> <div class="new-filter-content-content">
<el-checkbox-group v-model="checkStatus" @change="onChangeCategory" style="display: flex;flex-direction: column"> <el-checkbox-group v-model="checkStatus" @change="onChangeCategory" style="display: flex;flex-direction: column">
<el-checkbox v-for="item in statusList" :key="item.label" class="new-filter-content-checkbox" :label="item.status"> <el-checkbox v-for="item in statusList" :key="item.name" class="new-filter-content-checkbox" :label="item.status">
<div>{{ item.label }}</div> <div>{{ item.name }}</div>
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
</div> </div>
@@ -19,8 +19,8 @@
<div class="new-filter-content-title">{{ $t('overall.category') }}</div> <div class="new-filter-content-title">{{ $t('overall.category') }}</div>
<div class="new-filter-content-content"> <div class="new-filter-content-content">
<el-checkbox-group v-model="checkCategory" @change="onChangeCategory"> <el-checkbox-group v-model="checkCategory" @change="onChangeCategory">
<el-checkbox v-for="item in categoryList" :key="item.value" class="new-filter-content-checkbox" :label="item.value"> <el-checkbox v-for="item in categoryList" :key="item.name" class="new-filter-content-checkbox" :label="item.name">
{{ item.label }} {{ item.name }}
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
</div> </div>
@@ -29,9 +29,9 @@
<div> <div>
<div class="new-filter-content-title">{{ $t('overall.type') }}</div> <div class="new-filter-content-title">{{ $t('overall.type') }}</div>
<div class="new-filter-content-content"> <div class="new-filter-content-content">
<el-checkbox-group v-model="checkType" @change="onChangeCategory" style="display: flex;flex-direction: column"> <el-checkbox-group v-model="checkEventType" @change="onChangeCategory" style="display: flex;flex-direction: column">
<el-checkbox v-for="item in typeList" :key="item.value" class="new-filter-content-checkbox" :label="item.value"> <el-checkbox v-for="item in eventTypeList" :key="item.name" class="new-filter-content-checkbox" :label="item.name">
{{ item.label }} {{ item.name }}
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
</div> </div>
@@ -43,100 +43,76 @@
<script> <script>
import axios from 'axios' import axios from 'axios'
import { api } from '@/utils/api' import { api } from '@/utils/api'
import { switchStatus } from '@/utils/tools'
export default { export default {
name: 'DetectionFilter', name: 'DetectionFilter',
data () { data () {
return { return {
statusList: [], statusList: [], // 状态列表数据
categoryList: [], categoryList: [], // 类别列表
typeList: [], eventTypeList: [], // 事件类型列表
checkStatus: [], checkStatus: [], // checkbox选择的状态列表
checkCategory: [], checkCategory: [], // checkbox选择的类别
checkType: [], checkEventType: [], // checkbox选择的事件类型
url: api.detection.statistics policyTotal: 0, // 策略总条数,与筛选条件无关
policyEnabledNum: 0 // 策略中状态为enabled的数量
} }
}, },
mounted () { mounted () {
// 开发时删除---start this.getFilterData()
this.statusList = [
{ status: 1, label: 'Enabled' },
{ status: 0, label: 'Disabled' }
]
this.checkStatus = [0, 1]
this.categoryList = [
{ value: 'security', label: 'Security Event' },
{ value: 'performance', label: 'Performance Event' },
{ value: 'regulatory_risk', label: 'Regulatory Risk Event' }
]
this.checkCategory = ['security', 'performance', 'regulatory_risk']
this.typeList = [
{ value: 'c&c', label: 'C&C' },
{ value: 'ddos', label: 'DDos' },
{ value: 'lateral_movement', label: 'Lateral movement' },
{ value: 'brute_force', label: 'Brute force' }
]
this.checkType = ['c&c', 'ddos', 'lateral_movement', 'brute_force']
// 开发时删除---end
// todo 暂时禁用,后续再开发时解禁
// this.getFilterData()
}, },
methods: { methods: {
getFilterData (params) { getFilterData () {
let searchParams = { pageSize: -1 } axios.get(api.detection.statistics).then(response => {
if (params) {
searchParams = { ...searchParams, ...params }
}
axios.get(this.url, { params: searchParams }).then(response => {
if (response.status === 200) { if (response.status === 200) {
if (response.data.data.statusList) { const data = response.data.data
if (data.statusList) {
data.statusList.forEach(item => {
this.policyTotal += item.count
if (item.status === 1) {
this.policyEnabledNum = item.count
}
this.statusList.push({ status: item.status, name: this.$t(switchStatus(item.status)) })
})
this.$emit('policyTotal', this.policyTotal, this.policyEnabledNum)
} else {
this.statusList = [] this.statusList = []
response.data.data.statusList.forEach(item => {
this.statusList.push({ status: item.status, label: this.switchStatus(item.status) })
this.checkStatus.push(item.status)
})
} }
this.categoryList = response.data.data.categoryList if (data.categoryList) {
if (response.data.data.categoryList) { this.categoryList = data.categoryList
response.data.data.categoryList.forEach(item => { } else {
this.checkCategory.push(item.value) this.categoryList = []
})
} }
this.typeList = response.data.data.typeList if (data.eventTypeList) {
if (response.data.data.typeList) { this.eventTypeList = data.eventTypeList
response.data.data.typeList.forEach(item => { } else {
this.checkType.push(item.value) this.eventTypeList = []
})
} }
} else { } else {
console.error(response.data) console.error(response.data)
} }
}).finally(() => { }).catch((e) => {
// this.initTypeData() console.error(e)
// this.initStatusData() this.statusList = []
// const self = this this.categoryList = []
// this.$nextTick(() => { this.eventTypeList = []
// if (self.$refs.knowledgeTreeTypeFilter) {
// self.$refs.knowledgeTreeTypeFilter.setCheckedKeys(this.defaultCheckedCategory)
// }
// if (self.$refs.knowledgeTreeStatusFilter) {
// self.$refs.knowledgeTreeStatusFilter.setCheckedKeys(this.defaultCheckedStatus)
// }
// })
}) })
}, },
onChangeCategory (data) { onChangeCategory () {
// todo 暂时禁用,后续再开发时解禁 const obj = {}
// 根据选择的值,构造不同入参,传给列表页,调用查询列表接口 if (this.checkStatus.length > 0) {
}, obj.status = this.checkStatus.join(',')
switchStatus (status) {
switch (status) {
case 0: return 'Disabled'
case 1: return 'Enabled'
} }
if (this.checkCategory.length > 0) {
obj.category = this.checkCategory.join(',')
}
if (this.checkEventType.length > 0) {
obj.eventType = this.checkEventType.join(',')
}
this.$emit('filterParams', obj)
} }
} }
} }

View File

@@ -1,8 +1,9 @@
<template> <template>
<div class="detection-form"> <div class="detection-form">
<loading :loading="myLoading"></loading>
<div class="detection-form-header"> <div class="detection-form-header">
Create Alert Policies {{ $t('detection.createEventPolicies') }}
</div> </div>
<!--第一步General Settings--> <!--第一步General Settings-->
@@ -13,11 +14,11 @@
<template #title> <template #title>
<div class="form-collapse-header"> <div class="form-collapse-header">
<div :class="activeNames[0]==='1' ? 'form-collapse-header-no-active' : 'form-collapse-header-no'">1</div> <div :class="activeNames[0]==='1' ? 'form-collapse-header-no-active' : 'form-collapse-header-no'">1</div>
<div class="form-collapse-header-title">General Settings</div> <div class="form-collapse-header-title">{{ $t('detection.create.generalSettings') }}</div>
</div> </div>
</template> </template>
<div class="form-collapse-content"> <div class="form-collapse-content">
<general-settings ref="form" @setSettingForm="getFormSetting" /> <general-settings ref="form" :editObj="editObj" @setSettingForm="getFormSetting" />
</div> </div>
</el-collapse-item> </el-collapse-item>
</el-collapse> </el-collapse>
@@ -30,11 +31,11 @@
<template #title> <template #title>
<div class="form-collapse-header"> <div class="form-collapse-header">
<div :class="activeNames[0]==='2' ? 'form-collapse-header-no-active' : 'form-collapse-header-no'">2</div> <div :class="activeNames[0]==='2' ? 'form-collapse-header-no-active' : 'form-collapse-header-no'">2</div>
<div class="form-collapse-header-title">Rule Definition</div> <div class="form-collapse-header-title">{{ $t('detection.create.ruleDefinition') }}</div>
</div> </div>
</template> </template>
<div class="form-collapse-content"> <div class="form-collapse-content">
<rule-definition :settingObj="settingObj" @setRuleObj="getRuleObj" /> <rule-definition :settingObj="settingObj" :editObj="editObj" @setRuleObj="getRuleObj" />
</div> </div>
</el-collapse-item> </el-collapse-item>
</el-collapse> </el-collapse>
@@ -47,7 +48,7 @@
<template #title> <template #title>
<div class="form-collapse-header"> <div class="form-collapse-header">
<div :class="activeNames[0]==='3' ? 'form-collapse-header-no-active' : 'form-collapse-header-no'">3</div> <div :class="activeNames[0]==='3' ? 'form-collapse-header-no-active' : 'form-collapse-header-no'">3</div>
<div class="form-collapse-header-title">Trigger</div> <div class="form-collapse-header-title">{{ $t('detection.create.trigger') }}</div>
</div> </div>
</template> </template>
@@ -78,15 +79,18 @@
<div class="trigger-block-item"> <div class="trigger-block-item">
<div>With the counter resetting after no activity for</div> <div>With the counter resetting after no activity for</div>
<el-form-item prop="minute"> <el-form-item prop="resetInterval">
<el-input size="mini" v-model="triggerObj.minute" oninput="value=value.replace(/[^\d]/g,'')"></el-input> <el-input size="mini" v-model="triggerObj.resetInterval" oninput="value=value.replace(/[^\d]/g,'')"></el-input>
</el-form-item> </el-form-item>
<div>minutes</div> <div>{{ triggerObj.intervalVal }}</div>
</div> </div>
</el-form> </el-form>
<div class="form-setting__btn1"> <div class="form-setting__btn1">
<el-button @click="createPolicy">Create & enable rule</el-button> <div class="btn1">
<el-button @click="createPolicy">{{ $t('overall.create') }}</el-button>
</div>
<el-button @click="createPolicy('enabled')">{{ $t('overall.create') }} & {{ $t('detection.create.enablePolicy') }}</el-button>
</div> </div>
</div> </div>
</el-collapse-item> </el-collapse-item>
@@ -101,23 +105,17 @@ import GeneralSettings from '@/components/table/detection/GeneralSettings'
import RuleDefinition from '@/components/table/detection/RuleDefinition' import RuleDefinition from '@/components/table/detection/RuleDefinition'
import { api } from '@/utils/api' import { api } from '@/utils/api'
import axios from 'axios' import axios from 'axios'
import _ from 'lodash' import { useRoute } from 'vue-router'
import { detectionUnitList } from '@/utils/static-data'
import { ref } from 'vue'
import { getDurationsTimeByType, getTimeByDurations } from '@/utils/date-util'
import Loading from '@/components/common/Loading'
export default { export default {
name: 'DetectionForm', name: 'DetectionForm',
data () { data () {
return { return {
activeNames: ['1'], activeNames: ['1'],
settingObj: {}, // General Settings即第一步的form表单信息
ruleObj: {}, // 第二步的form表单信息
// 第三步的form表单信息
triggerObj: {
atLeast: '',
interval: '',
intervalVal: '',
minute: '',
finishFlag: false
},
rules: { rules: {
atLeast: [ atLeast: [
{ {
@@ -130,7 +128,7 @@ export default {
{ {
required: true, required: true,
message: this.$t('validate.required'), message: this.$t('validate.required'),
trigger: 'change' trigger: 'blur'
} }
], ],
intervalVal: [ intervalVal: [
@@ -140,7 +138,7 @@ export default {
trigger: 'change' trigger: 'change'
} }
], ],
minute: [ resetInterval: [
{ {
required: true, required: true,
message: this.$t('validate.required'), message: this.$t('validate.required'),
@@ -148,27 +146,80 @@ export default {
} }
] ]
}, },
intervalList: [] intervalList: detectionUnitList.intervalList || [],
editObj: {}
} }
}, },
components: { components: {
GeneralSettings, GeneralSettings,
RuleDefinition RuleDefinition,
Loading
}, },
mounted () { mounted () {
this.getStatistics() this.getDetailInfo()
},
setup () {
const { query } = useRoute()
const ruleId = ref(query.id || '')
const pageNoForTable = ref(query.pageNoForTable || 1)
const myLoading = ref(!!ruleId.value)
// General Settings即第一步的form表单信息
const settingObj = ref({})
// 第二步的form表单信息
const ruleObj = ref({})
// 第三步的form表单信息
const triggerObj = ref({
atLeast: '',
interval: '',
intervalVal: '',
resetInterval: '',
finishFlag: false
})
return {
ruleId,
myLoading,
pageNoForTable,
settingObj,
ruleObj,
triggerObj
}
}, },
methods: { methods: {
/** 获取下拉列表数据 */ /** 编辑时获取详情 */
getStatistics () { getDetailInfo () {
axios.get(api.detection.statistics, { pageSize: -1 }).then(response => { if (this.ruleId) {
if (response.status === 200) { axios.get(`${api.detection.detail}/${this.ruleId}`).then(response => {
this.intervalList = _.get(response, 'data.data.intervalList', []) if (response.status === 200) {
} else { if (!response.data.data) {
console.error(response.data) throw new Error('No data found, id: ' + this.ruleId)
} }
}).finally(() => { this.myLoading = false
}) this.editObj = { ...response.data.data, ruleId: this.ruleId }
this.settingObj = this.$_.cloneDeep(this.editObj)
this.settingObj.editFlag = false
this.settingObj.saveFlag = true
this.ruleObj = this.$_.cloneDeep(this.editObj.ruleConfigObj)
this.triggerObj = this.$_.cloneDeep(this.editObj.ruleTriggerObj)
this.triggerObj.intervalVal = getTimeByDurations(this.triggerObj.interval).type
this.triggerObj.interval = getTimeByDurations(this.triggerObj.interval).value
this.triggerObj.resetInterval = getTimeByDurations(this.triggerObj.resetInterval).value
this.activeNames = ['1', '2', '3']
} else {
console.error(response.data)
}
}).catch(e => {
console.error(e)
this.$message.error(this.errorMsgHandler(e))
this.$router.push({
path: '/detection/policies',
query: {
pageNo: this.pageNoForTable ? Number(this.pageNoForTable) : 1,
t: +new Date()
}
})
})
}
}, },
/** 获取General Settings折叠板form数据 */ /** 获取General Settings折叠板form数据 */
getFormSetting (data) { getFormSetting (data) {
@@ -190,7 +241,7 @@ export default {
}) })
}, },
/** 创建policy */ /** 创建policy */
createPolicy () { createPolicy (flag) {
const settingLen = Object.keys(this.settingObj).length const settingLen = Object.keys(this.settingObj).length
const ruleLen = Object.keys(this.ruleObj).length const ruleLen = Object.keys(this.ruleObj).length
@@ -198,36 +249,73 @@ export default {
this.$refs.form3.validate(valid => { this.$refs.form3.validate(valid => {
if (valid) { if (valid) {
// 最终提交form // 最终提交form
// const formObj = { ...this.settingObj, ...this.ruleObj, ...this.triggerObj } const formObj = this.$_.cloneDeep({ ...this.settingObj, ruleConfig: JSON.stringify(this.ruleObj), ruleTrigger: this.triggerObj })
this.$message({ if (flag) {
duration: 2000, formObj.status = 1
type: 'success', }
message: this.$t('tip.saveSuccess') // 将时间转为参数所需如5分钟转为PT5
}) formObj.ruleTrigger.resetInterval = getDurationsTimeByType(formObj.ruleTrigger.resetInterval, formObj.ruleTrigger.intervalVal)
formObj.ruleTrigger.interval = getDurationsTimeByType(formObj.ruleTrigger.interval, formObj.ruleTrigger.intervalVal)
formObj.ruleTrigger = JSON.stringify(formObj.ruleTrigger)
// 删除多余参数
delete formObj.ruleConfigObj
delete formObj.ruleTriggerObj
delete formObj.editFlag
delete formObj.saveFlag
// axios.post('api', formObj).then(response => { if (!this.ruleId) {
// if (response.status === 200) { // post调用是新增put是编辑
// this.$message({ this.myLoading = true
// duration: 2000, axios.post(api.detection.create.create, formObj).then(response => {
// type: 'success', if (response.status === 200) {
// message: this.$t('tip.saveSuccess') this.$message({
// }) duration: 2000,
// type: 'success',
// this.$router.push({ message: this.$t('tip.saveSuccess')
// path: '/detectionNew', })
// query: {
// t: +new Date() this.$router.push({
// } path: '/detectionsNew',
// }) query: {
// } else { t: +new Date()
// this.$message.error(this.errorMsgHandler(response)) }
// } })
// }).catch(e => { } else {
// console.error(e) this.$message.error(this.errorMsgHandler(response))
// this.$message.error(this.errorMsgHandler(e)) }
// }).finally(() => { }).catch(e => {
// // console.error(e)
// }) this.$message.error(this.errorMsgHandler(e))
}).finally(() => {
this.myLoading = false
})
} else {
this.myLoading = true
axios.put(api.detection.create.create, formObj).then(response => {
if (response.status === 200) {
this.$message({
duration: 2000,
type: 'success',
message: this.$t('tip.saveSuccess')
})
this.$router.push({
path: '/detectionsNew',
query: {
pageNo: self.pageNoForTable ? Number(self.pageNoForTable) : 1,
t: +new Date()
}
})
} else {
this.$message.error(this.errorMsgHandler(response))
}
}).catch(e => {
console.error(e)
this.$message.error(this.errorMsgHandler(e))
}).finally(() => {
this.myLoading = false
})
}
} }
}) })
} else if (settingLen === 0) { } else if (settingLen === 0) {

View File

@@ -50,7 +50,7 @@
</template> </template>
<template v-else-if="item.prop === 'status'"> <template v-else-if="item.prop === 'status'">
<div :class="`detection-tag-status${scope.row[item.prop]}`"> <div :class="`detection-tag-status${scope.row[item.prop]}`">
{{ switchStatus(scope.row[item.prop]) }} {{ $t(switchStatus(scope.row[item.prop])) }}
</div> </div>
</template> </template>
<template v-else-if="item.prop === 'description'"> <template v-else-if="item.prop === 'description'">
@@ -151,9 +151,9 @@ export default {
if (n) { if (n) {
n.forEach(t => { n.forEach(t => {
if (t.ruleType === 'indicator_match') { if (t.ruleType === 'indicator_match') {
t.library = t.ruleConfig.knowledge.name t.library = t.ruleConfigObj.knowledgeBase.name
} else if (t.ruleType === 'threshold') { } else if (t.ruleType === 'threshold') {
t.dimensions = t.ruleConfig.dimensions t.dimensions = t.ruleConfigObj.dimensions
} }
}) })
} }

View File

@@ -11,14 +11,16 @@
<span>{{ $t('overall.create') }}</span> <span>{{ $t('overall.create') }}</span>
</button> </button>
<!-- <button--> <button
<!-- id="knowledge-base-edit"--> :disabled="disableEdit"
<!-- :title="$t('knowledgeBase.editKnowledgeBase')"--> id="knowledge-base-edit"
<!-- class="top-tool-btn margin-r-10"--> :title="$t('knowledgeBase.editKnowledgeBase')"
<!-- style="width:72px;">--> class="top-tool-btn margin-r-10"
<!-- <i class="cn-icon-edit cn-icon" ></i>--> @click="onEdit"
<!-- <span>{{$t('overall.edit')}}</span>--> style="width:72px;">
<!-- </button>--> <i class="cn-icon-edit cn-icon" ></i>
<span>{{$t('overall.edit')}}</span>
</button>
<button <button
:disabled="disableDelete" :disabled="disableDelete"
@@ -51,6 +53,10 @@ export default {
disableDelete: { disableDelete: {
type: Boolean, type: Boolean,
default: true default: true
},
disableEdit: {
type: Boolean,
default: true
} }
}, },
data () { data () {
@@ -65,6 +71,9 @@ export default {
onCreate () { onCreate () {
this.$emit('create') this.$emit('create')
}, },
onEdit () {
this.$emit('edit')
},
onDelete (data) { onDelete (data) {
this.$emit('delete', data) this.$emit('delete', data)
} }

View File

@@ -1,22 +1,24 @@
<template> <template>
<div class="detection"> <div class="detection">
<div class="detection-title"> <div class="detection-title">
<span>{{ $t('overall.detections') }}</span> <span>{{ $t('overall.policies') }}</span>
<span class="detection-title-label"> <span class="detection-title-label">
60 polices created(200 max) | 32 polices enabled(100 max) {{ policyTotal }} {{ $t('detection.policesCreated') }} | {{ policyEnabledNum }} {{ $t('detection.policesEnabled') }}
</span> </span>
</div> </div>
<div class="detection-content"> <div class="detection-content">
<div class="detection-filter"> <div class="detection-filter">
<detection-filter></detection-filter> <detection-filter @filterParams="getFilterParams" @policyTotal="getPolicyTotal" />
</div> </div>
<div class="detection-block"> <div class="detection-block">
<detection-tools <detection-tools
@delete="toDelete" @delete="toDelete"
@create="onCreate" @create="onCreate"
@edit="onEdit"
@search="onSearch" @search="onSearch"
:disableEdit="disableEdit"
:disableDelete="disableDelete"/> :disableDelete="disableDelete"/>
<div class="detection-table" style="position: relative"> <div class="detection-table" style="position: relative">
@@ -32,7 +34,6 @@
:all-count="18" :all-count="18"
@selectionChange="selectionChange" @selectionChange="selectionChange"
@reload="reloadRowList" @reload="reloadRowList"
@toggleLoading="toggleLoading"
@rowDoubleClick="onRowDoubleClick" @rowDoubleClick="onRowDoubleClick"
></detection-table> ></detection-table>
</div> </div>
@@ -61,10 +62,8 @@
cell-style="padding:4px 0px;font-size: 12px;color: #353636;font-weight: 400;" cell-style="padding:4px 0px;font-size: 12px;color: #353636;font-weight: 400;"
header-cell-style="padding:4px 0px;background: #F5F8FA;font-size: 12px;color: #353636;font-weight: 500;"> header-cell-style="padding:4px 0px;background: #F5F8FA;font-size: 12px;color: #353636;font-weight: 500;">
<el-table-column :resizable="false" align="center" type="selection" width="50"></el-table-column> <el-table-column :resizable="false" align="center" type="selection" width="50"></el-table-column>
<el-table-column property="ruleId" label="ID" width="70"></el-table-column> <el-table-column property="ruleId" label="ID" width="150"></el-table-column>
<el-table-column property="name" label="Name"></el-table-column> <el-table-column property="name" label="Name"></el-table-column>
<el-table-column property="category" label="Category" width="100" :formatter="categoryFormat"></el-table-column>
<el-table-column property="function" label="Function" width="110" :formatter="sourceFormat"></el-table-column>
</el-table> </el-table>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
@@ -89,6 +88,7 @@ import DetectionTable from '@/views/detectionsNew/DetectionTable'
import { api } from '@/utils/api' import { api } from '@/utils/api'
import dataListMixin from '@/mixins/data-list' import dataListMixin from '@/mixins/data-list'
import DetectionDrawer from '@/views/detectionsNew/DetectionDrawer' import DetectionDrawer from '@/views/detectionsNew/DetectionDrawer'
import axios from 'axios'
export default { export default {
name: 'Index', name: 'Index',
@@ -101,8 +101,8 @@ export default {
mixins: [dataListMixin], mixins: [dataListMixin],
data () { data () {
return { return {
// url: api.detection.list, url: api.detection.list,
url: api.knowledgeBase, // url: api.knowledgeBase,
listUrl: api.detection.list, listUrl: api.detection.list,
tableId: 'detectionTable', tableId: 'detectionTable',
isNoData: false, isNoData: false,
@@ -110,42 +110,52 @@ export default {
isSelectedStatus: false, isSelectedStatus: false,
batchDeleteObjs: [], // 待删除列表 batchDeleteObjs: [], // 待删除列表
secondBatchDeleteObjs: [], secondBatchDeleteObjs: [],
disableEdit: true,
disableDelete: true, disableDelete: true,
showConfirmDialog: false, showConfirmDialog: false,
delItemList: [], delItemList: [],
showDrawer: false, showDrawer: false,
drawerInfo: {} drawerInfo: {},
filterParams: {},
policyTotal: 0,
policyEnabledNum: 0
} }
}, },
methods: { methods: {
onSearch () { onSearch (keyWord) {
// todo 暂时禁用,后续再开发时解禁 this.filterParams = {
// const params = { ...this.filterParams,
// ...this.filterParams, name: keyWord
// name: this.keyWord }
// } this.search(this.filterParams, 'detection')
// this.clearList()
// this.search(params)
// this.$refs.knowledgeFilter.reloadFilter(this.checkedCategoryIds, this.checkedStatusIds)
}, },
toDelete (data) { toDelete (data) {
// todo 暂时禁用,后续再开发时解禁 if (data && data.ruleId) {
// if (data && data.ruleId) { this.secondBatchDeleteObjs = []
// this.secondBatchDeleteObjs = [] this.batchDeleteObjs = []
// this.batchDeleteObjs = [] this.secondBatchDeleteObjs.push(data)
// this.secondBatchDeleteObjs.push(data) this.batchDeleteObjs.push(data)
// this.batchDeleteObjs.push(data) }
// } this.showDelDialog()
// this.showDelDialog()
}, },
onCreate () { onCreate () {
// todo 暂时禁用,后续再开发时解禁 this.$router.push({
// this.$router.push({ path: '/detection/policies/create',
// path: '/detection/policies/create', query: {
// query: { t: +new Date()
// t: +new Date() }
// } })
// }) },
onEdit () {
const pageNo = this.$router.currentRoute.value.query.pageNo
this.$router.push({
path: '/detection/policies/edit',
query: {
t: +new Date(),
pageNoForTable: pageNo || 1,
id: this.batchDeleteObjs[0].ruleId
}
})
}, },
selectionChange (objs) { selectionChange (objs) {
this.batchDeleteObjs = [] this.batchDeleteObjs = []
@@ -161,8 +171,6 @@ export default {
reloadRowList () { reloadRowList () {
this.getTableData() this.getTableData()
}, },
toggleLoading () {
},
showDelDialog () { showDelDialog () {
this.showConfirmDialog = true this.showConfirmDialog = true
this.$nextTick(() => { this.$nextTick(() => {
@@ -177,16 +185,6 @@ export default {
secondSelectionChange (objs) { secondSelectionChange (objs) {
this.secondBatchDeleteObjs = objs this.secondBatchDeleteObjs = objs
}, },
categoryFormat (row, column) {
// const category = row.category
// const t = knowledgeBaseCategory.find(t => t.value === category)
// return t ? t.name : category
},
sourceFormat (row, column) {
// const source = row.source
// const t = knowledgeBaseSource.find(t => t.value === source)
// return t ? t.name : source
},
submit () { submit () {
this.delBatchDetection() this.delBatchDetection()
this.showConfirmDialog = false this.showConfirmDialog = false
@@ -206,39 +204,64 @@ export default {
}) })
} else { } else {
// todo 调用接口删除 // todo 调用接口删除
// this.toggleLoading(true) this.toggleLoading(true)
// axios.delete(api.detection.delete + '?ruleIds=' + ids).then(response => { axios.delete(api.detection.delete + '?ruleIds=' + ids).then(response => {
// if (response.status === 200) { if (response.status === 200) {
// this.delFlag = true this.delFlag = true
// this.$message({ duration: 2000, type: 'success', message: this.$t('tip.deleteSuccess') }) this.$message({ duration: 2000, type: 'success', message: this.$t('tip.deleteSuccess') })
// this.secondBatchDeleteObjs.forEach((item) => { this.secondBatchDeleteObjs.forEach((item) => {
// this.$refs.delDataTable.toggleRowSelection(item, false) this.$refs.delDataTable.toggleRowSelection(item, false)
// }) })
// this.$refs.knowledgeFilter.reloadFilter() this.secondBatchDeleteObjs = []
// this.secondBatchDeleteObjs = [] this.batchDeleteObjs = []
// this.batchDeleteObjs = [] delete this.searchLabel.category
// delete this.searchLabel.category delete this.searchLabel.source
// delete this.searchLabel.source this.getTableData()
// this.getTableData() } else {
// } else { this.$message.error(response.data.message)
// this.$message.error(response.data.message) }
// } }).finally(() => {
// }).finally(() => { this.toggleLoading(false)
// this.toggleLoading(false) if (this.isSelectedStatus) {
// if (this.isSelectedStatus != undefined) { this.isSelectedStatus = false
// this.isSelectedStatus = false this.disableDelete = true
// this.disableDelete = true this.secondBatchDeleteObjs = []
// this.secondBatchDeleteObjs = [] this.batchDeleteObjs = []
// this.batchDeleteObjs = [] this.showConfirmDialog = false
// this.showConfirmDialog = false }
// } })
// })
} }
}, },
onRowDoubleClick (data) { onRowDoubleClick (data) {
// todo 暂时禁用,后续再开发时解禁 this.showDrawer = true
// this.showDrawer = true this.drawerInfo = data
// this.drawerInfo = data },
getFilterParams (params) {
const delList = []
if (params.status) {
this.filterParams.status = params.status
} else {
delete this.filterParams.status
delList.push('status')
}
if (params.category) {
this.filterParams.category = params.category
} else {
delete this.filterParams.category
delList.push('category')
}
if (params.eventType) {
this.filterParams.eventType = params.eventType
} else {
delete this.filterParams.eventType
delList.push('eventType')
}
this.search(this.filterParams, 'detection', delList)
},
getPolicyTotal (total, enabledNum) {
this.policyTotal = total
this.policyEnabledNum = enabledNum
} }
} }
} }