CN-1173: 检测功能UI开发

This commit is contained in:
刘洪洪
2023-08-03 18:47:18 +08:00
parent 238f452643
commit f71e27339d
24 changed files with 3671 additions and 103 deletions

View File

@@ -0,0 +1,194 @@
<template>
<div>
<div class="form-setting__block margin-b-20" style="display: flex">
<div class="block-mode">
<!--todo 图标没有后期换-->
<div class="block-mode-left">
<i class="cn-icon cn-icon-setting2 block-mode-icon"></i>
</div>
<div class="block-mode-right">
<div class="block-mode-title">Indicator Match</div>
<div class="block-mode-content">
Use indicators from intelligencesources to detect matchingevents and alerts.
</div>
<div :class="settingObj.ruleType===detectionRuleType.indicator?'block-mode-btn-active':'block-mode-btn'"
@click="selectMode(detectionRuleType.indicator)">select
</div>
</div>
</div>
<div class="block-mode">
<!--todo 图标没有后期换-->
<div class="block-mode-left">
<i class="cn-icon cn-icon-setting2 block-mode-icon"></i>
</div>
<div class="block-mode-right">
<div class="block-mode-title">Threshold</div>
<div class="block-mode-content">
Aggregate query results to detect when number of matches exceeds threshold.
</div>
<div :class="settingObj.ruleType===detectionRuleType.threshold?'block-mode-btn-active':'block-mode-btn'"
@click="selectMode(detectionRuleType.threshold)">select
</div>
</div>
</div>
</div>
<!--category-->
<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-select v-model="settingObj.category" class="form-setting__select" placeholder=" " size="mini" @change="changeEditFlag">
<el-option
v-for="item in categoryList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<!--type-->
<el-form-item label="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-option
v-for="item in typeList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<!--name-->
<el-form-item label="Name" prop="name" class="form-setting__block margin-b-20">
<el-input
maxlength="64"
show-word-limit
placeholder=""
v-model="settingObj.name"
@input="changeEditFlag"
class="form-setting__input" />
</el-form-item>
<!--Description-->
<div class="form-setting__block margin-b-20">
<div class="block-title">Description</div>
<el-input
maxlength="255"
show-word-limit
v-model="settingObj.description"
type="textarea"
resize='none'
@input="changeEditFlag"
class="form-setting__textarea" />
</div>
</el-form>
<div class="form-setting__block margin-b-20">
<div class="block-title">Policy Status</div>
<el-switch
v-model="settingObj.status"
@change="changeEditFlag"
active-color="#38ACD2"
inactive-color="#C0CEDB"
:active-value="1"
:inactive-value="0"
:active-text="switchStatus(settingObj.status)"/>
</div>
<div class="form-setting__btn">
<el-button @click="onContinue">Continue</el-button>
</div>
</div>
</template>
<script>
import { detectionRuleType } from '@/utils/constants'
import { switchStatus } from '@/utils/tools'
import { get } from '@/utils/http'
import { api } from '@/utils/api'
export default {
name: 'GeneralSettings',
data () {
return {
detectionRuleType,
categoryList: [],
typeList: [],
settingObj: {
ruleType: detectionRuleType.threshold,
category: '',
eventType: '',
name: '',
description: '',
status: 1,
editFlag: false, // 编辑标识如果保存之后继续编辑且不再点击保存置为true则不会将编辑内容提交到最终form
saveFlag: false // 是否点击保存标识
},
rules: {
category: [
{
required: true,
message: this.$t('validate.required'),
trigger: 'change'
}
],
eventType: [
{
required: true,
message: this.$t('validate.required'),
trigger: 'change'
}
],
name: [
{
required: true,
message: this.$t('validate.required'),
trigger: 'blur'
}
]
}
}
},
mounted () {
this.initData()
},
methods: {
switchStatus,
initData () {
get(api.detection.statistics, { pageSize: -1 }).then(response => {
if (response.code === 200) {
this.categoryList = response.data.categoryList || []
this.typeList = response.data.typeList || []
} else {
console.error(response)
}
}).finally(() => {
})
},
selectMode (ruleType) {
this.settingObj.ruleType = ruleType
this.changeEditFlag()
},
changeEditFlag () {
this.settingObj.editFlag = true
},
/** 点击继续,进行第二步 */
onContinue () {
this.$refs.form.validate(valid => {
if (valid) {
this.settingObj.editFlag = false
this.settingObj.saveFlag = true
this.$emit('setSettingForm', this.settingObj)
}
})
}
}
}
</script>
<style lang="scss">
</style>

View File

@@ -0,0 +1,234 @@
<template>
<div class="history-top-key" v-if="myDrawer">
<el-drawer v-model="myDrawer" :with-header="false">
<div class="key-header">
<div>History Top Keys</div>
<i class="cn-icon cn-icon-close" @click="closeDrawer"></i>
</div>
<div class="key-search">
<el-input v-model="searchKey" @keyup.enter="onSearch" size="mini" placeholder="Search for">
<template #prefix>
<!--todo 该图标名称错误已在iconfont修改后续记得改过来-->
<i class="cn-icon cn-icon-serach key-search-icon"></i>
</template>
</el-input>
<i class="cn-icon cn-icon-refresh1 key-refresh" @click="onRefresh"></i>
</div>
<div class="key-total">
Total: <span class="key-total-number">{{ tableTotal }}</span>
</div>
<div class="key-table">
<loading :loading="loading"></loading>
<el-table :data="tableData" style="width: 100%" @row-click="rowClick">
<el-table-column
v-for="(item, index) in tableTitle"
:key="`col-${index}`"
:fixed="item.fixed"
:label="item.label"
:min-width="`${item.minWidth}`"
:prop="item.prop"
:sort-orders="['ascending', 'descending']"
:width="`${item.width}`"
>
<template #header>
<span>{{ item.label }}</span>
</template>
<template #default="scope" :column="item">
<template v-if="item.prop === 'metric'">
<span>{{unitConvert(scope.row.metric, unitTypes.byte).join(' ')}}</span>
</template>
<template v-else-if="item.prop === 'last'">
<span>{{dateFormatByAppearance(scope.row[item.prop])}}</span>
</template>
<span v-else>{{ scope.row[item.prop] || '-' }}</span>
</template>
</el-table-column>
<template v-slot:empty >
<chart-error v-if="showError" :content="errorMsg" style="line-height: 0" />
</template>
</el-table>
</div>
</el-drawer>
</div>
</template>
<script>
import unitConvert from '@/utils/unit-convert'
import { unitTypes } from '@/utils/constants'
import { dateFormatByAppearance } from '@/utils/date-util'
import { get } from '@/utils/http'
import { api } from '@/utils/api'
import Loading from '@/components/common/Loading'
import ChartError from '@/components/common/Error'
export default {
name: 'HistoryTopKeys',
props: {
showDrawer: {
type: Boolean,
default: false
}
},
components: {
ChartError,
Loading
},
data () {
return {
myDrawer: false,
searchKey: '',
unitTypes,
loading: false,
tableTotal: 0,
tableData: [],
tableTitle: [
{
// label: this.$t('knowledge.status'),
label: 'Keys',
prop: 'keys',
show: true,
minWidth: 120
},
{
label: 'Last Seen',
prop: 'last',
show: true,
minWidth: 150
},
{
label: 'Primary metric',
prop: 'metric',
show: true,
minWidth: 120
}
],
showError: false,
errorMsg: ''
}
},
mounted () {
this.myDrawer = this.showDrawer
this.getTopKeysData()
},
methods: {
unitConvert,
dateFormatByAppearance,
getTopKeysData (data) {
this.loading = true
const params = {}
if (data) {
// todo 具体入参按文档要求
params.param = data
}
get(api.detection.create.topKeys, params).then(res => {
this.tableTotal = 0
this.tableData = []
if (res.code === 200) {
this.tableTotal = res.data.total
this.tableData = res.data.list
} else {
this.httpError(res)
}
}).catch(err => {
this.httpError(err)
}).finally(() => {
this.loading = false
})
},
/** 关闭topKeys弹窗 */
closeDrawer () {
this.myDrawer = false
this.$emit('closeDrawer', false)
},
/** 单击topKeys弹窗某一项 */
rowClick (data) {
this.$emit('keyRowClick', data)
},
onRefresh () {
this.getTopKeysData()
},
onSearch () {
this.getTopKeysData(this.searchKey)
},
httpError (e) {
this.showError = true
this.errorMsg = this.errorMsgHandler(e)
}
}
}
</script>
<style lang="scss">
.history-top-key {
width: 396px;
height: 520px;
.el-drawer__body {
border: 1px #E2E5EC solid;
}
}
.key-header {
height: 41px;
background: #F7F7F7;
box-shadow: 0 1px 0 0 rgba(226,229,236,1);
border-radius: 2px 2px 0 0;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 12px;
font-family: NotoSansHans-Medium;
font-size: 14px;
color: #353636;
font-weight: 500;
i {
font-size: 12px;
color: #575757;
cursor: pointer;
}
}
.key-search {
margin-left: 12px;
margin-top: 6px;
display: flex;
align-items: center;
justify-content: space-between;
.key-search-icon {
font-size: 13px;
color: #999999;
}
.key-refresh {
margin: 0 10px;
color: #38ACD2;
font-size: 14px;
height: 28px;
line-height: 28px;
cursor: pointer;
}
}
.key-total {
margin: 10px 12px;
font-family: NotoSansSChineseRegular;
font-size: 14px;
color: #999999;
line-height: 21px;
font-weight: 400;
.key-total-number {
color: #666666;
margin-left: 4px;
}
}
</style>

View File

@@ -0,0 +1,488 @@
<template>
<div>
<div v-if="mySettingObj.ruleType===detectionRuleType.threshold" style="display: flex;justify-content: space-between;">
<div>
<el-form ref="form" :model="thresholdRuleObj" label-position="top" :rules="rules">
<!--source-->
<el-form-item label="Source" prop="dataSource" class="form-setting__block margin-b-20">
<el-select v-model="thresholdRuleObj.dataSource" placeholder=" " size="mini" class="form-setting__select">
<el-option
v-for="item in sourceList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
<!--Dimensions-->
<div class="form-setting__block margin-b-20">
<div class="block-title">Dimensions</div>
<div class="block-dimension">
<div class="block-dimension-tag" v-for="(ite, ind) in dimensionList" :key="ind">{{ ite.label }}</div>
</div>
</div>
<div class="form-setting__block form-setting__block-key">
<div>Key Selection</div>
<div class="block-key">
<!--todo 图标暂无需要更换-->
<i class="cn-icon cn-icon-shijianjihua"></i>
<span @click="showDrawer=true">History Top Keys</span>
</div>
</div>
<!--Filters模块-->
<div class="form-setting__block margin-b-20">
<div class="block-title1">{{ $t('detections.filters') }}</div>
<div class="definition-filter-block" v-if="showFilter">
<div class="definition-filter-item" v-for="(item, index) in thresholdRuleObj.filterList" :key="index">
<el-select class="filter-item__select margin-r-8" v-model="item.filter" placeholder=" " size="mini">
<el-option
v-for="item in selectList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input class="filter-item__input margin-r-8" size="mini" disabled placeholder="equal"></el-input>
<el-input
class="filter-item__input margin-r-8"
size="mini"
oninput="value=value.replace(/[^\d]/g,'')"
v-model="item.value"></el-input>
<i class="cn-icon cn-icon-close" @click="delFilterItem(index)"></i>
</div>
<div style="height: 10px;"></div>
<div class="filter-block-footer">
<i class="cn-icon cn-icon-add" @click="addFilter"></i>
</div>
</div>
<div v-else class="block-filter-add" @click="addFilter">+</div>
</div>
<!--Condition模块-->
<div class="form-setting__block margin-b-20">
<div class="block-title">Condition</div>
<el-form ref="form2" :model="thresholdRuleObj" label-position="top">
<div class="definition-condition-block" v-for="(item, index) in thresholdRuleObj.conditionData" :key="index">
<el-form-item :label="$t('detection.level')" :prop="`conditionData.${index}.level`" :rules="rules.level">
<el-select class="condition__select margin-b-20" v-model="item.level" placeholder=" " size="mini">
<template #prefix>
<div
class="condition__select__icon"
:style="{background: eventSeverityColor[item.level]}"></div>
</template>
<el-option
v-for="item in levelList"
:key="item.label"
:label="item.label"
:value="item.value"
:disabled="item.disabled"
/>
</el-select>
</el-form-item>
<div class="condition-metric" v-for="(data, i) in item.list" :key="i">
<div class="condition-metric-item1">
<div class="metric-item1__text">
<span style="margin-right: 9px;">If</span>
<el-form-item :prop="`conditionData.${index}.list.${i}.metric`" :rules="rules.metric">
<el-select v-model="data.metric" placeholder=" " size="mini">
<el-option
v-for="item in metricList"
:key="item.label"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<span style="margin-left: 9px;">of keys</span>
</div>
<div>
<i class="cn-icon cn-icon-add" @click="addConditionItem(index)"></i>
<i
class="cn-icon cn-icon-close"
:class="delConditionDisabled ? 'metric-item1-close-disable' : 'metric-item1-close'"
@click="delConditionItem(index, i)">
</i>
</div>
</div>
<div class="condition-metric-item2">
<div style="height: 24px;line-height: 24px;display: flex;">
<el-form-item :prop="`conditionData.${index}.list.${i}.condition`" :rules="rules.condition">
<el-select class="metric-item2__select" v-model="data.condition" placeholder=" " size="mini">
<el-option
v-for="item in conditionList"
:key="item.label"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :prop="`conditionData.${index}.list.${i}.value`" :rules="rules.value">
<!--todo 应当添加长度限制-->
<el-input
class="metric-item2__input"
v-model.number="data.value"
oninput="value=value.replace(/[^\d]/g,'')"
size="mini"></el-input>
</el-form-item>
</div>
<span>{{ data.metric }}</span>
</div>
<el-divider v-if="item.list.length - 1 > i" class="condition-divider">and</el-divider>
</div>
</div>
</el-form>
<div class="condition-add" @click="addCondition">
<i class="cn-icon cn-icon-add"></i>Add Condition
</div>
</div>
</div>
<!--History Top Keys弹窗-->
<div class="key-drawer">
<history-top-keys
v-if="showDrawer"
:showDrawer="showDrawer"
@closeDrawer="onCloseDrawer"
@keyRowClick="getRowClick"
></history-top-keys>
</div>
</div>
<div v-if="mySettingObj.ruleType===detectionRuleType.indicator">
<el-form ref="form" :model="indicatorRuleObj" label-position="top" :rules="rules">
<!--Source-->
<el-form-item label="Source" prop="dataSource" class="form-setting__block margin-b-20">
<el-select v-model="indicatorRuleObj.dataSource" class="form-setting__select" placeholder=" " size="mini">
<el-option
v-for="item in sourceList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<!--Library-->
<el-form-item label="Library" prop="knowledgeId" class="form-setting__block margin-b-20">
<el-select v-model="indicatorRuleObj.knowledgeId" class="form-setting__select" placeholder=" " size="mini">
<el-option
v-for="item in libraryList"
:key="item.knowledgeId"
:label="item.label"
:value="item.knowledgeId"
/>
</el-select>
</el-form-item>
<!--Level-->
<el-form-item :label="$t('detection.level')" prop="level" class="form-setting__block">
<el-select v-model="indicatorRuleObj.level" class="condition__select form-setting__select" placeholder=" " size="mini">
<template #prefix>
<div
class="condition__select__icon"
:style="{background: eventSeverityColor[indicatorRuleObj.level]}"></div>
</template>
<el-option
v-for="item in levelList"
:key="item.label"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
</div>
<div class="form-setting__btn">
<el-button @click="onContinue">Continue</el-button>
</div>
</div>
</template>
<script>
import { get } from '@/utils/http'
import { api } from '@/utils/api'
import HistoryTopKeys from '@/components/table/detection/HistoryTopKeys'
import { eventSeverityColor, detectionRuleType } from '@/utils/constants'
export default {
name: 'RuleDefinition',
props: {
settingObj: {
type: Object
}
},
components: {
HistoryTopKeys
},
watch: {
settingObj: {
immediate: true,
deep: true,
handler (newVal, oldVal) {
if (!newVal.editFlag && newVal.saveFlag) {
this.mySettingObj = JSON.parse(JSON.stringify(newVal))
}
}
}
},
data () {
return {
eventSeverityColor,
detectionRuleType,
mySettingObj: {
ruleType: detectionRuleType.threshold
},
dimensionList: [], // Dimensions数据
// ruleType为Indicator时表单数据
indicatorRuleObj: {
dataSource: '',
knowledgeId: '',
level: ''
},
// ruleType为Threshold时表单数据
thresholdRuleObj: {
dataSource: '',
dimensionKeys: '',
filterList: [],
filters: '', // filter提交到接口的数据即filterList转化为字符串
conditionData: [
{
level: '',
list: [
{ metric: '', condition: '', value: '' }
]
}
],
conditions: {} // filter提交到接口的数据即filterList转化为字符串
},
rules: {
dataSource: [
{
required: true,
message: this.$t('validate.required'),
trigger: 'change'
}
],
level: [
{
required: true,
message: this.$t('validate.required'),
trigger: 'change'
}
],
metric: [
{
required: true,
message: this.$t('validate.required'),
trigger: 'change'
}
],
condition: [
{
required: true,
message: this.$t('validate.required'),
trigger: 'change'
}
],
value: [
{
required: true,
message: this.$t('validate.required'),
trigger: 'blur'
}
],
knowledgeId: [
{
required: true,
message: this.$t('validate.required'),
trigger: 'blur'
}
]
},
sourceList: [], // source下拉列表数据
// rule的policy创建信息
showDrawer: false, // 显示History Top Keys抽屉弹框
showFilter: false, // 显示filter筛选框点击add显示
selectList: [], // filter的第一个下拉列表
levelList: [], // condition的level下拉列表
metricList: [],
conditionList: [],
libraryList: [],
delConditionDisabled: true, // condition删除标识true表示禁止删除即只有一个condition时
unitObj: {
than: '>',
less: '<',
equal: '='
}
}
},
mounted () {
this.initData()
// todo 调用接口进行赋值
this.dimensionList = [
{ label: 'Destination IP/CIDR', value: 'Destination IP/CIDR' },
{ label: 'Source Port Number', value: 'Source Port Number' }
]
},
methods: {
initData () {
get(api.detection.statistics, { pageSize: -1 }).then(response => {
if (response.code === 200) {
this.sourceList = response.data.sourceList || []
this.levelList = response.data.levelList || []
this.conditionList = response.data.conditionList || []
this.metricList = response.data.metricList || []
this.libraryList = response.data.libraryList || []
} else {
console.error(response)
}
}).finally(() => {
})
},
/** 单击History Top Keys列表某行filter添加数据 */
getRowClick (data) {
this.addFilter(data)
},
/** 关闭History Top Keys弹框 */
onCloseDrawer () {
this.showDrawer = false
},
/** filter模块点击add按钮 */
addFilter (data) {
this.showFilter = true
if (this.selectList.length === 0) {
this.getFilterList()
}
// 添加数据前,先删除空白项
const delIndex = this.thresholdRuleObj.filterList.findIndex(t => t.filter === '')
if (delIndex > -1) {
this.thresholdRuleObj.filterList.splice(delIndex, 1)
}
if (data.metric) {
// 从key弹框添加数据
const obj = this.thresholdRuleObj.filterList.find(t => t.keyId === data.keyId)
if (!obj) {
this.thresholdRuleObj.filterList.push({ keyId: data.keyId, filter: this.selectList[0].label, value: data.metric })
}
} else {
// 手动添加
this.thresholdRuleObj.filterList.push({ filter: '', value: '' })
}
},
/** 添加condition大模块 */
addCondition () {
this.$refs.form2.validate(valid => {
if (valid) {
// 如果选择了level则禁用这一条level不允许再选择除非删除才能选择
this.levelList.forEach(item => {
const obj = this.thresholdRuleObj.conditionData.find(t => t.level === item.value)
if (obj) {
item.disabled = true
}
})
this.thresholdRuleObj.conditionData.push({ level: '', list: [{ metric: '', condition: '', value: '' }] })
this.delConditionDisabled = false
}
})
},
/** 添加condition小模块 */
addConditionItem (index) {
this.$refs.form2.validate(valid => {
if (valid) {
this.thresholdRuleObj.conditionData[index].list.push({ metric: '', condition: '', value: '' })
this.delConditionDisabled = false
}
})
},
/**
* 删除condition小模块只剩一个时点击删除则删除大模块
* 只有一个大模块时,只剩一个小模块则不可删除
* */
delConditionItem (index, i) {
if (!this.delConditionDisabled) {
const dataLen = this.thresholdRuleObj.conditionData.length
const listLen = this.thresholdRuleObj.conditionData[index].list.length
if (dataLen === 1) {
if (listLen >= 2) {
this.thresholdRuleObj.conditionData[index].list.splice(i, 1)
}
if (this.thresholdRuleObj.conditionData[index].list.length === 1) {
this.delConditionDisabled = true
}
} else {
if (listLen === 1) {
this.thresholdRuleObj.conditionData.splice(index, 1)
} else {
this.thresholdRuleObj.conditionData[index].list.splice(i, 1)
}
}
}
},
/** 获取filter模块下拉列表 */
getFilterList () {
// todo 请求接口
this.selectList = [
{ value: 'Destination As Number', label: 'Destination As Number' },
{ value: 'Destination As String', label: 'Destination As String' }
]
},
/** 删除filter某一项 */
delFilterItem (i) {
this.thresholdRuleObj.filterList.splice(i, 1)
},
/** 点击继续,展开第三步 */
onContinue () {
this.$refs.form.validate(valid => {
if (valid) {
if (this.mySettingObj.ruleType === detectionRuleType.indicator) {
// 第一步模式选择Indicator Match
this.$emit('setRuleObj', this.indicatorRuleObj)
} else {
// 第一步模式选择Threshold
this.$refs.form2.validate(valid2 => {
if (valid2) {
this.getConditions()
this.$emit('setRuleObj', this.thresholdRuleObj)
}
})
}
}
})
},
/** 将condition的数组转换为入参需要的字符串 */
getConditions () {
const conditionData = this.thresholdRuleObj.conditionData
const obj = {}
conditionData.forEach(item => {
obj[item.level] = ''
let str = ''
item.list.forEach(t => {
str = str + t.metric + ' ' + this.unitObj[t.condition] + ' ' + t.value + ' && '
})
str = str.substring(0, str.length - 4)
obj[item.level] = str
})
this.thresholdRuleObj.conditions = obj
}
}
}
</script>