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,173 @@
<template>
<div class="detection-drawer" style="height: 100vh;overflow: scroll;padding-bottom: 90px">
<div class="drawer-basic">
<div class="drawer-basic-header">
<div class="drawer-basic-id">ID: {{ drawerInfo.ruleId }}</div>
<div :class="`detection-tag-status${detailData.status}`">
{{ switchStatus(detailData.status) }}
</div>
</div>
<div class="drawer-basic-function">
<div class="detection-drawer-title">Name</div>
<div class="basic-function-value">{{ detailData.name }}</div>
</div>
<div class="drawer-basic-function">
<div class="detection-drawer-title">Type</div>
<div class="basic-function-value">{{ detailData.eventType }}</div>
</div>
<div class="drawer-basic-description">
<div class="detection-drawer-title">Description</div>
<div class="basic-description-value">{{ detailData.description }}</div>
</div>
</div>
<div class="detection-drawer-collapse">
<el-collapse v-model="activeRule">
<el-collapse-item title="Rule Definitcm" name="rule">
<div class="drawer-collapse-content">
<div class="drawer-basic-function">
<div class="detection-drawer-title">Source</div>
<div class="basic-function-value">{{ detailData.category }}</div>
</div>
<div v-if="detailData.ruleType==='indicator_match'">
<div class="drawer-basic-function">
<div class="detection-drawer-title">Library</div>
<span class="basic-function-value">{{ detailData.library }}</span>
</div>
<div class="drawer-basic-function">
<div class="detection-drawer-title">Level</div>
<div class="detection-drawer-title">
<div class="detection__icon" :style="`background-color: ${eventSeverityColor['critical']}`"></div>
<div class="basic-function-value">Critical</div>
</div>
</div>
</div>
<div v-else>
<div class="drawer-basic-function">
<div class="detection-drawer-title">Dimensions</div>
<span class="detection-tag-blue">{{ detailData.dimensions }}</span>
</div>
<div class="drawer-basic-function">
<div class="detection-drawer-title">Filters</div>
<span class="detection-tag-blue">source</span>
<span style="margin: 0 6px;">equal</span><span>19890</span>
</div>
<div class="drawer-basic-function" v-for="item in severityList" :key="item.severity"
style="padding-bottom: 0">
<div class="detection-drawer-title">
<div class="detection__icon" :style="`background-color: ${eventSeverityColor[item.severity]}`"></div>
<div>{{ toUpperCaseByString(item.severity) }}</div>
</div>
<div class="detection-drawer-title">Conditions</div>
<div>
<div class="detection-tag-gray margin-r-10">> 60 Kpackets/s</div>
<div class="detection-tag-gray">> 50 Unique Src IPs</div>
</div>
</div>
</div>
</div>
</el-collapse-item>
</el-collapse>
</div>
<div class="detection-drawer-collapse" style="margin: 20px 0">
<el-collapse v-model="activeTrigger">
<el-collapse-item title="Trigger" name="trigger">
<div class="drawer-collapse-content">
<div class="drawer-collapse-trigger">
Triggered when conditions occur at least
<span style="color: #046ECA" v-if="detailData.trigger">
{{ detailData.trigger.atLeast }} time
</span> in
<span style="color: #046ECA" v-if="detailData.trigger">
<!--todo 此处返回的是PT5M具体时间处理根据后续字段来看-->
{{ getNumberFromStr(detailData.trigger.interval) }} minutes
</span>
</div>
<div class="drawer-basic-function">
<div class="detection-drawer-title">Evaluation Frequency</div>
<div class="basic-function-value" v-if="detailData.trigger">{{ getNumberFromStr(detailData.trigger.resetInterval) }} minutes</div>
</div>
</div>
</el-collapse-item>
</el-collapse>
</div>
</div>
</template>
<script>
import { switchStatus, toUpperCaseByString } from '@/utils/tools'
import { eventSeverityColor } from '@/utils/constants'
import axios from '_axios@0.21.4@axios'
import { api } from '@/utils/api'
export default {
name: 'DetectionDrawer',
props: {
drawerInfo: {
type: Object
}
},
data () {
return {
activeRule: 'rule',
activeTrigger: 'trigger',
detailData: {},
eventSeverityColor,
severityList: []
}
},
watch: {
drawerInfo: {
immediate: true,
deep: true,
handler (n) {
if (n) {
this.getDetailData()
}
}
}
},
methods: {
switchStatus,
toUpperCaseByString,
getDetailData () {
this.severityList = [
{
severity: 'critical',
list: ['> 60 Kpackets/s', '> 50 Unique Src IPs']
},
{
severity: 'high',
list: ['> 20 Kpackets/s', '> 50 Unique Src IPs']
}
]
axios.get(`${api.detection.detail}/${this.drawerInfo.ruleId}`).then(res => {
if (res.data.code === 200) {
this.detailData = res.data.data
}
}).catch(err => {
console.error(err)
})
},
getNumberFromStr (str) {
return str.match(/\d+(\.\d+)?/g)[0]
}
}
}
</script>
<style lang="scss">
</style>

View File

@@ -0,0 +1,215 @@
<template>
<div>
<div class="detection-filter-title">
{{ $t('detections.filters') }}
</div>
<div class="detection-filter-content">
<div>
<div class="filter-content-title">{{ $t('overall.status') }}</div>
<div class="filter-content-content">
<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="filter-content-checkbox" :label="item.status">
<div>{{ item.label }}</div>
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div>
<div class="filter-content-title">{{ $t('overall.category') }}</div>
<div class="filter-content-content">
<el-checkbox-group v-model="checkCategory" @change="onChangeCategory">
<el-checkbox v-for="item in categoryList" :key="item.value" class="filter-content-checkbox" :label="item.value">
{{ item.label }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div>
<div class="filter-content-title">{{ $t('overall.type') }}</div>
<div class="filter-content-content">
<el-checkbox-group v-model="checkType" @change="onChangeCategory" style="display: flex;flex-direction: column">
<el-checkbox v-for="item in typeList" :key="item.value" class="filter-content-checkbox" :label="item.value">
{{ item.label }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</div>
</div>
</template>
<script>
import { get } from '@/utils/http'
import { api } from '@/utils/api'
export default {
name: 'DetectionFilter',
data () {
return {
statusList: [],
categoryList: [],
typeList: [],
checkStatus: [],
checkCategory: [],
checkType: [],
url: api.detection.statistics
}
},
mounted () {
// 开发时删除---start
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: {
getFilterData (params) {
let searchParams = { pageSize: -1 }
if (params) {
searchParams = { ...searchParams, ...params }
}
get(this.url, searchParams).then(response => {
if (response.code === 200) {
if (response.data.statusList) {
this.statusList = []
response.data.statusList.forEach(item => {
this.statusList.push({ status: item.status, label: this.switchStatus(item.status) })
this.checkStatus.push(item.status)
})
}
this.categoryList = response.data.categoryList
if (response.data.categoryList) {
response.data.categoryList.forEach(item => {
this.checkCategory.push(item.value)
})
}
this.typeList = response.data.typeList
if (response.data.typeList) {
response.data.typeList.forEach(item => {
this.checkType.push(item.value)
})
}
} else {
console.error(response)
}
}).finally(() => {
// this.initTypeData()
// this.initStatusData()
// const self = this
// this.$nextTick(() => {
// if (self.$refs.knowledgeTreeTypeFilter) {
// self.$refs.knowledgeTreeTypeFilter.setCheckedKeys(this.defaultCheckedCategory)
// }
// if (self.$refs.knowledgeTreeStatusFilter) {
// self.$refs.knowledgeTreeStatusFilter.setCheckedKeys(this.defaultCheckedStatus)
// }
// })
})
},
onChangeCategory (data) {
// todo 暂时禁用,后续再开发时解禁
// 根据选择的值,构造不同入参,传给列表页,调用查询列表接口
},
switchStatus (status) {
switch (status) {
case 0: return 'Disabled'
case 1: return 'Enabled'
}
}
}
}
</script>
<style lang="scss" scoped>
.detection-filter-title {
height: 32px;
line-height: 32px;
background: #F7F7F7;
padding: 0 20px;
box-shadow: 0 1px 0 0 rgba(226, 229, 236, 1);
border-radius: 4px 4px 0 0;
}
.detection-filter-content {
padding: 20px;
.filter-content-title {
font-family: NotoSansHans-Medium;
font-size: 14px;
line-height: 14px;
margin-bottom: 10px;
color: #353636;
font-weight: 500;
}
.filter-content-content {
display: flex;
flex-direction: column;
.filter-content-checkbox {
line-height: 16px;
margin-bottom: 10px;
font-family: NotoSansSChineseRegular;
font-size: 14px;
color: #353636;
font-weight: 400;
.el-checkbox__inner {
width: 16px !important;
height: 16px !important;
text-align: center !important;
line-height: 16px !important;
}
.el-checkbox__input.is-indeterminate .el-checkbox__inner {
border-color: #38ACD2;
background: #38ACD2;
border-radius: 2px;
}
.el-checkbox__input.is-indeterminate .el-checkbox__inner:before {
background: #FFFFFF;
border-radius: 1px;
}
.el-checkbox__input.is-checked {
.el-checkbox__inner {
border-color: #38ACD2;
background: #38ACD2;
border-radius: 2px;
}
}
.el-checkbox__input.is-focus {
.el-checkbox__inner {
border-color: #38ACD2;
}
}
.el-checkbox__label {
font-family: NotoSansSChineseRegular;
font-size: 14px;
color: #353636;
font-weight: 400;
}
}
}
}
</style>

View File

@@ -0,0 +1,251 @@
<template>
<div class="detection-form">
<div class="detection-form-header">
Create Alert Policies
</div>
<!--第一步General Settings-->
<div class="detection-form-content">
<div class="detection-form-collapse">
<el-collapse v-model="activeNames">
<el-collapse-item name="1">
<template #title>
<div class="form-collapse-header">
<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>
</template>
<div class="form-collapse-content">
<general-settings ref="form" @setSettingForm="getFormSetting" />
</div>
</el-collapse-item>
</el-collapse>
</div>
<!--第二步Rule Definition-->
<div class="detection-form-collapse">
<el-collapse v-model="activeNames" style="position: relative;">
<el-collapse-item name="2">
<template #title>
<div class="form-collapse-header">
<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>
</template>
<div class="form-collapse-content">
<rule-definition :settingObj="settingObj" @setRuleObj="getRuleObj" />
</div>
</el-collapse-item>
</el-collapse>
</div>
<!--第三步Trigger-->
<div class="detection-form-collapse">
<el-collapse v-model="activeNames">
<el-collapse-item name="3">
<template #title>
<div class="form-collapse-header">
<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>
</template>
<div class="form-collapse-content margin-t-18">
<el-form class="trigger-block margin-b-20" ref="form3" :model="triggerObj" :rules="rules">
<div class="trigger-block-item margin-b-10">
<div>At least</div>
<el-form-item prop="atLeast">
<el-input size="mini" v-model="triggerObj.atLeast" oninput="value=value.replace(/[^\d]/g,'')"></el-input>
</el-form-item>
<div>times within</div>
<el-form-item prop="interval">
<el-input size="mini" v-model="triggerObj.interval" oninput="value=value.replace(/[^\d]/g,'')"></el-input>
</el-form-item>
<el-form-item prop="intervalVal">
<el-select v-model="triggerObj.intervalVal" class="form-trigger__select" placeholder=" " size="mini">
<el-option
v-for="item in intervalList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</div>
<div class="trigger-block-item">
<div>With the counter resetting after no activity for</div>
<el-form-item prop="minute">
<el-input size="mini" v-model="triggerObj.minute" oninput="value=value.replace(/[^\d]/g,'')"></el-input>
</el-form-item>
<div>minutes</div>
</div>
</el-form>
<div class="form-setting__btn1">
<el-button @click="createPolicy">Create & enable rule</el-button>
</div>
</div>
</el-collapse-item>
</el-collapse>
</div>
</div>
</div>
</template>
<script>
import GeneralSettings from '@/components/table/detection/GeneralSettings'
import RuleDefinition from '@/components/table/detection/RuleDefinition'
import { get } from '@/utils/http'
import { api } from '@/utils/api'
import axios from 'axios'
export default {
name: 'DetectionForm',
data () {
return {
activeNames: ['1'],
settingObj: {}, // General Settings即第一步的form表单信息
ruleObj: {}, // 第二步的form表单信息
// 第三步的form表单信息
triggerObj: {
atLeast: '',
interval: '',
intervalVal: '',
minute: '',
finishFlag: false
},
rules: {
atLeast: [
{
required: true,
message: this.$t('validate.required'),
trigger: 'blur'
}
],
interval: [
{
required: true,
message: this.$t('validate.required'),
trigger: 'change'
}
],
intervalVal: [
{
required: true,
message: this.$t('validate.required'),
trigger: 'change'
}
],
minute: [
{
required: true,
message: this.$t('validate.required'),
trigger: 'blur'
}
]
},
intervalList: []
}
},
components: {
GeneralSettings,
RuleDefinition
},
mounted () {
this.getStatistics()
},
methods: {
/** 获取下拉列表数据 */
getStatistics () {
get(api.detection.statistics, { pageSize: -1 }).then(response => {
if (response.code === 200) {
this.intervalList = response.data.intervalList || []
} else {
console.error(response)
}
}).finally(() => {
})
},
/** 获取General Settings折叠板form数据 */
getFormSetting (data) {
this.handleActiveNames('1', this.activeNames)
this.settingObj = JSON.parse(JSON.stringify(data))
},
/** 获取Rule Definition折叠板form数据 */
getRuleObj (data) {
this.handleActiveNames('2', this.activeNames)
this.ruleObj = JSON.parse(JSON.stringify(data))
},
/** 自动展开收起折叠板 */
handleActiveNames (name, arr) {
const list = arr
list.splice(list.indexOf(name), 1)
this.activeNames = []
list.forEach(t => {
this.activeNames.push(t)
})
},
/** 创建policy */
createPolicy () {
const self = this
const settingLen = Object.keys(this.settingObj).length
const ruleLen = Object.keys(this.ruleObj).length
if (settingLen > 0 && ruleLen > 0) {
this.$refs.form3.validate(valid => {
if (valid) {
// 最终提交form
// const formObj = { ...this.settingObj, ...this.ruleObj, ...this.triggerObj }
this.$message({
duration: 2000,
type: 'success',
message: this.$t('tip.saveSuccess')
})
// axios.post('api', formObj).then(response => {
// if (response.data.code === 200) {
// this.$message({
// duration: 2000,
// type: 'success',
// message: this.$t('tip.saveSuccess')
// })
//
// this.$router.push({
// path: '/detectionNew',
// query: {
// t: +new Date()
// }
// })
// } else {
// this.$message.error(this.errorMsgHandler(response))
// }
// }).catch(e => {
// console.error(e)
// this.$message.error(this.errorMsgHandler(e))
// }).finally(() => {
// //
// })
}
})
} else if (settingLen === 0) {
this.handleFormError('1')
} else if (ruleLen === 0) {
this.handleFormError('2')
}
},
handleFormError (name) {
const list = this.activeNames
list.push(name)
this.activeNames = []
list.forEach(t => {
this.activeNames.push(t)
})
this.$message.error('请确保信息填写完整')
}
}
}
</script>

View File

@@ -0,0 +1,175 @@
<template>
<el-table
id="detectionTable"
class="detection-table"
ref="dataTable"
:data="tableData"
height="100%"
border
empty-text=" "
@header-dragend="dragend"
@sort-change="tableDataSort"
@selection-change="selectionChange"
@row-dblclick="rowDoubleClick"
>
<el-table-column
:resizable="false"
align="center"
type="selection"
:selectable="selectable"
width="55">
</el-table-column>
<el-table-column
v-for="(item, index) in customTableTitles"
:key="`col-${index}`"
:fixed="item.fixed"
:label="item.label"
:min-width="`${item.minWidth}`"
:prop="item.prop"
:resizable="true"
:sort-orders="['ascending', 'descending']"
:sortable="item.sortable"
:width="`${item.width}`"
class="data-column"
>
<template #header>
<span class="data-column__span">{{ item.label }}</span>
<div class="col-resize-area"></div>
</template>
<template #default="scope" :column="item">
<template v-if="item.prop === 'name'">
<template v-if="scope.row.i18n">
<span :title="scope.row[item.prop]">{{ $t(scope.row.i18n) }}</span>
</template>
<template v-else-if="scope.row.name">
<span :title="scope.row[item.prop]">{{ scope.row.name }}</span>
</template>
<template v-else>
<span>-</span>
</template>
</template>
<template v-else-if="item.prop === 'status'">
<div :class="`detection-tag-status${scope.row[item.prop]}`">
{{ switchStatus(scope.row[item.prop]) }}
</div>
</template>
<template v-else-if="item.prop === 'description'">
<div style="padding-right: 20px">{{ scope.row[item.prop] }}</div>
</template>
<template v-else-if="item.prop === 'dimensions' && scope.row[item.prop]">
<span class="detection-tag-blue">{{ scope.row[item.prop] }}</span>
</template>
<template v-else-if="item.prop === 'library' && scope.row[item.prop]">
<span class="detection-table-library">{{ scope.row[item.prop] }}</span>
</template>
<span v-else>{{ scope.row[item.prop] || '-' }}</span>
</template>
</el-table-column>
<template v-slot:empty>
<div class="table-no-data" v-if="isNoData">
<div class="table-no-data__title">{{ $t('npm.noData') }}</div>
</div>
</template>
</el-table>
</template>
<script>
import table from '@/mixins/table'
import { dateFormatByAppearance } from '@/utils/date-util'
import { switchStatus } from '@/utils/tools'
export default {
name: 'DetectionTable',
props: {
isNoData: {
type: Boolean,
default: false
}
},
mixins: [table],
data () {
return {
tableTitle: [
{
label: this.$t('knowledge.status'),
prop: 'status',
show: true,
minWidth: 70
},
{
label: 'ID',
prop: 'ruleId',
show: true,
minWidth: 65
},
{
label: this.$t('config.roles.name'),
prop: 'name',
minWidth: 90,
show: true
},
{
label: this.$t('overall.category'),
prop: 'category',
minWidth: 140,
show: true
},
{
label: this.$t('overall.type'),
prop: 'eventType',
minWidth: 145,
show: true
},
{
label: this.$t('overall.remark'),
prop: 'description',
minWidth: 205,
show: true
},
{
// label: this.$t('config.user.createTime'),
label: 'Dimensions',
prop: 'dimensions',
minWidth: 204,
show: true
},
{
// label: this.$t('config.user.createTime'),
label: 'Library',
prop: 'library',
minWidth: 204,
show: true
}
]
}
},
watch: {
tableData: {
immediate: true,
deep: true,
handler (n) {
if (n) {
n.forEach(t => {
if (t.ruleType === 'indicator_match') {
t.library = t.ruleConfig.knowledge.name
} else if (t.ruleType === 'threshold') {
t.dimensions = t.ruleConfig.dimensions
}
})
}
}
}
},
methods: {
dateFormatByAppearance,
switchStatus,
rowDoubleClick (data) {
this.$emit('rowDoubleClick', data)
}
}
}
</script>
<style lang="scss">
</style>

View File

@@ -0,0 +1,77 @@
<template>
<div>
<div class="top-tools__left">
<button
id="knowledge-base-add"
:title="$t('knowledgeBase.createKnowledgeBase')"
class="top-tool-btn margin-r-10 top-tool-btn--create"
@click="onCreate"
style="width:72px;">
<i class="cn-icon-xinjian cn-icon"></i>
<span>{{ $t('overall.create') }}</span>
</button>
<!-- <button-->
<!-- id="knowledge-base-edit"-->
<!-- :title="$t('knowledgeBase.editKnowledgeBase')"-->
<!-- class="top-tool-btn margin-r-10"-->
<!-- style="width:72px;">-->
<!-- <i class="cn-icon-edit cn-icon" ></i>-->
<!-- <span>{{$t('overall.edit')}}</span>-->
<!-- </button>-->
<button
:disabled="disableDelete"
id="knowledge-base-delete"
:title="$t('knowledgeBase.deleteKnowledgeBase')"
class="top-tool-btn margin-r-10"
@click="onDelete"
style="width:72px;">
<i class="cn-icon-delete cn-icon"></i>
<span>{{ $t('overall.delete') }}</span>
</button>
<div class="top-tool-search margin-l-10">
<el-input v-model="keyWord" size="small" style="height: 28px;" @keyup.enter="onSearch"></el-input>
<button
class="top-tool-btn top-tool-btn--search"
style="border-radius: 0 2px 2px 0 !important;"
@click="onSearch">
<i class="el-icon-search"></i>
</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'DetectionTools',
props: {
disableDelete: {
type: Boolean,
default: true
}
},
data () {
return {
keyWord: ''
}
},
methods: {
onSearch () {
this.$emit('search', this.keyWord)
},
onCreate () {
this.$emit('create')
},
onDelete (data) {
this.$emit('delete', data)
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,301 @@
<template>
<div class="detection">
<div class="detection-title">
<span>{{ $t('overall.detections') }}</span>
<span class="detection-title-label">
60 Polices created(200 max | 32 polices enabled(100 max)
</span>
</div>
<div class="detection-content">
<div class="detection-filter">
<detection-filter></detection-filter>
</div>
<div class="detection-block">
<detection-tools
@delete="toDelete"
@create="onCreate"
@search="onSearch"
:disableDelete="disableDelete"/>
<div class="detection-table" style="position: relative">
<loading :loading="loading"></loading>
<detection-table
ref="dataTable"
height="100%"
:api="url"
:isNoData="isNoData"
:custom-table-title="tools.customTableTitle"
:table-data="tableData"
:is-selected-status="isSelectedStatus"
:all-count="18"
@selectionChange="selectionChange"
@reload="reloadRowList"
@toggleLoading="toggleLoading"
@rowDoubleClick="onRowDoubleClick"
></detection-table>
</div>
<div class="knowledge-pagination">
<pagination ref="pagination" :page-obj="pageObj" :table-id="tableId" @pageNo='pageNo' @pageSize='pageSize'></pagination>
</div>
</div>
</div>
</div>
<el-dialog
v-model="showConfirmDialog"
:title="$t('overall.hint')"
width="480px"
custom-class="del-model-hint"
:before-close="handleClose">
<div class="dialog-message">{{ $t('knowledge.deleteDataHint') }}</div>
<el-table v-model="delItemList"
ref="delDataTable"
:data="batchDeleteObjs"
@selection-change="secondSelectionChange"
height="156px"
width="100%"
class="dialog-table"
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;">
<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="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>
<template #footer>
<span class="dialog-footer">
<el-button @click="showConfirmDialog = false">{{ $t('overall.cancel') }}</el-button>
<el-button type="primary" @click="submit">{{ $t('tip.confirm') }}</el-button>
</span>
</template>
</el-dialog>
<div class="detection-drawer">
<el-drawer v-model="showDrawer" :with-header="false">
<detection-drawer :drawer-info="drawerInfo"></detection-drawer>
</el-drawer>
</div>
</template>
<script>
import DetectionFilter from '@/views/detectionsNew/DetectionFilter'
import DetectionTools from '@/views/detectionsNew/DetectionTools'
import DetectionTable from '@/views/detectionsNew/DetectionTable'
import { api } from '@/utils/api'
import dataListMixin from '@/mixins/data-list'
import DetectionDrawer from '@/views/detectionsNew/DetectionDrawer'
import axios from 'axios'
export default {
name: 'Index',
components: {
DetectionFilter,
DetectionTools,
DetectionTable,
DetectionDrawer
},
mixins: [dataListMixin],
data () {
return {
// url: api.detection.list,
url: api.knowledgeBase,
listUrl: api.detection.list,
tableId: 'detectionTable',
isNoData: false,
tableData: [],
isSelectedStatus: false,
batchDeleteObjs: [], // 待删除列表
secondBatchDeleteObjs: [],
disableDelete: true,
showConfirmDialog: false,
delItemList: [],
showDrawer: false,
drawerInfo: {}
}
},
methods: {
onSearch () {
// todo 暂时禁用,后续再开发时解禁
// const params = {
// ...this.filterParams,
// name: this.keyWord
// }
// this.clearList()
// this.search(params)
// this.$refs.knowledgeFilter.reloadFilter(this.checkedCategoryIds, this.checkedStatusIds)
},
toDelete (data) {
// todo 暂时禁用,后续再开发时解禁
// if (data && data.ruleId) {
// this.secondBatchDeleteObjs = []
// this.batchDeleteObjs = []
// this.secondBatchDeleteObjs.push(data)
// this.batchDeleteObjs.push(data)
// }
// this.showDelDialog()
},
onCreate () {
// todo 暂时禁用,后续再开发时解禁
// this.$router.push({
// path: '/detection/policies/create',
// query: {
// t: +new Date()
// }
// })
},
selectionChange (objs) {
this.batchDeleteObjs = []
objs.forEach(obj => {
const delObj = this.batchDeleteObjs.find(item => item.ruleId === obj.ruleId)
if (delObj === undefined) {
this.batchDeleteObjs.push(obj)
}
})
this.disableEdit = this.batchDeleteObjs.length !== 1
this.disableDelete = this.batchDeleteObjs.length < 1
},
reloadRowList () {
this.getTableData()
},
toggleLoading () {
},
showDelDialog () {
this.showConfirmDialog = true
this.$nextTick(() => {
this.batchDeleteObjs.forEach((item) => {
this.$refs.delDataTable.toggleRowSelection(item, true)
})
})
},
handleClose () {
this.showConfirmDialog = false
},
secondSelectionChange (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 () {
this.delBatchDetection()
this.showConfirmDialog = false
},
delBatchDetection () {
const ids = []
if (this.secondBatchDeleteObjs && this.secondBatchDeleteObjs.length > 0) {
this.secondBatchDeleteObjs.forEach(item => {
ids.push(item.ruleId)
})
}
if (ids.length === 0) {
this.$alert(this.$t('tip.pleaseSelect'), {
confirmButtonText: this.$t('tip.yes'),
type: 'warning'
}).catch(() => {
})
} else {
// todo 调用接口删除
// this.toggleLoading(true)
// axios.delete(api.detection.delete + '?ruleIds=' + ids).then(response => {
// if (response.data.code === 200) {
// this.delFlag = true
// this.$message({ duration: 2000, type: 'success', message: this.$t('tip.deleteSuccess') })
// this.secondBatchDeleteObjs.forEach((item) => {
// this.$refs.delDataTable.toggleRowSelection(item, false)
// })
// this.$refs.knowledgeFilter.reloadFilter()
// this.secondBatchDeleteObjs = []
// this.batchDeleteObjs = []
// delete this.searchLabel.category
// delete this.searchLabel.source
// this.getTableData()
// } else {
// this.$message.error(response.data.message)
// }
// }).finally(() => {
// this.toggleLoading(false)
// if (this.isSelectedStatus != undefined) {
// this.isSelectedStatus = false
// this.disableDelete = true
// this.secondBatchDeleteObjs = []
// this.batchDeleteObjs = []
// this.showConfirmDialog = false
// }
// })
}
},
onRowDoubleClick (data) {
// todo 暂时禁用,后续再开发时解禁
// this.showDrawer = true
// this.drawerInfo = data
}
}
}
</script>
<style lang="scss" scoped>
.detection {
padding: 20px;
height: 100%;
overflow: auto;
.detection-title {
font-family: NotoSansHans-Black;
line-height: 24px;
font-size: 24px;
color: #353636;
font-weight: 900;
display: flex;
flex-direction: column;
.detection-title-label {
font-family: NotoSansSChineseRegular;
font-size: 12px;
color: #717171;
letter-spacing: 0;
line-height: 18px;
font-weight: 400;
margin-top: 5px;
}
}
.detection-content {
margin-top: 15px;
width: 100%;
height: calc(100% - 96px);
display: flex;
.detection-filter {
width: 320px;
height: calc(100% + 34px);
background: #FFFFFF;
border: 1px solid rgba(226, 229, 236, 1);
border-radius: 4px;
margin-right: 20px;
}
.detection-block {
width: calc(100% - 340px);
.detection-table {
width: 100%;
height: calc(100% - 44px);
border-radius: 4px;
margin-top: 12px;
}
}
}
}
</style>

View File

@@ -0,0 +1,150 @@
<template >
<div class="entity-filter-case">
<div class="filter-case__header">{{$t('entities.filter')}}</div>
<div
class="entity-filter"
v-for="(filters, index) in filterData"
:key="index"
>
<div class="filter__header">{{filters.title}}</div>
<div class="filter__body">
<div class="filter__row" v-for="(item, i) in filters.data" :key="i">
<el-popover popper-class="filter__row-popover" placement="right-start" :width="440" v-model:visible="item.showTopTen">
<template #reference>
<div class="filter__row-popover" @click="showTopDialog(i, item, filters)">
<div class="row__label">
<i :class="item.icon"></i>
<span>{{item.label}}</span>
</div>
<div class="row__value">
<loading :loading="loadingLeft" size="small"></loading>
<span>{{item.value}}</span>
</div>
</div>
</template>
<entity-top
ref="entityTopTenPop"
:loading="loading"
:popover-data="popoverData"
:item-data="itemData"
:total-count="totalCount"
:top-column="item.topColumn"
@filter="filter"
></entity-top>
</el-popover>
</div>
</div>
</div>
</div>
</template>
<script>
import EntityTop from '@/views/entityExplorer/EntityTop'
import { get } from '@/utils/http'
import { api } from '@/utils/api'
import Loading from '@/components/common/Loading'
import { getSecond } from '@/utils/date-util'
export default {
name: 'EntityFilter',
components: {
Loading,
EntityTop
},
props: {
filterData: Array,
q: String,
timeFilter: Object,
loadingLeft: Boolean
},
data () {
return {
topList: 'list',
topData: [],
entityTopTenData: [],
currentColumn: {},
totalCount: 0,
loading: false,
popoverData: [],
itemData: {}
}
},
watch: {
currentColumn (n, o) {
if (n.column === 'dnsServerOrgCount') {
this.totalCount = this.filterData[3].orgTotalCount
} else if (n.column === 'dnsServerSoftwareCount') {
this.totalCount = this.filterData[3].softwareTotalCount
} else if (n.column === 'dnsServerOsCount') {
this.totalCount = this.filterData[3].osTotalCount
} else if (n.column === 'categoryDistinctCount' && n.type === 'app') {
this.totalCount = this.filterData[1].totalCount
} else {
let count = 0
this.filterData.forEach(f => {
const filter = f.data.some(d => d.column === n.column)
if (filter) {
count = f.totalCount
}
})
this.totalCount = count
}
}
},
methods: {
showTopDialog (i, item, filter) {
if (this.currentColumn.column === item.column && item.showTopTen) {
item.showTopTen = false
return
}
this.filterData.forEach(f => {
f.data.forEach(ff => {
ff.showTopTen = false
})
})
item.showTopTen = true
this.currentColumn = {
column: item.column,
type: filter.type
}
const queryParams = {
q: this.q,
entityType: filter.type,
column: item.topColumn,
top: 10,
startTime: getSecond(this.timeFilter.startTime),
endTime: getSecond(this.timeFilter.endTime)
}
this.loading = true
this.popoverData = []
this.itemData = {}
get(api.filterTop, queryParams).then(response => {
if (response.code === 200) {
if (this.currentColumn.column === item.column) {
if (filter.type === 'dns') {
this.popoverData = response.data.result.filter(f => {
return f.count > 0
})
} else {
this.popoverData = response.data.result
}
this.itemData = item
}
} else {
this.popoverData = []
this.itemData = item
}
this.loading = false
}).catch(e => {
this.popoverData = []
this.itemData = item
this.loading = false
})
},
filter (name, topData) {
this.showTopDialog('', topData)
this.$emit('filter', name, topData)
}
}
}
</script>