Compare commits

..

26 Commits

Author SHA1 Message Date
chenjinsong
cdd3557bbe CN-1388 fix: 调整实体列表等待交互和接口请求顺序等
1.调整接口请求顺序为list > 左侧筛选 > 顶部统计;
2.优化左侧筛选loading交互;
3.将默认每页条数改为10,可选条数由20,30,50改为10,20,50
2023-10-14 15:22:38 +08:00
chenjinsong
b44a99f354 CN-1372 fix: 曲线图步长优化后,恢复默认时间范围为1小时;修复短时间范围时横坐标显示不友好的问题 2023-10-12 17:05:05 +08:00
chenjinsong
76c0c081e5 CN-1373 fix: 去掉链路网格图整行或整列全没数据时会隐藏当前行列的逻辑 2023-10-11 18:03:14 +08:00
chenjinsong
6ac9b71b0e CN-1373 fix: 修复下一跳下钻后参数大小写变更的问题 2023-10-11 16:07:49 +08:00
chenjinsong
41f161e301 CN-1373 fix: 适配链路配置逻辑变更 2023-10-11 14:45:03 +08:00
chenjinsong
e926163da4 CN-1373 fix: 适配链路配置逻辑变更 2023-10-11 14:38:42 +08:00
chenjinsong
7b7c2456e2 fix: 删除部分无用路由 2023-10-10 15:36:57 +08:00
chenjinsong
4fb8ab2996 fix: 删除部分无用组件 2023-10-10 15:36:21 +08:00
chenjinsong
be6a1e9fb6 Merge remote-tracking branch 'origin/dev-23.09' into dev-23.09 2023-10-10 15:25:02 +08:00
chenjinsong
faae781f42 fix: 修复依赖问题 2023-10-10 15:24:53 +08:00
唐浩
216c6eaab2 Update .gitlab-ci.yml file 2023-10-10 06:45:25 +00:00
chenjinsong
f939d344fa fix: 修复依赖问题 2023-10-10 14:24:21 +08:00
chenjinsong
7b93068d75 feat: 适配Poc的变更:默认时间范围改为1小时、网络质量评分阈值变更 2023-10-10 10:57:35 +08:00
陈劲松
5dedc6d12d Merge branch 'cherry-pick-8cc6edbd' into 'dev-23.09'
fix: 实体搜索允许通过语言添加俄语

See merge request cyber-narrator/cn-ui!46
2023-10-10 02:50:55 +00:00
刘洪洪
d0cad48f6a fix: 实体搜索允许通过语言添加俄语
(cherry picked from commit 8cc6edbdd4)
2023-10-10 02:50:46 +00:00
陈劲松
1fa8f157ed Merge branch 'cherry-pick-6f72b225' into 'dev-23.09'
fix: 修复实体搜索内容包含非英文时会弹窗提示非法字符串的问题

See merge request cyber-narrator/cn-ui!45
2023-10-10 02:50:16 +00:00
刘洪洪
9a3e4e07ab fix: 修复实体搜索内容包含非英文时会弹窗提示非法字符串的问题
(cherry picked from commit 6f72b2258f)
2023-10-10 02:50:09 +00:00
陈劲松
817d593c78 Merge branch 'cherry-pick-cc61cbc0' into 'dev-23.09'
fix: 修复networkoverview line去掉尾部4个0值的代码导致测试用例不通过的问题

See merge request cyber-narrator/cn-ui!44
2023-10-07 02:39:59 +00:00
chenjinsong
ed8be53798 fix: 修复networkoverview line去掉尾部4个0值的代码导致测试用例不通过的问题
(cherry picked from commit cc61cbc05c)
2023-10-07 02:39:52 +00:00
陈劲松
d14f34ec9f Merge branch 'cherry-pick-7cc2439d' into 'dev-23.09'
fix: 去除无效打印

See merge request cyber-narrator/cn-ui!43
2023-10-07 02:39:33 +00:00
刘洪洪
6cb27b72af fix: 去除无效打印
(cherry picked from commit 7cc2439dd2)
2023-10-07 02:39:24 +00:00
陈劲松
4abe0ce74c Merge branch 'cherry-pick-8ba06205' into 'dev-23.09'
CN-1363 fix: 修复单引号内不能包含逗号的问题,以及语句中引号内包含逗号后添加连接has函数的逻辑

See merge request cyber-narrator/cn-ui!42
2023-10-07 02:38:35 +00:00
刘洪洪
272fd2a5d2 CN-1363 fix: 修复单引号内不能包含逗号的问题,以及语句中引号内包含逗号后添加连接has函数的逻辑
(cherry picked from commit 8ba062054c)
2023-10-07 02:38:15 +00:00
chenjinsong
fc56a1fc0c fix: networkoverview的曲线图去掉尾部最多4个0值点 2023-09-28 13:23:28 +08:00
chenjinsong
ccd2b3d08b fix: 修复实体列表流量速率显示为横杠的问题 2023-09-28 10:01:13 +08:00
chenjinsong
60bbb8b165 fix: 修复实体列表实体属性为空串时,不显示'-'的问题 2023-09-27 19:53:09 +08:00
110 changed files with 4843 additions and 7314 deletions

View File

@@ -1,5 +1,5 @@
const BASE_CONFIG = {
baseUrl: 'http://192.168.44.54:8090/',
version: '23.10',
baseUrl: 'http://192.168.44.54:8091/',
version: '23.06',
apiVersion: 'v1'
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -83,71 +83,6 @@
.entity-detail-event-block {
width: calc(100% - 2px);
.behavior-pattern {
height:100% ;
position: relative;
display:flex;
flex-direction: row;
.behavior-pattern-legend {
display:flex;
flex-direction: column;
justify-content: center;
height: 100%;
padding:10px 18px 10px 18px;
width:400px;
.behavior-pattern-legend__item {
display:flex;
flex-direction: row;
font-size: 12px;
color: #353636;
line-height: 12px;
margin-bottom:11px;
.legend-icon {
width: 8px;
height: 8px;
margin: 3px 8px 0 0;
border-radius: 1px;;
}
.legend-name {
width:180px;
font-weight: 400;
}
.legend-value{
display: flex;
justify-content: left;
margin-left:20px;
width:90px;
font-weight: 500;
}
.legend-percent {
margin-left:20px;
width:70px;
justify-content: left;
display: flex;
font-weight: 500;
}
}
}
.behavior-pattern-chart{
height: calc(100% - 50px);
width:calc(100% - 400px);
position: relative
}
.chart-bottom-dot__left {
position: absolute;
height: 0;
width: 195px;
border-bottom: 1px dashed #d3d3d3;
top: 319px;left: 575px;
}
.chart-bottom-dot__right {
position: absolute;
height: 0;
width: 195px;
border-bottom: 1px dashed #d3d3d3;
top: 319px;left: 830px;
}
}
}
.entity-detail-event-error {

View File

@@ -32,7 +32,7 @@
width: 50px;
height: 100%;
margin-right: 8px;
transform: rotate(-45deg) translate(-5px,-15px);
transform: translate(0, -15px);
color: #353636;
font-size: 12px;
}
@@ -128,25 +128,24 @@ $blue: #046ECA;
}
}
}
.link-statistical-dimension {
.row-dot {
margin-top: 5px;
margin-right: 5px;
}
.green-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: #749F4D;
}
.row-dot {
margin-top: 5px;
margin-right: 5px;
}
.red-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: #E26154;
}
.green-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: #749F4D;
}
.red-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: #E26154;
}
.item-popover-up, .item-popover-down {

View File

@@ -31,12 +31,8 @@
line-height: 16px;
margin-right: 10px;
.block-mode-icon1 {
font-size: 14px;
}
.block-mode-icon {
font-size: 15px;
font-size: 16px;
}
}
@@ -212,15 +208,6 @@
font-weight: 500;
padding: 0 14px;
}
.btn1 {
margin-right: 10px;
.el-button {
background: #F5F6F7;
border: 1px solid rgba(215,215,215,1);
color: #353636;
}
}
}
.form-setting__btn1 {

View File

@@ -101,12 +101,6 @@
justify-content: flex-start;
height: 24px;
line-height: 24px;
.policy-form-item {
.el-form-item__error {
width: 260px;
}
}
}
.el-input--mini .el-input__inner {

View File

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

View File

@@ -59,7 +59,6 @@
width: 5px;
height: 20px;
border-radius: 12px;
margin-top: 2px;
}
.cn-detection__row {
@@ -89,7 +88,6 @@
margin-left: 12px;
font-size: xx-small;
font-weight: bold;
margin-top: -1px;
}
.circle {
@@ -97,7 +95,7 @@
height: 10px;
border: 2px solid #da5656;
border-radius: 10px;
margin-top: 1px;
margin-top: 4px;
margin-right: 12px;
}
@@ -121,12 +119,10 @@
margin-right: 12px;
}
.detection-event-severity-block {
height: 20px;
line-height: 20px;
font-size: 12px;
color: #046EC9;
font-weight: 500;
padding: 0 10px;
padding: 2px 10px;
background: rgba(56,172,210,0.10);
border: 1px solid #ADC7DB;
box-shadow: 0 2px 4px 0 rgba(51,51,51,0.02);
@@ -266,8 +262,3 @@
color: #FFD82D !important;
}
}
.detection-list-icon {
margin-top: 1px;
font-size: 16px !important;
}

View File

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

View File

@@ -61,19 +61,22 @@
.detection-tag-status0 {
font-weight: 500;
font-family: NotoSansHans-Medium;
background: rgba(113, 113, 113, 0.12);
color: #717171;
padding: 0 10px;
padding: 0 12px;
}
.detection-tag-status1 {
font-weight: 500;
font-family: NotoSansHans-Medium;
background: rgba(126, 159, 84, 0.12);
color: #7E9F54;
padding: 0 10px;
padding: 0 8px;
}
.detection-table-library {
font-family: NotoSansSChineseRegular;
font-size: 12px;
color: #046ECA;
font-weight: 400;

View File

@@ -4,18 +4,6 @@
width: 100%;
margin-bottom: 10px;
}
.detections {
.entity__pagination{
bottom: 9px;
height: 40px;
width: 100%;
}
.detections__container {
display: flex;
flex-direction: column;
padding-bottom: 20px;
}
}
.detection__list {
display: flex;
flex-direction: column;

View File

@@ -32,6 +32,7 @@
width: calc(100% - 30px);
margin: 0 10px 0 20px;
max-height: 265px;
min-height: 40px;
overflow-y: scroll;
overflow-x: hidden;

View File

@@ -349,24 +349,6 @@
.data-score-green {
background: #749F4D;
}
.score-dot {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: #CBCBCB;
margin-right: 4px;
&.score-dot--red {
background-color: #E26154;
}
&.score-dot--yellow {
background-color: #E5A219;
}
&.score-dot--green {
background-color: #749F4D;
}
}
height:24px;
font-size: 14px;
color: #046ECA;

View File

@@ -21,7 +21,7 @@
.entity-list--list {
display: flex;
flex-direction: column;
height: 100%;
height: auto;
/*overflow: visible;/*overflow: auto;*/
.cn-entity__shadow {

View File

@@ -174,25 +174,6 @@
font-size: 14px;
color: #353636;
font-weight: 400;
.score-dot {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: #CBCBCB;
margin-right: 4px;
&.score-dot--red {
background-color: #E26154;
}
&.score-dot--yellow {
background-color: #E5A219;
}
&.score-dot--green {
background-color: #749F4D;
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
@font-face {
font-family: "cn-icon"; /* Project id 2614877 */
src: url('iconfont.woff2?t=1698229141457') format('woff2'),
url('iconfont.woff?t=1698229141457') format('woff'),
url('iconfont.ttf?t=1698229141457') format('truetype');
src: url('iconfont.woff2?t=1693386443164') format('woff2'),
url('iconfont.woff?t=1693386443164') format('woff'),
url('iconfont.ttf?t=1693386443164') format('truetype');
}
.cn-icon {
@@ -13,18 +13,6 @@
-moz-osx-font-smoothing: grayscale;
}
.cn-icon-indicator-match:before {
content: "\e80c";
}
.cn-icon-threshold:before {
content: "\e80d";
}
.cn-icon-behavior:before {
content: "\e61c";
}
.cn-icon-category-group:before {
content: "\e6c7";
}

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1173,9 +1173,7 @@ export default class Parser {
const arr = []
// 如果出现this.columnList中的字段如IP\Domain\App\Country等则不进行模糊搜索将str返回出去
this.columnList.forEach(item => {
// todo 取消了大小写校验,后续观察是否出现问题
// arr.push(item.label.toLowerCase())
arr.push(item.label)
arr.push(item.label.toLowerCase())
})
// 因为手动输入时可能会输入and所以将操作符的AND转换为and统一处理
@@ -1185,9 +1183,7 @@ export default class Parser {
newStr = newStr.replace(new RegExp(arr[i], 'g'), arr[i])
}
// 检查str字段在arr中是否出现,true为出现过
// todo 取消了大小写校验,后续观察是否出现问题
// const result = arr.some(item => newStr.toLowerCase().includes(item))
const result = arr.some(item => newStr.includes(item))
const result = arr.some(item => newStr.toLowerCase().includes(item))
if (newStr.indexOf(' and ') > -1) {
// 将单引号包裹的and拿出来放到数组tempList里原来的单引号包裹内容用temp即'it is test keyword{键值}'代替
// 再将字符串用and转换为数组遍历数组发现值为temp的获取键值根据键值获取tempList的值组合起来
@@ -1198,7 +1194,7 @@ export default class Parser {
// 将单引号包裹的and内容集合起来
while ((match = regex.exec(newStr)) !== null) {
if (match[1].includes(' and ')) {
if (match[1].includes('and')) {
tempList.push(match[1])
}
}
@@ -1238,8 +1234,7 @@ export default class Parser {
// 不区分大小写用this.columnList里的label
arr.forEach(item => {
if (str.toLowerCase().indexOf(item.toLowerCase()) > -1) {
// todo 记录一下此处取消了转小写转换,后续搜索验证
// str = str.replace(new RegExp(item, 'gi'), item)
str = str.replace(new RegExp(item, 'gi'), item)
if (!operatorList.some(ite => str.includes(ite)) && str.toLowerCase() !== item.toLowerCase()) {
str = this.checkFormatByStr(str)
}

View File

@@ -58,13 +58,24 @@ export default {
*/
setup (props) {
const { query } = useRoute()
const pageSize = ref(defaultPageSize)
// pageSize取值顺序1.url2.缓存3.pageObj4.默认值
const urlPageSize = parseInt(query.pageSize)
const cachePageSize = parseInt(localStorage.getItem(storageKey.pageSize + '-' + localStorage.getItem(storageKey.username) + '-' + props.tableId))
const pageObjPageSize = props.pageObj.pageSize
const pageSize = ref(urlPageSize || cachePageSize || pageObjPageSize || defaultPageSize)
const currentPageNo = ref(props.storePageNoOnUrl ? (query.pageNo || (props.pageObj.pageNo || 1)) : (props.pageObj.pageNo || 1))
return {
pageSize,
currentPageNo
}
},
mounted () {
if (this.postPageSizes && this.postPageSizes.length > 0) {
this.resetPageSizes()
}
this.currentPageNo = parseInt(this.currentPageNo)
this.current(this.currentPageNo)
},
data () {
return {
// pageSize: defaultPageSize,
@@ -147,6 +158,7 @@ export default {
size (val) {
// eslint-disable-next-line vue/no-mutating-props
// this.pageObj.pageNo = 1
this.$emit('scrollbarToTop')
this.$emit('pageSize', val)
this.backgroundColor()
@@ -163,11 +175,12 @@ export default {
wrap.scrollTop = 0
}
})
this.$emit('scrollbarToTop')
})
},
resetPageSizes: function () {
if (this.postPageSizes) {
this.pageSizes = this.postPageSizes.map((item) => {
this.pageSizes = this.postPageSizes.map(item => {
return {
label: item + this.$t('pageSize'),
value: item
@@ -183,20 +196,6 @@ export default {
// }
}
},
mounted () {
if (this.postPageSizes && this.postPageSizes.length > 0) {
this.pageSize = this.postPageSizes[0]
this.resetPageSizes()
} else {
const pageSize = localStorage.getItem(storageKey.pageSize + '-' + localStorage.getItem(storageKey.username) + '-' + this.tableId)
if (pageSize != 'undefined' && pageSize != null) {
this.pageSize = parseInt(pageSize)
}
}
this.currentPageNo = parseInt(this.currentPageNo)
this.current(this.currentPageNo)
},
watch: {
postPageSizes: {
immediate: true,

View File

@@ -254,14 +254,12 @@ export default {
const returnValue = () => {
store.commit('setTimeFilter', { startTime: myStartTime.value, endTime: myEndTime.value, range: dateRangeValue.value })
cancelHttp()
if (rangeHistory.value[0]) {
const d = rangeHistory.value[0]
if (d.start !== myStartTime.value || d.end !== myEndTime.value) {
rangeHistory.value.unshift({
start: myStartTime.value,
end: myEndTime.value
})
}
const obj = rangeHistory.value.find(d => d.start === myStartTime.value && d.end === myEndTime.value)
if (!obj) {
rangeHistory.value.unshift({
start: myStartTime.value,
end: myEndTime.value
})
}
if (!rangeHistory.value[0]) {
rangeHistory.value.unshift({

View File

@@ -99,7 +99,7 @@
</div>
</template>
<template v-else-if="index===2">
<span v-if="route===wholeScreenRouterMapping.dns || !route.startsWith('/panel')">{{ $t(item.value) }}</span>
<span v-if="route===wholeScreenRouterMapping.dns">{{ $t(item.value) }}</span>
<span v-else class="route-menu" @click="jump(route,item.value,'',3)">{{ $t(item.value) }}</span>
</template>
<!-- index=0和index=1的点击跳转由breadcrumb里的数据控制 -->
@@ -392,7 +392,7 @@ export default {
this.dnsRcodeMapData = await getDnsMapData('dnsRcode')
}
}
const path = this.$route.path
let path = this.$route.path;
if (path.indexOf('panel') > -1 && path.indexOf('linkMonitor') === -1) {
await this.initDropdownList()
}
@@ -455,6 +455,22 @@ export default {
type: entityGraphMenu.type
})
return true
} else if (this.route === '/detectionsNew') {
const detectionMenu = menus.find(m => m.route === '/detection')
const policyMenu = menus.find(m => m.route === '/detectionsNew')
breadcrumb.push({
code: detectionMenu.code,
value: detectionMenu.i18n ? this.$t(detectionMenu.i18n) : detectionMenu.name,
route: detectionMenu.route,
type: detectionMenu.type
})
breadcrumb.push({
code: policyMenu.code,
value: policyMenu.i18n ? this.$t(policyMenu.i18n) : policyMenu.name,
route: policyMenu.route,
type: policyMenu.type
})
return true
}
const menu = menus.find(m => m.route === this.route)
if (menu) {
@@ -770,7 +786,6 @@ export default {
t: +new Date()
}
})
return
} else if (opeType === 3) {
this.$router.push({
query: {
@@ -779,7 +794,6 @@ export default {
t: +new Date()
}
})
return
} else if (opeType !== 4) {
this.$router.push({
query: {
@@ -789,12 +803,11 @@ export default {
t: +new Date()
}
})
return
}
if (route === this.route) {
/* if (route === this.route) {
this.refresh()
return
}
} */
if (route) {
this.$router.push({
path: route,

View File

@@ -26,7 +26,7 @@
class="right-box__select"
clearable
collapse-tags
placeholder=" "
placeholder=""
popper-class="right-box-select-dropdown prevent-clickoutside"
size="small"
@change="()=>{ this.$forceUpdate() }">

View File

@@ -45,7 +45,7 @@
class="right-box__select"
clearable
collapse-tags
placeholder=" "
placeholder=""
popper-class="right-box-select-dropdown prevent-clickoutside"
size="small"
@change="()=>{ this.$forceUpdate() }">
@@ -61,7 +61,7 @@
class="right-box__select"
clearable
collapse-tags
placeholder=" "
placeholder=""
popper-class="right-box-select-dropdown prevent-clickoutside"
size="small">
<template v-for="lang in langData" :key="lang.value">
@@ -70,20 +70,20 @@
</el-select>
</el-form-item>
<!--theme-->
<!-- <el-form-item :label="$t('config.user.theme')" prop="i18n">
<el-form-item :label="$t('config.user.theme')" prop="i18n">
<el-select id="account-input-roleIds"
v-model="editObject.theme"
class="right-box__select"
clearable
collapse-tags
placeholder=" "
placeholder=""
popper-class="right-box-select-dropdown prevent-clickoutside"
size="small">
<template v-for="theme in themeData" :key="theme.value">
<el-option :label="theme.label" :value="theme.value"></el-option>
</template>
</el-select>
</el-form-item>-->
</el-form-item>
<!--enable-->
<el-form-item :label="$t('config.user.enable')">
<el-switch

View File

@@ -4,60 +4,56 @@
<div class="block-mode">
<!--todo 图标没有后期换-->
<div class="block-mode-left">
<i class="cn-icon cn-icon-indicator-match block-mode-icon1"></i>
<i class="cn-icon cn-icon-setting2 block-mode-icon"></i>
</div>
<div class="block-mode-right" style="position:relative;">
<div class="block-mode-title">{{ $t('detection.policy.indicatorMatch') }}</div>
<div class="block-mode-right">
<div class="block-mode-title">Indicator Match</div>
<div class="block-mode-content">
{{ $t('detection.policy.indicatorMatchIntroduce') }}
<div v-if="language==='cn'" style="color: rgba(0,0,0,0)">0</div>
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)">{{ $t('overall.select') }}
@click="selectMode(detectionRuleType.indicator)">select
</div>
</div>
</div>
<div class="block-mode" style="cursor: no-drop;">
<div class="block-mode">
<!--todo 图标没有后期换-->
<div class="block-mode-left">
<i class="cn-icon cn-icon-threshold block-mode-icon"></i>
<i class="cn-icon cn-icon-setting2 block-mode-icon"></i>
</div>
<div class="block-mode-right">
<div class="block-mode-title">{{ $t('detection.policy.threshold') }}</div>
<div class="block-mode-title">Threshold</div>
<div class="block-mode-content">
{{ $t('detection.policy.thresholdIntroduce') }}
Aggregate query results to detect when number of matches exceeds threshold.
</div>
<!--todo 当前版本暂时不可选择-->
<div style="cursor: no-drop;" :class="settingObj.ruleType===detectionRuleType.threshold?'block-mode-btn-active':'block-mode-btn'">
{{ $t('overall.select') }}
<div :class="settingObj.ruleType===detectionRuleType.threshold?'block-mode-btn-active':'block-mode-btn'"
@click="selectMode(detectionRuleType.threshold)">select
</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="$t('overall.category')" prop="category" class="form-setting__block margin-b-20">
<el-select :disabled="settingObj.ruleId" v-model="settingObj.category" class="form-setting__select" placeholder=" " size="mini" @change="changeEditFlag">
<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="$t(item.label)"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<!--type-->
<el-form-item :label="$t('overall.type')" prop="eventType" class="form-setting__block margin-b-20">
<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 eventTypeList"
v-for="item in typeList"
:key="item.value"
:label="item.label"
:value="item.value"
@@ -66,32 +62,32 @@
</el-form-item>
<!--name-->
<el-form-item :label="$t('overall.name')" prop="name" class="form-setting__block margin-b-20">
<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"
@blur="changeEditFlag"
@input="changeEditFlag"
class="form-setting__input" />
</el-form-item>
<!--Description-->
<div class="form-setting__block margin-b-20">
<div class="block-title">{{ $t('config.dataSet.description') }}</div>
<div class="block-title">Description</div>
<el-input
maxlength="255"
show-word-limit
v-model="settingObj.description"
type="textarea"
resize='none'
@blur="changeEditFlag"
@input="changeEditFlag"
class="form-setting__textarea" />
</div>
</el-form>
<div class="form-setting__block margin-b-20">
<div class="block-title">{{ $t('detection.create.policyStatus') }}</div>
<div class="block-title">Policy Status</div>
<el-switch
v-model="settingObj.status"
@change="changeEditFlag"
@@ -99,36 +95,30 @@
inactive-color="#C0CEDB"
:active-value="1"
:inactive-value="0"
:active-text="$t(switchStatus(settingObj.status))"/>
:active-text="switchStatus(settingObj.status)"/>
</div>
<div class="form-setting__btn">
<el-button @click="onContinue">{{ $t('detection.create.continue') }}</el-button>
<el-button @click="onContinue">Continue</el-button>
</div>
</div>
</template>
<script>
import { detectionRuleType, storageKey, detectionUnitList } from '@/utils/constants'
import { detectionRuleType } from '@/utils/constants'
import { switchStatus } from '@/utils/tools'
import axios from 'axios'
import { api } from '@/utils/api'
export default {
name: 'GeneralSettings',
props: {
editObj: {
type: Object
},
isComplete: {
type: Boolean
}
},
data () {
return {
detectionRuleType,
categoryList: [],
eventTypeList: [],
typeList: [],
settingObj: {
ruleType: detectionRuleType.indicator,
ruleType: detectionRuleType.threshold,
category: '',
eventType: '',
name: '',
@@ -159,21 +149,6 @@ export default {
trigger: 'blur'
}
]
},
language: 'en'
}
},
watch: {
editObj (newVal) {
if (newVal.ruleId) {
this.settingObj = JSON.parse(JSON.stringify({ ...newVal }))
this.settingObj.editFlag = false
this.settingObj.saveFlag = true
}
},
isComplete (newVal) {
if (!newVal) {
this.onContinue()
}
}
},
@@ -183,9 +158,15 @@ export default {
methods: {
switchStatus,
initData () {
this.language = localStorage.getItem(storageKey.language) || 'en'
this.categoryList = detectionUnitList.categoryList
this.eventTypeList = detectionUnitList.eventTypeList
axios.get(api.detection.statistics, { params: { pageSize: -1 } }).then(response => {
if (response.status === 200) {
this.categoryList = response.data.data.categoryList || []
this.typeList = response.data.data.typeList || []
} else {
console.error(response.data)
}
}).finally(() => {
})
},
selectMode (ruleType) {
this.settingObj.ruleType = ruleType
@@ -193,16 +174,9 @@ export default {
},
changeEditFlag () {
this.settingObj.editFlag = true
if (this.settingObj.ruleType && this.settingObj.category && this.settingObj.eventType && this.settingObj.name) {
this.settingObj.settingNoContinue = true
this.onContinue()
}
},
/** 点击继续,进行第二步 */
onContinue (e) {
if (e) {
delete this.settingObj.settingNoContinue
}
onContinue () {
this.$refs.form.validate(valid => {
if (valid) {
this.settingObj.editFlag = false
@@ -214,3 +188,7 @@ export default {
}
}
</script>
<style lang="scss">
</style>

View File

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

View File

@@ -4,7 +4,7 @@
<div>
<el-form ref="form" :model="thresholdRuleObj" label-position="top" :rules="rules">
<!--source-->
<el-form-item :label="$t('config.user.source')" prop="dataSource" class="form-setting__block margin-b-20">
<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"
@@ -18,7 +18,7 @@
<!--Dimensions-->
<div class="form-setting__block margin-b-20">
<div class="block-title">{{ $t('detection.create.dimensions') }}</div>
<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>
@@ -52,7 +52,7 @@
size="mini"
oninput="value=value.replace(/[^\d]/g,'')"
v-model="item.value"></el-input>
<i class="cn-icon cn-icon-close" @click="delFilterItem(index, item)"></i>
<i class="cn-icon cn-icon-close" @click="delFilterItem(index)"></i>
</div>
<div style="height: 10px;"></div>
@@ -65,7 +65,7 @@
<!--Condition模块-->
<div class="form-setting__block margin-b-20">
<div class="block-title">{{ $t('detection.create.condition') }}</div>
<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">
@@ -97,7 +97,7 @@
v-for="item in metricList"
:key="item.label"
:label="item.label"
:value="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
@@ -144,7 +144,7 @@
</el-form>
<div class="condition-add" @click="addCondition">
<i class="cn-icon cn-icon-add"></i>{{ $t('detection.create.addCondition') }}
<i class="cn-icon cn-icon-add"></i>Add Condition
</div>
</div>
</div>
@@ -154,7 +154,6 @@
<history-top-keys
v-if="showDrawer"
:showDrawer="showDrawer"
:delKeyId="delKeyId"
@closeDrawer="onCloseDrawer"
@keyRowClick="getRowClick"
></history-top-keys>
@@ -164,24 +163,24 @@
<div v-if="mySettingObj.ruleType===detectionRuleType.indicator">
<el-form ref="form" :model="indicatorRuleObj" label-position="top" :rules="rules">
<!--Source-->
<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" @change="handleParamsComplete">
<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="$t(item.label)"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<!--Library-->
<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" filterable placeholder=" " size="mini" @change="handleParamsComplete">
<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.name"
:label="item.label"
:value="item.knowledgeId"
/>
</el-select>
@@ -189,7 +188,7 @@
<!--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" @change="handleParamsComplete">
<el-select v-model="indicatorRuleObj.level" class="condition__select form-setting__select" placeholder=" " size="mini">
<template #prefix>
<div
class="condition__select__icon"
@@ -199,7 +198,7 @@
<el-option
v-for="item in levelList"
:key="item.label"
:label="$t(item.label)"
:label="item.label"
:value="item.value"
/>
</el-select>
@@ -208,29 +207,22 @@
</div>
<div class="form-setting__btn">
<el-button @click="onContinue">{{ $t('detection.create.continue') }}</el-button>
<el-button @click="onContinue">Continue</el-button>
</div>
</div>
</template>
<script>
import HistoryTopKeys from '@/components/table/detection/HistoryTopKeys'
import { eventSeverityColor, detectionRuleType, detectionUnitList, securityLevel } from '@/utils/constants'
import axios from 'axios'
import _ from 'lodash'
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
},
editObj: {
type: Object
},
isComplete: {
type: Boolean
}
},
components: {
@@ -240,31 +232,11 @@ export default {
settingObj: {
immediate: true,
deep: true,
handler (newVal) {
handler (newVal, oldVal) {
if (!newVal.editFlag && newVal.saveFlag) {
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)
}
}
}
},
isComplete (newVal) {
if (!newVal) {
this.onContinue()
}
}
},
data () {
@@ -272,7 +244,7 @@ export default {
eventSeverityColor,
detectionRuleType,
mySettingObj: {
ruleType: detectionRuleType.indicator
ruleType: detectionRuleType.threshold
},
dimensionList: [], // Dimensions数据
// ruleType为Indicator时表单数据
@@ -355,8 +327,7 @@ export default {
than: '>',
less: '<',
equal: '='
},
delKeyId: '' // 删除的keyId
}
}
},
mounted () {
@@ -370,31 +341,18 @@ export default {
},
methods: {
initData () {
this.sourceList = detectionUnitList.sourceList || []
this.levelList = securityLevel || []
// threshold模式还没确定所以数据暂时静态数据后续根据需要修改
this.conditionList = detectionUnitList.conditionList || []
this.metricList = detectionUnitList.metricList || []
if (this.mySettingObj.ruleType === this.detectionRuleType.indicator) {
axios.get(api.knowledgeBaseList, { params: { pageSize: -1 } }).then(response => {
if (response.status === 200) {
this.libraryList = _.get(response, 'data.data.list', []).filter(l => l.isBuiltIn === 0)
} else {
console.error(response.data.message)
this.libraryList = []
if (response.data.message) {
this.$message.error(response.data.message)
} else {
this.$message.error(this.$t('tip.somethingWentWrong'))
}
}
}).catch(e => {
console.error(e)
this.libraryList = []
this.$message.error(this.errorMsgHandler(e))
})
}
axios.get(api.detection.statistics, { params: { pageSize: -1 } }).then(response => {
if (response.status === 200) {
this.sourceList = response.data.data.sourceList || []
this.levelList = response.data.data.levelList || []
this.conditionList = response.data.data.conditionList || []
this.metricList = response.data.data.metricList || []
this.libraryList = response.data.data.libraryList || []
} else {
console.error(response.data)
}
}).finally(() => {
})
},
/** 单击History Top Keys列表某行filter添加数据 */
getRowClick (data) {
@@ -406,7 +364,6 @@ export default {
},
/** filter模块点击add按钮 */
addFilter (data) {
this.delKeyId = ''
this.showFilter = true
if (this.selectList.length === 0) {
this.getFilterList()
@@ -488,15 +445,11 @@ export default {
]
},
/** 删除filter某一项 */
delFilterItem (i, item) {
this.delKeyId = item.keyId
delFilterItem (i) {
this.thresholdRuleObj.filterList.splice(i, 1)
},
/** 点击继续,展开第三步 */
onContinue (e) {
if (e) {
delete this.indicatorRuleObj.ruleNoContinue
}
onContinue () {
this.$refs.form.validate(valid => {
if (valid) {
if (this.mySettingObj.ruleType === detectionRuleType.indicator) {
@@ -529,12 +482,6 @@ export default {
obj[item.level] = str
})
this.thresholdRuleObj.conditions = obj
},
handleParamsComplete () {
if (this.indicatorRuleObj.dataSource && this.indicatorRuleObj.knowledgeId && this.indicatorRuleObj.level) {
this.indicatorRuleObj.ruleNoContinue = true
this.onContinue()
}
}
}
}

View File

@@ -104,11 +104,6 @@
>
</el-switch>
</template>
<template v-else-if="item.prop === 'color'">
<div class="knowledge-color">
<span class="knowledge-color__icon" :class="colorName(scope.row[item.prop])"></span> <span>{{colorText(scope.row[item.prop])}}</span>
</div>
</template>
<span v-else>{{scope.row[item.prop] || '-'}}</span>
</template>
</el-table-column>
@@ -158,11 +153,6 @@ export default {
prop: 'reference',
width: 180,
show: true
}, {
label: this.$t('overall.color'),
prop: 'color',
width: 180,
show: true
}, {
label: this.$t('overall.remark'),
prop: 'description',
@@ -199,23 +189,6 @@ export default {
show: true,
width: 80
}
],
knowledgeBaseColor: [
{
label: this.$t('knowledge.info'),
value: 'rgb(119,131,145)',
name: 'info'
},
{
label: this.$t('knowledge.benign'),
value: 'rgb(116,159,77)',
name: 'benign'
},
{
label: this.$t('knowledge.malicious'),
value: 'rgb(226,97,84)',
name: 'malicious'
}
]
}
},
@@ -248,20 +221,6 @@ export default {
const t = knowledgeBaseSource.find(t => t.value === type)
return t ? t.name : ''
}
},
colorText () {
const vm = this
return function (color) {
const t = vm.knowledgeBaseColor.find(t => t.value === color)
return t ? t.label : vm.knowledgeBaseColor[0].label
}
},
colorName () {
const vm = this
return function (color) {
const t = vm.knowledgeBaseColor.find(t => t.value === color)
return t ? t.name : vm.knowledgeBaseColor[0].name
}
}
}
}

View File

@@ -3,21 +3,30 @@
<div class="card-type-title" v-if="aiTaggingList.length > 0">{{$t('knowledgeBase.intelligenceLearning')}}</div>
<el-checkbox-group v-model="checkList" >
<div class="card-box" v-for="data in aiTaggingList" :key="data.knowledgeId">
<div @click="isSelectedStatus && data.isBuiltIn !== 1 && clickCard(data,$event)" @mouseenter="mouseenter(data)" @mouseleave="mouseleave(data)" class="card-item" :class="data.isSelected ? 'card-selected' : ''">
<div @click="isSelectedStatus && data.isBuiltIn !== 1 && clickCard(data,$event)" @mouseenter="mouseenter(data)" @mouseleave="mouseleave(data)" class="card-item" :class="data.isSelected ? 'card-selected' : ''">
<div class="card-content">
<div class="card-operate">
<el-switch v-model="data.status"
class="card-enable"
active-color="#38ACD2"
inactive-color="#C0CEDB"
:active-value="1"
:inactive-value="0"
:before-change="(knowledgeId) => confirmSwitchLearning(data.knowledgeId)"
<el-tooltip
effect="light"
trigger="hover"
:content="$t('tip.notAvailable')"
placement="right"
popper-class="panel-tooltip"
>
</el-switch>
<el-switch v-model="data.status"
class="card-enable"
active-color="#38ACD2"
inactive-color="#C0CEDB"
:disabled="true"
:active-value="1"
:inactive-value="0"
@change="changeStatus($event,data.knowledgeId)"
>
</el-switch>
</el-tooltip>
</div>
<div class="card-icon">
<img :src="data.iconUrl" style="max-height: 50px;"/>
<img :src="data.iconUrl"/>
</div>
<div class="card-title">
<div class="card-title-name" :title="data.label">{{data.label}}</div>
@@ -66,9 +75,7 @@
<div class="center-dialog">
<el-dialog v-model="showUpdateDialog"
:destroy-on-close="true"
:custom-class="showAddUpdateDialog ? 'update-knowledge update-knowledge--upload' : 'update-knowledge'"
:before-close="beforeClose"
:after-close="handleClose">
<div class="knowledge-update__top" >
<div class="update-left__icon">
@@ -79,47 +86,32 @@
<div class="update-title">
<div class="card-title-name" :title="updateKnowledge.label">{{updateKnowledge.label}}</div>
</div>
<el-switch v-model="updateKnowledge.status"
active-color="#38ACD2"
inactive-color="#C0CEDB"
:active-value="1"
:inactive-value="0"
:before-change="(knowledgeId) => confirmSwitchLearning(updateKnowledge.knowledgeId)"
v-if="updateKnowledge.source === 'cn_psiphon3_ip'"
<el-tooltip
effect="light"
trigger="hover"
v-if="showEnable"
:content="$t('tip.notAvailable')"
placement="right"
popper-class="panel-tooltip"
>
</el-switch>
<el-switch v-model="updateKnowledge.status"
active-color="#38ACD2"
inactive-color="#C0CEDB"
:disabled="true"
:active-value="1"
:inactive-value="0"
@change="changeStatus($event,updateKnowledge.knowledgeId)"
>
</el-switch>
</el-tooltip>
</div>
<div class="knowledge-desc" :title="updateKnowledge.desc">{{updateKnowledge.desc ? updateKnowledge.desc : '—'}}</div>
</div>
</div>
<template v-if="!showAddUpdateDialog">
<div class="knowledge-update__tab" v-if="showEnable">
<el-tabs v-model="activeTab"
class="update-log-tab"
@tab-click="handleClick"
>
<el-tab-pane :label="$t('knowledgeBase.updateRecord')"
name="updateRecord"
key="updateRecord"
ref="knowledgeUpdateRecordTab">
</el-tab-pane>
<el-tab-pane :label="$t('knowledgeBase.learningEngineLogs')"
name="intelligenceLearning"
key="intelligenceLearning"
ref="knowledgeIntelligenceLearningTab">
</el-tab-pane>
</el-tabs>
<div class="update-operate">
<button :title="$t('overall.update')" class="top-tool-btn--update"
@click="uploadRecord">
<i class="cn-icon-update-knowledge-base cn-icon"></i>
<span>{{$t('overall.update')}}</span>
</button>
</div>
</div>
<div class="knowledge-update" v-else>
<div class="update-title" >
<div class="card-title-name">{{$t('knowledgeBase.updateRecord')}}</div>
<div class="knowledge-update" >
<div class="update-title">
<div class="card-title-name">update record</div>
</div>
<div class="update-operate">
<button :title="$t('overall.update')" class="top-tool-btn--update"
@@ -129,92 +121,23 @@
</button>
</div>
</div>
<div :style="{height: updateKnowledge.source === 'cn_psiphon3_ip' && activeTab === 'intelligenceLearning' ? 'calc(90vh - 190px - 200px - 50px - 42px)' : 'calc(100% - 242px)', marginTop: '42px', position: 'absolute', width: 'calc(100% - 60px)'}">
<loading :loading="updateLogLoading"></loading>
</div>
<el-table ref="updateDataTable"
border
:data="updateHistoryList"
@selection-change="secondSelectionChange"
width="100%"
class="update-dialog__table"
:class="{
'update-dialog__table--psiphon3': updateKnowledge.source === 'cn_psiphon3_ip' && activeTab === 'intelligenceLearning',
'update-dialog__table--system-user': updateKnowledge.source === 'cn_psiphon3_ip' && activeTab !== 'intelligenceLearning'
}"
:header-cell-style="{background:'#f5f7fa',color:'#353636',fontWeight: '400',fontSize: '12px',borderRight: 'none',borderBottom: 'none'}"
cell-style="padding:6px 0px;font-size: 12px;color: #353636;font-weight: 400;line-height: 20px;border-right:none;"
header-cell-style="padding:8px 0px;font-size: 12px;color: #353636;font-weight: 500;border-right:none;">
<el-table-column prop="opTime" :label="$t('entities.tab.informationAggregation.updateTime')" width="150" >
<template #default="scope" :column="item">
<span>{{scope.row.opTime ? dateFormatByAppearance(scope.row.opTime) : '-'}}</span>
</template>
</el-table-column>
<el-table-column prop="user" :label="$t('knowledgeBase.operator')" width="150" v-if="activeTab === 'updateRecord'">
<el-table-column prop="opTime" label="Update time" width="150" ></el-table-column>
<el-table-column prop="user" label="Operating user" width="150" >
<template #default="scope" :column="item">
<span>{{$_.get(scope.row, 'user.name', '-')}}</span>
</template>
</el-table-column>
<el-table-column prop="commitVersion" :label="$t('overall.version')" width="150" ></el-table-column>
<el-table-column prop="description" :label="$t('overall.remark')"></el-table-column>
<template v-slot:empty >
<div class="table-no-data" v-if="updateHistoryList.length === 0 && !updateLogLoading">
<div class="table-no-data__title">{{ $t('npm.noData') }}</div>
</div>
<div v-else></div>
</template>
<el-table-column prop="commitVersion" label="Version information" width="150" ></el-table-column>
<el-table-column prop="description" label="Description"></el-table-column>
</el-table>
<div class="psiphon3" v-if="updateKnowledge.source === 'cn_psiphon3_ip' && activeTab === 'intelligenceLearning'">
<div class="psiphon3-title">{{$t('knowledgeBase.psiphon3IpCount')}}</div>
<div class="psiphon3-bar">
<chart-error v-if="showErrorForPsiphon3" :content="errorMsgForPsiphon3"/>
<div class="bar-header" v-else>
<div class="bar-header-left">
<div class="bar-value-active" ></div>
<div class="bar-value">
<template v-for="(item, index) in tabs" :key="index">
<div class="bar-value-tabs"
:class=" {'is-active': tabType === item.class, 'mousemove-cursor': mousemoveCursor === item.class}"
@mouseenter="mouseenterTab(item)"
@mouseleave="mouseleaveTab(item)"
@click="activeChange(item)"
>
<div class="bar-value-tabs-name">
<div :class="item.class"></div>
<div class="tabs-name" >{{ $t(item.name) }}</div>
</div>
</div>
</template>
</div>
</div>
<div class="bar-select bar-header-right">
<div class="bar-select-time">
<div class="bar-select__operation">
<el-select
size="mini"
v-model="selectTime"
placeholder=" "
popper-class="common-select"
:popper-append-to-body="false"
@change="timeChange"
>
<template #prefix>
<div class="calendar-popover-text"><i class="cn-icon cn-icon-Data"></i></div>
</template>
<el-option v-for="item in dateRangeArr" :key="item.value" :label="item.name" :value="item.value"></el-option>
</el-select>
</div>
</div>
</div>
</div>
<div style="height: calc(100% - 24px); position: relative">
<chart-no-data v-if="isNoDataForPsiphon3 && !showErrorForPsiphon3 && !psiphon3Loading"></chart-no-data>
<loading :loading="psiphon3Loading"></loading>
<div class="chart-drawing" v-show="!isNoDataForPsiphon3 && !showErrorForPsiphon3" id="psiphonBarChart"></div>
</div>
</div>
</div>
</template>
<template v-if="showAddUpdateDialog">
<div class="update-knowledge-form">
@@ -264,7 +187,6 @@
<el-dialog v-model="showConfirmDialog"
:title="$t('overall.tips')"
custom-class="update-knowledge-tip"
:width="480"
:before-close="handleConfirmClose">
<div class="dialog-message">{{$t('knowledge.updateTips')}}</div>
<template #footer>
@@ -274,37 +196,18 @@
</span>
</template>
</el-dialog>
<el-dialog
v-model="showConfirmSwitch"
:width="330"
custom-class="confirm-knowledge-switch"
:title="$t('overall.hint')"
>
<div class="dialog-message">{{ confirmSwitchLearningTip }}</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="showConfirmSwitch = false">{{ $t('overall.cancel') }}</el-button>
<el-button type="primary" @click="switchLearning">OK</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script>
import table from '@/mixins/table'
import Loading from '@/components/common/Loading'
import { getSecond, getMillisecond, xAxisTimeFormatter, xAxisTimeRich } from '@/utils/date-util'
import { knowledgeCategoryValue, unitTypes, storageKey, builtInKnowledgeBaseBasicInfo } from '@/utils/constants'
import { ref, shallowRef } from 'vue'
import { ref } from 'vue'
import { api } from '@/utils/api'
import { detectionTooltipFormatter } from '@/views/charts/charts/tools'
import ChartNoData from '@/views/charts/charts/ChartNoData'
import axios from 'axios'
import _ from 'lodash'
import * as echarts from 'echarts'
import unitConvert from '@/utils/unit-convert'
export default {
name: 'knowledgeBaseTableForCard',
mixins: [table],
@@ -318,8 +221,7 @@ export default {
}
},
components: {
Loading,
ChartNoData
Loading
},
data () {
return {
@@ -335,57 +237,12 @@ export default {
updateHistoryList: [],
updateObject: {},
currentVersion: 0,
uploadLoading: false,
psiphon3Loading: false,
updateLogLoading: false,
showConfirmSwitch: false,
switchKnowledgeId: '',
activeTab: 'updateRecord',
isNoDataForPsiphon3: false,
showErrorForPsiphon3: false,
errorMsgForPsiphon3: '',
leftOffset: 0,
tabType: 'total',
mousemoveCursor: '',
selectTime: 1440,
tabs: [
{
name: 'knowledgeBase.total',
class: 'total',
color: '#00A7AB',
data: []
},
{
name: 'knowledgeBase.active',
class: 'active',
color: '#7FA054',
data: []
},
{
name: 'knowledgeBase.new',
class: 'new',
color: '#98709B',
data: []
}
],
dateRangeArr: [
{ value: 1440, name: this.$t('dateTime.last1Day') },
{ value: 2880, name: this.$t('dateTime.last2Days') },
{ value: 10080, name: this.$t('dateTime.last7Days') },
{ value: 21600, name: this.$t('dateTime.last15Days') },
{ value: 43200, name: this.$t('dateTime.last30Days') }
]
uploadLoading: false
}
},
setup () {
// 没上传过文件的提示
const uploadErrorTip = ref('')
const nowMill = window.$dayJs.tz().valueOf()
const timeFilter = ref({
startTime: nowMill - 1000 * 60 * 60 * 24,
endTime: nowMill,
dateRangeValue: 1440
})
return {
baseUrl: BASE_CONFIG.baseUrl,
apiVersion: BASE_CONFIG.apiVersion,
@@ -395,174 +252,13 @@ export default {
uploadErrorTip,
fileTypeLimit: '.csv',
fileList: ref([]),
uploadFileSizeLimit: 1024 * 1024 * 1024,
myChart: shallowRef(null),
chartOption: shallowRef(null),
timeFilter
uploadFileSizeLimit: 100 * 1024 * 1024
}
},
methods: {
echartsInit (echartsData) {
const _this = this
const curTab = this.tabs.find(item => item.class === _this.tabType)
this.chartOption = {
color: curTab.color,
legend: {
show: false
},
tooltip: {
show: true,
formatter: (params) => {
params.seriesName = this.$t(params.seriesName)
params.borderColor = params.color
return detectionTooltipFormatter(params)
}
},
grid: {
top: '12%',
left: '2%',
right: '2%',
bottom: 24,
containLabel: true
},
xAxis: {
type: 'time',
boundaryGap: ['1%', '3%'],
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
formatter: xAxisTimeFormatter,
rich: xAxisTimeRich
}
},
yAxis: {
type: 'value',
splitLine: {
show: true,
lineStyle: {
color: '#ECECEC'
}
},
axisLabel: {
margin: 20
},
minInterval: 1
},
series: [
{
name: curTab.name,
data: echartsData,
type: 'bar',
barWidth: 26
}
]
}
this.$nextTick(() => {
if (!this.myChart) {
this.myChart = echarts.init(document.getElementById('psiphonBarChart'))
}
this.myChart.setOption(this.chartOption)
})
},
init (val, show, active, n) {
this.psiphon3Loading = true
const params = {
startTime: getSecond(this.timeFilter.startTime),
endTime: getSecond(this.timeFilter.endTime)
}
const url = api.knowledgeBaseTimedistribution.replace('{{knowledgeId}}', this.updateKnowledge.knowledgeId).replace('{{type}}', this.tabType)
axios.get(url, { params: params }).then(response => {
const res = response.data
if (response.status === 200) {
this.isNoDataForPsiphon3 = res.data.result.length === 0
this.showErrorForPsiphon3 = false
if (!this.isNoDataForPsiphon3) {
const chartsData = res.data.result.map(item => {
return [getMillisecond(item.statTime), item.count]
})
this.echartsInit(chartsData)
}
} else {
this.httpError(res)
}
}).catch(e => {
console.error(e)
this.httpError(e)
}).finally(() => {
this.psiphon3Loading = false
})
},
httpError (e) {
this.isNoDataForPsiphon3 = false
this.showErrorForPsiphon3 = true
this.errorMsgForPsiphon3 = this.errorMsgHandler(e)
},
handleActiveBar () {
if (document.querySelector('.psiphon3-bar .bar-value-tabs.is-active')) {
const {
offsetLeft,
clientWidth,
clientLeft
} = document.querySelector('.psiphon3-bar .bar-value-tabs.is-active')
const activeBar = document.querySelector('.psiphon3-bar .bar-value-active')
activeBar.style.cssText += `width: ${clientWidth}px; left: ${offsetLeft + this.leftOffset + clientLeft}px;`
}
},
resize () {
if (this.myChart) {
this.myChart.resize()
}
},
dispatchSelectAction (type, name) {
this.myChart && this.myChart.dispatchAction({
type: type,
name: name
})
},
legendSelectChange (item) {
this.dispatchSelectAction('legendSelect', item.name)
this.tabs.forEach((t) => {
if (t.name !== item.name) {
this.dispatchSelectAction('legendUnSelect', t.name)
}
})
},
timeChange () {
this.timeFilter.endTime = window.$dayJs.tz().valueOf()
this.timeFilter.startTime = this.timeFilter.endTime - this.selectTime * 60 * 1000
if (this.updateKnowledge.source === 'cn_psiphon3_ip') {
this.init()
}
this.$nextTick(() => {
this.handleActiveBar()
})
},
activeChange (item) { // isClick:代表是通过点击操作来的
if (item) {
this.tabType = item.class
}
this.legendSelectChange(item)
if (this.updateKnowledge.source === 'cn_psiphon3_ip') {
this.init()
}
},
mouseenterTab (item) {
if (this.isNoDataForPsiphon3) return
this.mousemoveCursor = item.class
this.$nextTick(() => {
this.handleActiveBar()
})
},
mouseleaveTab () {
this.mousemoveCursor = ''
},
fileChange (file, fileList) {
console.info(!_.endsWith(file.name, '.csv'))
console.info(file.size > this.uploadFileSizeLimit)
// 判断后缀,仅支持.csv
if (!_.endsWith(file.name, '.csv')) {
this.fileList = []
@@ -591,10 +287,10 @@ export default {
if (response.code === 200) { */
this.$message.success(this.$t('tip.success'))
this.showAddUpdateDialog = false
this.getCurTabData()
if (this.updateKnowledge.source === 'cn_psiphon3_ip') {
this.init()
}
axios.get(api.knowledgeBaseLog + '/' + this.updateKnowledge.knowledgeId, { params: { pageSize: 999 } }).then(res => {
this.updateHistoryList = res.data.data.list
this.currentVersion = this.updateHistoryList[0].commitVersion + 1
})
/* } else {
this.$message.error(this.$t('tip.uploadFailed', { msg: response.message }))
} */
@@ -629,13 +325,6 @@ export default {
data.isSelected = val
this.$emit('checkboxStatusChange', val, data)
},
beforeClose (done) {
if (this.myChart) {
this.myChart.dispose()
this.myChart = null
}
done()
},
handleClose () {
this.showUpdateDialog = false
this.showAddUpdateDialog = false
@@ -651,16 +340,13 @@ export default {
this.showUpdateDialog = true
this.showAddUpdateDialog = false
},
async jumpToUpdatePage (data, showEnable) {
this.updateKnowledge = data
this.showEnable = showEnable
await this.getCurTabData()
if (data.source === 'cn_psiphon3_ip') {
await this.init()
}
this.showUpdate()
this.$nextTick(() => {
this.handleActiveBar()
jumpToUpdatePage (data, showEnable) {
axios.get(api.knowledgeBaseLog + '/' + data.knowledgeId, { params: { pageSize: 999 } }).then(res => {
this.updateKnowledge = data
this.updateHistoryList = res.data.data.list
this.currentVersion = this.updateHistoryList[0].commitVersion + 1
this.showEnable = showEnable
this.showUpdate()
})
},
uploadRecord () {
@@ -669,43 +355,6 @@ export default {
this.updateObject.label = this.updateKnowledge.label
this.updateObject.description = ''
},
getCurTabData () { // showEnable:true 为psiphon3的知识库false为其它知识库
let params = {
pageSize: -1
}
if (this.showEnable) {
if (this.activeTab === 'updateRecord') {
params = {
...params,
opUser: -1
}
} else if (this.activeTab === 'intelligenceLearning') {
params = {
...params,
opUser: 0
}
}
}
this.updateLogLoading = true
this.updateHistoryList = []
axios.get(api.knowledgeBaseLog + '/' + this.updateKnowledge.knowledgeId, { params: params }).then(res => {
this.updateHistoryList = res.data.data.list
if (this.updateHistoryList[0]) {
this.currentVersion = this.updateHistoryList[0].commitVersion + 1
}
}).catch(e => {
console.error(e)
}).finally(() => {
this.updateLogLoading = false
})
},
// 切换tab
handleClick (tab) {
this.getCurTabData()
if (tab.index === '1') {
this.init()
}
},
clearSelect () {
this.$nextTick(() => {
this.checkList = []
@@ -717,10 +366,18 @@ export default {
})
},
mouseenter (card) {
card.showUpdate = true
this.tableData.forEach(t => {
if (t.knowledgeId === card.knowledgeId) {
card.showUpdate = true
}
})
},
mouseleave (card) {
card.showUpdate = false
this.tableData.forEach(t => {
if (t.knowledgeId === card.knowledgeId) {
card.showUpdate = false
}
})
},
del (data) {
this.$emit('delete', data)
@@ -737,54 +394,9 @@ export default {
dataType: dataType
}
})
},
confirmSwitchLearning (id) {
this.showConfirmSwitch = true
this.switchKnowledgeId = id
return false
},
switchLearning () {
const hint = this.aiTaggingList.find(d => d.knowledgeId === this.switchKnowledgeId)
const toStatus = hint.status === 0 ? 1 : 0
const url = toStatus === 0 ? api.knowledgeBaseLearningStop : api.knowledgeBaseLearningStart
if (hint.knowledgeId === 101 || hint.knowledgeId === 102) {
hint.status = toStatus
this.$message.success(this.$t('tip.success'))
this.showConfirmSwitch = false
} else {
axios.post(`${url}?knowledgeId=${hint.knowledgeId}`).then(res => {
if (res.status === 200) {
hint.status = toStatus
this.$message.success(this.$t('tip.success'))
} else {
console.error(res.message)
this.$message.error(this.errorMsgHandler(res))
}
}).catch(e => {
console.error(e)
this.$message.error(this.errorMsgHandler(e))
}).finally(() => {
this.showConfirmSwitch = false
})
}
}
},
watch: {
tabType (n) {
this.$nextTick(() => {
this.handleActiveBar()
})
},
timeFilter: {
handler () {
if (this.updateKnowledge.source === 'cn_psiphon3_ip') {
this.init()
}
this.$nextTick(() => {
this.handleActiveBar()
})
}
},
tableData: {
handler (n) {
if (this.tableData && this.tableData.length > 0) {
@@ -805,48 +417,6 @@ export default {
}
}
})
// 2024-01-15 以下两个是为105环境演示准备的假数据
const data1 = {
knowledgeId: 101,
name: 'CyberGhost',
category: 'ai_tagging',
description: 'CyberGhost is a VPN service used to unblock sites and browse privately and anonymously.',
isBuiltIn: 1,
isPublished: 1,
status: 1,
showUpdate: false
}
const basicInfo1 = builtInKnowledgeBaseBasicInfo.find(bi => bi.knowledgeId === data1.knowledgeId)
this.aiTaggingList.push({
...data1,
...basicInfo1
})
const data2 = {
knowledgeId: 102,
name: 'HotSpotshield VPN',
category: 'ai_tagging',
description: 'Hotspot Shield is a public VPN service, providing\n' +
'a secure proxy connection through an encrypted\n' +
'channel between your device and the target\n' +
'website, using VPN technology.',
isBuiltIn: 1,
isPublished: 1,
status: 1,
showUpdate: false
}
const basicInfo2 = builtInKnowledgeBaseBasicInfo.find(bi => bi.knowledgeId === data2.knowledgeId)
this.aiTaggingList.push({
...data2,
...basicInfo2
})
}
}
},
activeTab (n) {
if (n === 'updateRecord') {
if (this.myChart) {
this.myChart.dispose()
this.myChart = null
}
}
},
@@ -854,23 +424,11 @@ export default {
handler (n) {
if (!n) {
this.fileList = []
if (this.updateKnowledge.source === 'cn_psiphon3_ip') {
this.init()
}
} else {
if (this.myChart) {
this.myChart.dispose()
this.myChart = null
}
}
}
}
},
mounted () {
this.myChart = null
this.chartOption = null
window.addEventListener('resize', this.resize)
this.aiTaggingList = []
this.websketchList = []
this.tableData.forEach(item => {
@@ -882,22 +440,6 @@ export default {
}
})
},
beforeUnmount () {
clearTimeout(this.timer)
window.removeEventListener('resize', this.resize)
const dom = document.getElementById('psiphonBarChart')
if (dom) {
let myChart = echarts.getInstanceByDom(document.getElementById('psiphonBarChart'))
if (myChart) {
echarts.dispose(myChart)
}
myChart = null
}
if (this.myChart) {
echarts.dispose(this.myChart)
}
},
computed: {
uploadParams () {
return {
@@ -905,20 +447,6 @@ export default {
action: 'overwrite',
description: this.updateObject.description
}
},
confirmSwitchLearningTip () {
let tip = ''
if (this.switchKnowledgeId) {
const find = this.aiTaggingList.find(item => item.knowledgeId === this.switchKnowledgeId)
if (find) {
if (find.status === 0) {
tip = this.$t('tip.confirmEnable') + '?'
} else if (find.status === 1) {
tip = this.$t('tip.confirmDisable') + '?'
}
}
}
return tip
}
}
}

View File

@@ -110,26 +110,64 @@ export default {
if (this.listUrl) {
listUrl = this.listUrl
}
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'))
// todo 此段是为了避免mock没开启打开detection界面报错提示后续再开发detection时删除
if (listUrl === api.detection.list) {
const list = []
for (let i = 0; i < 20; i++) {
const obj = {
ruleId: 100000 + i,
ruleType: 'indicator_match',
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)
}
}).catch(() => {
this.isNoData = true
}).finally(() => {
this.toggleLoading(false)
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
}).finally(() => {
this.toggleLoading(false)
this.loading = false
})
}
},
del (row) {
this.$confirm(this.$t('tip.confirmDelete'), {
@@ -362,23 +400,10 @@ export default {
this.searchLabel.orderBy = orderBy
this.getTableData()
},
search (params, flag, list) {
search (params) {
this.pageObj.pageNo = 1
if (flag !== 'detection') {
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
}
}
delete this.searchLabel.category
delete this.searchLabel.source
this.getTableData(params)
},
getTimeString () {

View File

@@ -96,41 +96,5 @@ export default {
this.relationshipShowMoreTwo = false
this.relationshipShowMoreOne = false
}
},
computed: {
scoreDot () {
const dots = []
if (this.score === '-') {
for (let i = 0; i < 6; i++) {
dots.push({
class: 'score-dot'
})
}
} else {
for (let i = 0; i < 6; i++) {
if (i < this.score) {
dots.push({
class: `score-dot ${handleClass(this.score)}`
})
} else {
dots.push({
class: 'score-dot'
})
}
}
}
return dots
function handleClass (score) {
if (score <= 2) {
return 'score-dot--red'
} else if (score <= 4) {
return 'score-dot--yellow'
} else if (score <= 6) {
return 'score-dot--green'
}
return ''
}
}
}
}

View File

@@ -7,7 +7,7 @@ if (openMock) {
const list = []
for (let i = 0; i < 20; i++) {
const obj = {
ruleId: 163 + i,
ruleId: 100000 + i,
ruleType: 'indicator_match',
status: 1,
name: 'name123',
@@ -50,17 +50,51 @@ if (openMock) {
Mock.mock(new RegExp(urlAndVersion + '/detection/statistics.*'), 'get', function (requestObj) {
const data = {
statusList: [
{ status: 1, count: 34 },
{ status: 0, count: 28 }
{ status: 1 },
{ status: 0 }
],
categoryList: [
{ name: 'Security Event', count: 32 },
{ name: 'Performance Event', count: 28 }
{ value: 'security', label: 'Security Event' },
{ value: 'performance', label: 'Performance Event' },
{ value: 'regulatory_risk', label: 'Regulatory Risk Event' }
],
eventTypeList: [
{ name: 'DDos', count: 15 },
{ name: 'Lateral movement', count: 17 },
{ name: 'Brute force', count: 12 }
typeList: [
{ value: 'c&c', label: 'C&C' },
{ value: 'ddos', label: 'DDos' },
{ value: 'lateral_movement', label: 'Lateral movement' },
{ 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' }
]
}
@@ -95,50 +129,28 @@ if (openMock) {
const ruleId = getLastValue(requestObj.url)
const data = {
name: 'name123',
category: 'security_event',
category: 'Security Event',
ruleType: 'indicator_match',
eventType: 'C&C',
description: 'Built-in darkweb IoC',
status: 1,
ruleConfig: {
dataSource: 'VPN Server IP',
knowledgeBase: {
knowledgeId: 10,
name: 'cn_ioc_darkweb',
category: 'websketch',
source: 'cn_ioc_darkweb'
},
level: 'critical'
knowledgeId: 10,
level: 10
},
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: {
trigger: {
atLeast: 1,
interval: 'PT5M',
resetInterval: 'PT10M'
}
}
data.ruleConfig = JSON.stringify(data.ruleConfig)
data.trigger = JSON.stringify(data.trigger)
if (ruleId % 2 === 0) {
data.ruleType = 'threshold'
data.status = 0
} else {
data.status = 1
} else {
data.status = 0
}
return {

View File

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

View File

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

View File

@@ -93,16 +93,16 @@ const routes = [
component: () => import('@/views/administration/I18n')
},
{
path: '/detectionPolicy',
component: () => import('@/views/detections/detectionPolicies/Index')
path: '/detectionsNew',
component: () => import('@/views/detectionsNew/Index')
},
{
path: '/detectionPolicy/create',
component: () => import('@/views/detections/detectionPolicies/PolicyForm')
path: '/detection/policies',
component: () => import('@/views/detectionsNew/Index')
},
{
path: '/detectionPolicy/edit',
component: () => import('@/views/detections/detectionPolicies/PolicyForm')
path: '/detection/policies/create',
component: () => import('@/views/detectionsNew/DetectionForm')
}
]
}

View File

@@ -60,29 +60,6 @@ const panel = {
routerHistoryList: [], // 路由跳转记录列表
dnsQtypeMapData: [],
dnsRcodeMapData: [],
scoreBase: {
isReady: false,
establishLatencyMs: {
p10: null,
p90: null
},
httpResponseLatency: {
p10: null,
p90: null
},
sslConLatency: {
p10: null,
p90: null
},
tcpLostlenPercent: {
p10: null,
p90: null
},
pktRetransPercent: {
p10: null,
p90: null
}
},
chartTabList: null // chartTabs组件的tab状态点击列表初始化为null方便原有逻辑计算
},
mutations: {
@@ -176,56 +153,6 @@ const panel = {
setRouterHistoryList (state, list) {
state.routerHistoryList = list
},
resetScoreBase (state) {
state.scoreBase = {
isReady: false,
establishLatencyMs: {
p10: null,
p90: null
},
httpResponseLatency: {
p10: null,
p90: null
},
sslConLatency: {
p10: null,
p90: null
},
tcpLostlenPercent: {
p10: null,
p90: null
},
pktRetransPercent: {
p10: null,
p90: null
}
}
},
setScoreBase (state, scoreBase) {
state.scoreBase = {
isReady: true,
establishLatencyMs: {
p10: scoreBase.establishLatencyMsP10,
p90: scoreBase.establishLatencyMsP90
},
httpResponseLatency: {
p10: scoreBase.httpResponseLatencyP10,
p90: scoreBase.httpResponseLatencyP90
},
sslConLatency: {
p10: scoreBase.sslConLatencyP10,
p90: scoreBase.sslConLatencyP90
},
tcpLostlenPercent: {
p10: scoreBase.tcpLostlenPercentP10,
p90: scoreBase.tcpLostlenPercentP90
},
pktRetransPercent: {
p10: scoreBase.pktRetransPercentP10,
p90: scoreBase.pktRetransPercentP90
}
}
},
setChartTabList (state, list) {
state.chartTabList = list
}
@@ -305,12 +232,6 @@ const panel = {
},
getChartTabList (state) {
return state.chartTabList
},
scoreBaseReady (state) {
return state.scoreBase.isReady
},
getScoreBase (state) {
return state.scoreBase
}
},
actions: {

View File

@@ -118,7 +118,7 @@ const user = {
localStorage.setItem(storageKey.linkInfo, res.page.list[0].cvalue)
}
})
axios.get(api.config, { params: { ckey: 'schema_explore' } }).then(response => {
axios.get(api.config, { params: { ckey: 'schema_entity_explore' } }).then(response => {
const res = response.data
if (response.status === 200 && res.page.list && res.page.list.length > 0) {
localStorage.setItem(storageKey.schemaEntityExplore, res.page.list[0].cvalue)

View File

@@ -38,12 +38,9 @@ export const api = {
knowledgeBase: apiVersion + '/knowledgeBase',
knowledgeBaseList: apiVersion + '/knowledgeBase/list',
knowledgeBaseEnable: apiVersion + '/knowledgeBase/status',
knowledgeBaseLearningStart: apiVersion + '/knowledgeBase/intelligence-learning/start',
knowledgeBaseLearningStop: apiVersion + '/knowledgeBase/intelligence-learning/stop',
knowledgeBaseStatistics: apiVersion + '/knowledgeBase/statistics',
updateKnowledgeUrl: apiVersion + '/knowledgeBase/items/batch',
knowledgeBaseLog: apiVersion + '/knowledgeBase/audit/log',
knowledgeBaseTimedistribution: apiVersion + '/knowledgeBase/{{knowledgeId}}/{{type}}/timedistribution',
// 报告相关
reportJob: '/report/job',
@@ -126,20 +123,7 @@ export const api = {
listBasic: '/interface/detection/security/list/basic',
listCount: '/interface/detection/security/list/count',
overviewBasic: '/interface/detection/security/detail/overview/basic',
overviewEvent: '/interface/detection/security/detail/overview/event',
securityList: apiVersion + '/detection/security/list', // 安全事件列表
timeDistribution: apiVersion + '/detection/security/severity/timedistribution', // 事件严重等级分布(顶部柱状图)
severityStatistics: apiVersion + '/detection/security/severity/statistics', // 事件严重等级统计(左侧filter事件严重等级和饼图)
statusStatistics: apiVersion + '/detection/security/status/statistics', // 事件状态统计
eventTypeStatistics: apiVersion + '/detection/security/event-type/statistics', // 事件类型统计
offenderIpStatistics: apiVersion + '/detection/security/offender-ip/statistics', // 攻击者IP统计
victimIpStatistics: apiVersion + '/detection/security/victim-ip/statistics', // 受害者IP统计
relationEvent: apiVersion + '/detection/security/ip/relation/event', // IP相关近期事件
securityCount: apiVersion + '/detection/security/count', // 安全事件总数
detail: apiVersion + '/detection/security/entity/detail', // 安全事件实体详情,后面得加上实体类型
ipDetail: apiVersion + '/detection/security/entity/detail/ip', // 安全事件实体详情ip响应
domainDetail: apiVersion + '/detection/security/entity/detail/domain', // 安全事件实体详情domain响应
appDetail: apiVersion + '/detection/security/entity/detail/app' // 安全事件实体详情app响应
overviewEvent: '/interface/detection/security/detail/overview/event'
},
performanceEvent: {
eventSeverityTrend: '/interface/detection/performance/filter/severityTrend',
@@ -155,13 +139,13 @@ export const api = {
},
list: apiVersion + '/rule/detection/list', // 检测规则列表
detail: apiVersion + '/rule/detection', // 检测规则详情
delete: apiVersion + '/rule/detection', // 检测规则删除
delete: apiVersion + '/rule', // 检测规则删除
// 获取单位列表如source、type、metric等
statistics: apiVersion + '/rule/detection/statistics',
statistics: apiVersion + '/detection/statistics',
// 规则新建模块
create: {
topKeys: apiVersion + '/detection/topKeys', // topKeys列表
create: apiVersion + '/rule/detection'
create: apiVersion + '/rule/detection/create' // todo 规则新建编辑此api为模拟后续需要修改
}
},
// Dashboard
@@ -264,7 +248,6 @@ export const api = {
throughput: apiVersion + '/entity/detail/traffic/throughput',
security: apiVersion + '/entity/detail/event/security',
performance: apiVersion + '/entity/detail/event/performance',
behaviorPattern: apiVersion + '/entity/detail/behavior/ip',
// 域名解析ip相关app、domain
domainNameResolutionAboutAppsOfIp: apiVersion + '/entity/detail/ip/relate/apps',
domainNameResolutionAboutDomainsOfIp: apiVersion + '/entity/detail/ip/relate/domains',

File diff suppressed because one or more lines are too long

View File

@@ -13,17 +13,11 @@ export function getMillisecond (time) {
ms = window.$dayJs.tz(new Date(time)).valueOf()
} else if (_.isNumber(time)) {
const timeStr = _.toString(time)
/* const difference = timeStr.length - 13
const difference = timeStr.length - 13
if (difference >= 0) {
ms = window.$dayJs.tz(new Date(Number(timeStr.slice(0, 13)))).valueOf()
} else {
ms = window.$dayJs.tz(new Date(Math.floor(time * (10 ** (0 - difference))))).valueOf()
} */
// 判断9位和10位数为秒12位和13位为毫秒。其他位数不做处理
if (timeStr.length === 9 || timeStr.length === 10) {
ms = window.$dayJs.tz(new Date(Number(time * 1000))).valueOf()
} else {
ms = window.$dayJs.tz(new Date(Number(time))).valueOf()
}
} else if (_.isString(time)) {
try {
@@ -49,7 +43,7 @@ export function rTime (date) {
}
// 日期转换为时间戳
export function toTime (date) {
return new Date(parseFloat(date)).getTime()
return new Date(date).getTime()
}
// 时间格式转换
export function dateFormat (date, format = 'YYYY-MM-DD HH:mm:ss') {
@@ -127,9 +121,9 @@ export function xAxisTimeFormatter (value) {
':' +
(date.getMinutes() < 10 ? `0${date.getMinutes()}` : date.getMinutes())
// 如果是一天的开始
if (getSecond(date.getTime()) === getSecond(dayStart.getTime())) {
if (date.getTime() === dayStart.getTime()) {
return '{day|' + dateFormat(date, 'YYYY-MM-DD') + '}'
} else if (getSecond(date.getTime()) === getSecond(hourStart.getTime())) {
} else if (date.getTime() === hourStart.getTime()) {
return '{hour|' + HHmm + '}'
} else {
return HHmm
@@ -143,69 +137,3 @@ export const xAxisTimeRich = {
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]
// P5D表示持续5天PT5M持续5分钟T是位于时间分量之前的时间指示符即T之前是年月周日T之后是时分秒
if (isNaN(item) && item !== 'T') {
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

@@ -15,42 +15,42 @@ _this.$t = _this.t
export const dataForNpmTrafficLine = {
tabs: [
{
name: 'network.total',
name: _this.$t('network.total'),
show: true,
positioning: 0,
data: [],
unitType: 'number'
},
{
name: 'network.inbound',
name: _this.$t('network.inbound'),
show: true,
positioning: 1,
data: [],
unitType: 'number'
},
{
name: 'network.outbound',
name: _this.$t('network.outbound'),
show: true,
positioning: 2,
data: [],
unitType: 'number'
},
{
name: 'network.internal',
name: _this.$t('network.internal'),
show: true,
positioning: 3,
data: [],
unitType: 'number'
},
{
name: 'network.through',
name: _this.$t('network.through'),
show: true,
positioning: 4,
data: [],
unitType: 'number'
},
{
name: 'network.other',
name: _this.$t('network.other'),
show: true,
positioning: 5,
data: [],
@@ -58,21 +58,21 @@ export const dataForNpmTrafficLine = {
}
],
npmQuantity: [
{ name: 'networkAppPerformance.tcpConnectionEstablishLatency', show: true, positioning: 0, data: [], unitType: unitTypes.time, index: 0 },
{ name: 'networkAppPerformance.httpResponse', show: true, positioning: 0, data: [], unitType: unitTypes.time, index: 1 },
{ name: 'networkAppPerformance.sslResponseLatency', show: true, positioning: 0, data: [], unitType: unitTypes.time, index: 2 },
{ name: 'networkAppPerformance.packetLoss', show: true, positioning: 0, data: [], unitType: unitTypes.percent, index: 3 },
{ name: 'overall.packetRetrans', show: true, positioning: 0, data: [], unitType: unitTypes.percent, index: 4 }
{ name: _this.$t('networkAppPerformance.tcpConnectionEstablishLatency'), show: true, positioning: 0, data: [], unitType: unitTypes.time, index: 0 },
{ name: _this.$t('networkAppPerformance.httpResponse'), show: true, positioning: 0, data: [], unitType: unitTypes.time, index: 1 },
{ name: _this.$t('networkAppPerformance.sslResponseLatency'), show: true, positioning: 0, data: [], unitType: unitTypes.time, index: 2 },
{ name: _this.$t('networkAppPerformance.packetLoss'), show: true, positioning: 0, data: [], unitType: unitTypes.percent, index: 3 },
{ name: _this.$t('overall.packetRetrans'), show: true, positioning: 0, data: [], unitType: unitTypes.percent, index: 4 }
],
metricOptions: [
/* { value: 'Bits/s', label: 'Bits/s' },
{ value: 'Packets/s', label: 'Packets/s' },
{ value: 'Sessions/s', label: 'Sessions/s' }, */
{ value: 'establishLatencyMs', label: 'networkAppPerformance.tcpConnectionEstablishLatency' },
{ value: 'httpResponseLatency', label: 'networkAppPerformance.httpResponse' },
{ value: 'sslConLatency', label: 'networkAppPerformance.sslResponseLatency' },
{ value: 'tcpLostlenPercent', label: 'networkAppPerformance.packetLoss' },
{ value: 'pktRetransPercent', label: 'overall.packetRetrans' }
{ value: 'establishLatencyMs', label: _this.$t('networkAppPerformance.tcpConnectionEstablishLatency') },
{ value: 'httpResponseLatency', label: _this.$t('networkAppPerformance.httpResponse') },
{ value: 'sslConLatency', label: _this.$t('networkAppPerformance.sslResponseLatency') },
{ value: 'tcpLostlenPercent', label: _this.$t('networkAppPerformance.packetLoss') },
{ value: 'pktRetransPercent', label: _this.$t('overall.packetRetrans') }
]
}
@@ -175,9 +175,9 @@ export const dataForLinkTrafficLine = {
export const dataForNpmLine = {
chartOptionLineData: [
{ legend: 'network.total', index: 0, invertTab: true, show: false, color: '#749F4D' },
{ legend: 'network.inbound', index: 1, invertTab: true, show: false, color: '#98709B' },
{ legend: 'network.outbound', index: 2, invertTab: true, show: false, color: '#E5A219' }
{ legend: _this.$t('network.total'), index: 0, invertTab: true, show: false, color: '#749F4D' },
{ legend: _this.$t('network.inbound'), index: 1, invertTab: true, show: false, color: '#98709B' },
{ legend: _this.$t('network.outbound'), index: 2, invertTab: true, show: false, color: '#E5A219' }
],
npmLineColor: [
{ legend: '', color: '#749F4D' },
@@ -221,27 +221,27 @@ export const dataForDnsTrafficLine = {
export const dataForNpmEventsHeader = {
chartData: [
{
eventSeverity: 'overall.critical',
eventSeverity: 'critical',
count: '-',
index: 0
},
{
eventSeverity: 'overall.high',
eventSeverity: 'high',
count: '-',
index: 1
},
{
eventSeverity: 'overall.medium',
eventSeverity: 'medium',
count: '-',
index: 2
},
{
eventSeverity: 'overall.low',
eventSeverity: 'low',
count: '-',
index: 3
},
{
eventSeverity: 'overall.info',
eventSeverity: 'info',
count: '-',
index: 4
}
@@ -334,91 +334,11 @@ export const columnList1 = [
}
}
]
const securityEvent = [
{
name: 'event_type',
type: 'string',
label: 'event_type',
doc: {
constraints: {
operator_functions: '=,in,like'
}
}
},
{
name: 'event_name',
type: 'string',
label: 'event_name',
doc: {
constraints: {
operator_functions: '=,in,like'
}
}
},
{
name: 'severity',
type: 'string',
label: 'severity',
doc: {
constraints: {
operator_functions: '=,in,like'
}
}
},
{
name: 'offender_ip',
type: 'string',
label: 'offender Ip',
doc: {
constraints: {
operator_functions: '=,in,like'
}
}
},
{
name: 'victim_ip',
type: 'string',
label: 'victim Ip',
doc: {
constraints: {
operator_functions: '=,in,like'
}
}
},
{
name: 'domain',
type: 'string',
label: 'domain',
doc: {
constraints: {
operator_functions: '=,in,like'
}
}
},
{
name: 'app',
type: 'string',
label: 'app',
doc: {
constraints: {
operator_functions: '=,in,like'
}
}
}
]
const schema = localStorage.getItem(storageKey.schemaEntityExplore)
const schemaEntityExplore = schema ? JSON.parse(schema).entityMetadata.searchColumns : columnList1
let schemaEntityExplore = localStorage.getItem(storageKey.schemaEntityExplore)
schemaEntityExplore = schemaEntityExplore ? JSON.parse(schemaEntityExplore).entityMetadata.searchColumns : columnList1
export const columnList = schemaEntityExplore
let securityEventMetadata = securityEvent
if (schema) {
if (JSON.parse(schema).securityEventMetadata) {
securityEventMetadata = JSON.parse(schema).securityEventMetadata.searchColumns
}
}
export const schemaDetectionSecurity = securityEventMetadata
export const operatorList = ['=', '!=', /* '>', '<', '>=', '<=', */'IN', 'NOT IN', 'LIKE', 'NOT LIKE']
export const connectionList = [
{

View File

@@ -784,7 +784,7 @@ export function getChainRatio (current, prev) {
}
}
export function computeScore (data, scoreBase) {
export function computeScore (data) {
let score = 0
let k = 0
let totalScore = 0
@@ -799,14 +799,26 @@ export function computeScore (data, scoreBase) {
} else if (t === 'httpResponseLatency' || t === 'sslConLatency') {
k = 0.05
}
if (!data[t] && data[t] !== 0) {
score = 1
} else if (data[t] <= scoreBase[t].p10) {
score = 1
} else if (data[t] >= scoreBase[t].p90) {
score = 0
} else {
score = (data[t] - scoreBase[t].p90) / (scoreBase[t].p10 - scoreBase[t].p90)
if (t === 'establishLatencyMs' || t === 'httpResponseLatency' || t === 'sslConLatency') {
if (!data[t] && data[t] !== 0) {
score = 1
} else if (data[t] <= 100) {
score = 1
} else if (data[t] > 1000) {
score = 0
} else {
score = (data[t] - 1000) / (100 - 1000)
}
} else if (t === 'tcpLostlenPercent' || t === 'pktRetransPercent') {
if (!data[t] && data[t] !== 0) {
score = 1
} else if (data[t] <= 0.001) {
score = 1
} else if (data[t] > 0.05) {
score = 0
} else {
score = (data[t] - 0.05) / (0.001 - 0.05)
}
}
scoreArr.push(score * k)
})
@@ -1306,13 +1318,12 @@ export function numberWithCommas (num) {
* @returns {string}
*/
export function switchStatus (status) {
switch (status + '') {
case '0':
return 'detection.create.disabled'
case '1':
return 'detection.create.enabled'
switch (status) {
case 0:
return 'Disabled'
case 1:
return 'Enabled'
}
return null
}
/**
@@ -1345,18 +1356,3 @@ export function beforeRouterPush () {
}
store.commit('setRouterHistoryList', historyList)
}
/**
* 配置tag颜色此为接口返回的颜色字段color为rgb格式
* @param color
* @returns {string}
*/
export function getTagColor (color) {
if (color) {
let backgroundColor = ''
if (color.indexOf('rgb(') > -1) {
backgroundColor = color.replace('rgb(', 'rgba(').replace(')', ',0.06)')
}
return `color: ${color};border-color: ${color};background-color: ${backgroundColor};`
}
}

View File

@@ -58,7 +58,7 @@
size="mini"
v-model="table.limit"
class="option__select select-topn"
placeholder=" "
placeholder=""
popper-class="option-popper"
@change="tableLimitChange"
>
@@ -125,7 +125,7 @@
size="mini"
v-model="copyOrderPieTable"
class="option__select select-column"
placeholder=" "
placeholder=""
popper-class="option-popper"
@change="orderPieTableChange"
>

View File

@@ -24,7 +24,7 @@
<el-select
size="mini"
v-model="metric"
placeholder=" "
placeholder=""
popper-class="common-select"
v-if="showMetric"
:popper-append-to-body="false"
@@ -104,9 +104,7 @@ export default {
dnsRcodeMapData: [],
dnsQtypeMapData: [],
score: null,
curTabState: curTabState,
performanceData: {},
scoreDataState: false // 评分数据是否加载完成
curTabState: curTabState
}
},
computed: {
@@ -116,32 +114,22 @@ export default {
// 显示顶部的Metric单位选项标识
showMetric () {
return this.panelType === panelTypeAndRouteMapping.networkOverview || this.panelType === panelTypeAndRouteMapping.networkOverviewDrillDown
},
scoreBaseState () {
return this.$store.getters.scoreBaseReady
}
},
watch: {
// npmThirdLevelMenuScore: {
// deep: true,
// immediate: true,
// handler (n) {
// this.score = n
// }
// }
timeFilter: {
handler () {
if (this.$route.path === '/panel/networkAppPerformance') {
this.$store.commit('resetScoreBase')
this.queryScoreBase()
if (this.lineQueryCondition || this.networkOverviewBeforeTab) {
this.scoreCalculation()
}
if (this.$route.path === '/panel/networkAppPerformance' && (this.lineQueryCondition || this.networkOverviewBeforeTab)) {
this.scoreCalculation()
}
}
},
scoreBaseState (n) {
if (n && this.scoreDataState) {
this.handleScoreData()
}
},
scoreDataState (n) {
if (n && this.scoreBaseState) {
this.handleScoreData()
}
}
},
async mounted () {
@@ -224,14 +212,8 @@ export default {
return chart
})
})
if (this.$route.path === '/panel/networkAppPerformance') {
if (this.lineQueryCondition || this.networkOverviewBeforeTab) {
this.scoreCalculation()
}
}
if (this.$route.path === '/panel/networkAppPerformance' || this.$route.path === '/panel/linkMonitor') {
this.$store.commit('resetScoreBase')
this.queryScoreBase()
if (this.$route.path === '/panel/networkAppPerformance' && (this.lineQueryCondition || this.networkOverviewBeforeTab)) {
this.scoreCalculation()
}
},
setup (props) {
@@ -414,44 +396,6 @@ export default {
})
overwriteUrl(newUrl)
},
// 动态查询评分基准
queryScoreBase () {
const params = {
startTime: this.timeFilter.startTime,
endTime: this.timeFilter.endTime
}
const tcp = axios.get(api.npm.overview.tcpSessionDelay, { params: params })
const http = axios.get(api.npm.overview.httpResponseDelay, { params: params })
const ssl = axios.get(api.npm.overview.sslConDelay, { params: params })
const tcpPercent = axios.get(api.npm.overview.tcpLostlenPercent, { params: params })
const packetPercent = axios.get(api.npm.overview.packetRetransPercent, { params: params })
Promise.all([tcp, http, ssl, tcpPercent, packetPercent]).then(res => {
const scoreBase = {}
res.forEach((t, i) => {
if (t.status === 200) {
if (i === 0) {
scoreBase.establishLatencyMsP10 = _.get(t.data, 'data.result.establishLatencyMsP10', null)
scoreBase.establishLatencyMsP90 = _.get(t.data, 'data.result.establishLatencyMsP90', null)
} else if (i === 1) {
scoreBase.httpResponseLatencyP10 = _.get(t.data, 'data.result.httpResponseLatencyP10', null)
scoreBase.httpResponseLatencyP90 = _.get(t.data, 'data.result.httpResponseLatencyP90', null)
} else if (i === 2) {
scoreBase.sslConLatencyP10 = _.get(t.data, 'data.result.sslConLatencyP10', null)
scoreBase.sslConLatencyP90 = _.get(t.data, 'data.result.sslConLatencyP90', null)
} else if (i === 3) {
scoreBase.tcpLostlenPercentP10 = _.get(t.data, 'data.result.tcpLostlenPercentP10', null)
scoreBase.tcpLostlenPercentP90 = _.get(t.data, 'data.result.tcpLostlenPercentP90', null)
} else if (i === 4) {
scoreBase.pktRetransPercentP10 = _.get(t.data, 'data.result.pktRetransPercentP10', null)
scoreBase.pktRetransPercentP90 = _.get(t.data, 'data.result.pktRetransPercentP90', null)
}
}
})
this.$store.commit('setScoreBase', scoreBase)
}).catch((e) => {
}).finally(() => {
})
},
scoreCalculation () {
let condition = ''
let url = ''
@@ -499,21 +443,18 @@ export default {
url = api.npm.overview.networkAnalysis
}
if ((type && condition) || type) {
this.scoreDataState = false
this.performanceData = {}
params.type = params.type || type
axios.get(url, { params }).then(res => {
if (res.status === 200) {
this.performanceData = {
const data = {
establishLatencyMs: _.get(res, 'data.data.result.establishLatencyMsAvg', null),
httpResponseLatency: _.get(res, 'data.data.result.httpResponseLatencyAvg', null),
sslConLatency: _.get(res, 'data.data.result.sslConLatencyAvg', null),
tcpLostlenPercent: _.get(res, 'data.data.result.tcpLostlenPercentAvg', null),
pktRetransPercent: _.get(res, 'data.data.result.pktRetransPercentAvg', null)
}
this.score = computeScore(data)
}
}).finally(() => {
this.scoreDataState = true
})
}
},
@@ -526,9 +467,6 @@ export default {
}
})
window.open(href, '_blank')
},
handleScoreData () {
this.score = computeScore(this.performanceData, this.$store.getters.getScoreBase)
}
},
/**

View File

@@ -27,13 +27,12 @@
</div>
</div>
<div class="line-select line-header-right">
<!-- <div class="line-select-metric">
<div class="line-select-metric">
<span>{{$t('network.metric')}}:</span>
<div class="line-select__operation">
<el-select
size="mini"
v-model="lineMetric"
placeholder=" "
popper-class="common-select"
:popper-append-to-body="false"
@change="metricSelectChange"
@@ -41,14 +40,13 @@
<el-option v-for="item in options1" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
</div>
</div>-->
</div>
<div class="line-select-reference-line">
<span>{{$t('network.referenceLine')}}:</span>
<div class="line-select__operation">
<el-select
size="mini"
v-model="lineRefer"
placeholder=" "
:disabled="!lineTab"
popper-class="common-select"
:popper-append-to-body="false"
@@ -176,9 +174,6 @@ export default {
if (res.status === 200) {
this.showError = false
this.isNoData = res.data.data.result.length === 0
if (!active) {
this.tabs = _.cloneDeep(dataForDnsTrafficLine.tabs)
}
if (this.isNoData) {
this.lineTab = ''
this.tabs = _.cloneDeep(dataForDnsTrafficLine.tabs)
@@ -450,7 +445,6 @@ export default {
this.legendSelectChange(e, 0)
})
this.tabs = tabs
this.lineRefer = 'Average'
this.echartsInit(this.tabs, true)
} else {
const unit = 'bps'
@@ -470,7 +464,7 @@ export default {
dnsData.forEach(e => {
e.unitType = type
if (parseFloat(e.analysis.max) === 0 || isNaN(parseFloat(e.analysis.max))) {
if (parseFloat(e.analysis.avg) === 0 || isNaN(parseFloat(e.analysis.avg))) {
e.show = false
num += 1
} else {
@@ -480,18 +474,13 @@ export default {
}
}
if (this.lineTab === e.class) {
if (parseFloat(e.analysis.max) <= 0) {
if (parseFloat(e.analysis.avg) <= 0) {
this.lineTab = ''
this.lineRefer = ''
// this.init() // 后续多关注
this.init()
}
}
})
const emptyData = dnsData.filter(d => parseFloat(d.analysis.max) === 0)
this.isNoData = emptyData.length === dnsData.length
if (this.isNoData) {
return true
}
this.tabs = dnsData
// 如果三者avg都为0时至少保证total显示
@@ -500,10 +489,10 @@ export default {
let ingressAvg = 0
let egressAvg = 0
if (ingressObj) {
ingressAvg = parseFloat(ingressObj.analysis.max) || 0
ingressAvg = parseFloat(ingressObj.analysis.avg) || 0
}
if (egressObj) {
egressAvg = parseFloat(egressObj.analysis.max) || 0
egressAvg = parseFloat(egressObj.analysis.avg) || 0
}
if ((ingressAvg + egressAvg) === 0) {
const totalObj = dnsData.find(d => d.name === 'network.total')

View File

@@ -32,13 +32,7 @@
</el-popover>
</div>
<div class="entity-tags" v-if="!hideTagArea">
<div v-for="tag in levelTwoTags"
:key="tag.value"
class="entity-tag"
:class="`entity-tag--level-two-${tag.type}`"
:style="getTagColor(tag.color)">
{{tag.value}}
</div>
<div v-for="tag in levelTwoTags" :key="tag.value" class="entity-tag" :class="`entity-tag--level-two-${tag.type}`">{{tag.value}}</div>
</div>
<!-- 分割线-->
<div class="dividing-line"></div>
@@ -60,11 +54,10 @@ import {
drillDownPanelTypeMapping,
entityType,
entityDetailTags,
tagValueLabelMapping,
riskLevelMapping,
entityDefaultColor
psiphon3IpType,
riskLevelMapping
} from '@/utils/constants'
import { selectElementText, copySelectionText, getTagColor } from '@/utils/tools'
import { selectElementText, copySelectionText } from '@/utils/tools'
import { ref } from 'vue'
import i18n from '@/i18n'
import { useRouter } from 'vue-router'
@@ -91,10 +84,16 @@ export default {
}
},
methods: {
getTagColor,
tagValueHandler (value) {
const find = tagValueLabelMapping.find(t => t.value === value)
return find ? find.name : value
tagValueHandler (k, k2, value) {
if (k === 'psiphon3Ip') {
if (k2 === 'type') {
const find = psiphon3IpType.find(t => t.value === value)
if (find) {
return find.name
}
}
}
return value
},
getData () {
this.toggleLoading(true)
@@ -115,13 +114,13 @@ export default {
Object.keys(res.data[k]).forEach(k2 => {
const find = entityDetailTags[this.entity.entityType].find(t => t.name === k2)
if (find) {
this.levelTwoTags.push({ key: k2, value: this.tagValueHandler(res.data[k][k2]), type: find.type })
this.levelTwoTags.push({ key: k2, value: this.tagValueHandler(k, k2, res.data[k][k2]), type: find.type })
}
})
}
})
if (_.isArray(res.data.userDefinedTags)) {
this.levelTwoTags = _.concat(this.levelTwoTags, res.data.userDefinedTags.map(tag => ({ value: tag.tagValue, color: tag.knowledgeBase ? tag.knowledgeBase.color : entityDefaultColor })))
this.levelTwoTags = _.concat(this.levelTwoTags, res.data.userDefinedTags.map(tag => ({ value: tag.tagValue, type: 'normal' })))
}
this.hideTagArea = _.isEmpty(this.levelTwoTags)
this.$nextTick(() => {

View File

@@ -50,7 +50,7 @@
<el-select
size="mini"
v-model="metric"
placeholder=" "
placeholder=""
popper-class="common-select"
:popper-append-to-body="false"
@change="metricChange"
@@ -66,7 +66,6 @@
size="mini"
v-model="lineRefer"
:disabled="!lineTab"
placeholder=" "
popper-class="common-select"
:popper-append-to-body="false"
@change="referenceSelectChange"
@@ -227,9 +226,6 @@ export default {
if (response.status === 200) {
this.isNoData = res.data.result.length === 0
this.showError = false
if (!active) {
this.tabs = _.cloneDeep(this.tabsTemplate)
}
if (this.isNoData) {
this.lineTab = ''
this.tabs = _.cloneDeep(this.tabsTemplate)
@@ -501,7 +497,7 @@ export default {
})
}
if (data && data.length > 0) {
if (data !== undefined && data.length > 0) {
newData.forEach((item) => {
item.type = getLineType(item.type)
if (item.type === val) {
@@ -514,24 +510,6 @@ export default {
})
}
lineData.splice(0, 1)
// TODO 下面的逻辑是判断total曲线的尾部数据从尾往前数0值的个数若个数大于0所有曲线都从尾部去掉相同数量的点最多4个
const totalData = lineData[0]
if (_.get(totalData, 'values', []).length > 4) {
let count = 0
for (let i = totalData.values.length - 1; i >= totalData.values.length - 4; i--) {
if (totalData.values[i].length > 1 && totalData.values[i][1] === 0) {
count++
} else {
break
}
}
if (count > 0) {
lineData.forEach(l => {
l.values.splice(l.values.length - count, count)
})
}
}
if (val === 'Sessions/s') {
const tabs = _.cloneDeep(this.tabsTemplate)
lineData.forEach((d, i) => {
@@ -549,7 +527,6 @@ export default {
})
this.tabs = tabs
this.$nextTick(() => {
this.lineRefer = 'Average'
this.echartsInit(this.tabs, true)
})
} else {
@@ -567,7 +544,7 @@ export default {
const self = this
tabs.forEach(e => {
e.unitType = type
if (e.name !== 'network.total' && parseFloat(e.analysis.max) === 0) {
if (e.name !== 'network.total' && parseFloat(e.analysis.avg) === 0) {
e.show = false
num += 1
} else {
@@ -577,18 +554,13 @@ export default {
}
}
if (self.lineTab === e.class) {
if (parseFloat(e.analysis.max) <= 0) {
if (parseFloat(e.analysis.avg) <= 0) {
self.lineTab = ''
self.lineRefer = ''
// self.init() // 后续多测试关注
self.init()
}
}
})
const emptyData = tabs.filter(d => parseFloat(d.analysis.max) === 0)
this.isNoData = emptyData.length === tabs.length
if (this.isNoData) {
return true
}
this.tabs = tabs
if (num === 5) {
tabs[0].invertTab = false

View File

@@ -20,7 +20,6 @@
<security-event v-else-if="tab.name === entityDetailTabsName.securityEvent && tab.name === activeTab" @toggleLoading="setLoading" :timeFilter="oneDayTimeFilter" @checkTag="setTag" />
<performance-event v-else-if="tab.name === entityDetailTabsName.performanceEvent && tab.name === activeTab" @toggleLoading="setLoading" :timeFilter="oneDayTimeFilter" @checkTag="setTag" />
<open-port v-else-if="tab.name === entityDetailTabsName.openPort && tab.name === activeTab" @toggleLoading="setLoading" :entity="entity" :timeFilter="oneDayTimeFilter" @checkTag="setTag"></open-port>
<behavior-pattern v-else-if="tab.name === entityDetailTabsName.behaviorPattern && tab.name === activeTab" @toggleLoading="setLoading" :entity="entity" :timeFilter="oneDayTimeFilter" @checkTag="setTag"></behavior-pattern>
</el-tab-pane>
</el-tabs>
</div>
@@ -29,27 +28,24 @@
<script>
import chartMixin from '@/views/charts2/chart-mixin'
import i18n from '@/i18n'
import { entityDetailTabsName, entityDetailTags } from '@/utils/constants'
import { entityDetailTabsName, entityDetailTags, psiphon3IpType } from '@/utils/constants'
import { reactive, ref } from 'vue'
import InformationAggregation from '@/views/charts2/charts/entityDetail/tabs/InformationAggregation'
import DomainNameResolution from '@/views/charts2/charts/entityDetail/tabs/DomainNameResolution'
import SecurityEvent from '@/views/charts2/charts/entityDetail/tabs/SecurityEvent'
import PerformanceEvent from '@/views/charts2/charts/entityDetail/tabs/PerformanceEvent'
import BehaviorPattern from '@/views/charts2/charts/entityDetail/tabs/BehaviorPattern'
import OpenPort from '@/views/charts2/charts/entityDetail/tabs/OpenPort'
import DigitalCertificate from '@/views/charts2/charts/entityDetail/tabs/DigitalCertificate'
import { overwriteUrl, urlParamsHandler } from '@/utils/tools'
import { useRoute } from 'vue-router'
import axios from 'axios'
import { api } from '@/utils/api'
import { tagValueLabelMapping } from '../../../../utils/constants'
export default {
name: 'EntityDetailTabs',
mixins: [chartMixin],
components: {
PerformanceEvent,
BehaviorPattern,
SecurityEvent,
InformationAggregation,
DomainNameResolution,
@@ -94,9 +90,6 @@ export default {
if (entityType !== 'app') {
tabs.unshift({ name: entityDetailTabsName.informationAggregation, label: i18n.global.t('entities.informationAggregation'), icon: 'cn-icon cn-icon-information-aggregation', tag: 0 })
}
if (entityType === 'ip') {
tabs.push({ name: entityDetailTabsName.behaviorPattern, label: i18n.global.t('entities.behaviorPattern'), icon: 'cn-icon cn-icon-behavior', tag: 0 })
}
const activeTab = ref(tabs[0].name)
const { query } = useRoute()
@@ -125,6 +118,7 @@ export default {
const openPort = axios.get(url, { params: params })
// const security = axios.get(`${api.entity.security}/${this.entity.entityType}`, { params: params })
// const performance = axios.get(`${api.entity.performance}/${this.entityType}`, { params: params })
Promise.all([informationAggregation, openPort]).then(response => {
if (response[0].status === 200) {
const list = []
@@ -142,7 +136,7 @@ export default {
Object.keys(r[k]).forEach(k2 => {
const find = entityDetailTags[this.entity.entityType].find(t => t.name === k2)
if (find) {
aggregation.intelligenceContent.push({ key: k2, value: this.tagValueHandler(r[k][k2]), type: find.type })
aggregation.intelligenceContent.push({ key: k2, value: this.tagValueHandler(k, k2, r[k][k2]), type: find.type })
}
})
}
@@ -165,10 +159,6 @@ export default {
// }
this.initSetTag(entityDetailTabsName.securityEvent, 0)
this.initSetTag(entityDetailTabsName.performanceEvent, 0)
if (this.entity.entityName === 'hqzc.wssp.hainan.gov.cn' || this.entity.entityName === '218.77.183.150') {
this.initSetTag(entityDetailTabsName.securityEvent, 3)
this.initSetTag(entityDetailTabsName.performanceEvent, 1)
}
})
// 域名解析
@@ -181,8 +171,7 @@ export default {
if (this.entity.entityType === 'ip') {
const appsOfIp = axios.get(api.entity.domainNameResolutionAboutAppsOfIp, { params: params })
const domainsOfIp = axios.get(api.entity.domainNameResolutionAboutDomainsOfIp, { params: params })
const behaviorPattern = axios.get(api.entity.behaviorPattern, { params: params })
this.promiseData(appsOfIp, domainsOfIp, behaviorPattern)
this.promiseData(appsOfIp, domainsOfIp)
}
if (this.entity.entityType === 'domain') {
@@ -202,18 +191,6 @@ export default {
const len1 = res[0].status === 200 ? res0.data.result.length : 0
const len2 = res[1].status === 200 ? res1.data.result.length : 0
this.initSetTag(entityDetailTabsName.relatedEntity, len1 + len2)
const behaviorPatternData = res[2].status === 200 ? res[2].data.data.result : 0
let behaviorPatternLen = 0
if (behaviorPatternData && behaviorPatternData[0]) {
const dataObject = behaviorPatternData[0]
Object.keys(dataObject).forEach(key => {
const value = Number(dataObject[key]) ? Number(dataObject[key]) : 0
if (value !== 0) {
behaviorPatternLen++
}
})
}
this.initSetTag(entityDetailTabsName.behaviorPattern, behaviorPatternLen)
break
}
case 'domain': {
@@ -274,9 +251,16 @@ export default {
case 'app': return api.entity.openPortOfApp
}
},
tagValueHandler (value) {
const find = tagValueLabelMapping.find(t => t.value === value)
return find ? find.name : value
tagValueHandler (k, k2, value) {
if (k === 'psiphon3Ip') {
if (k2 === 'type') {
const find = psiphon3IpType.find(t => t.value === value)
if (find) {
return find.name
}
}
}
return value
}
},
beforeUnmount () {

View File

@@ -1,189 +0,0 @@
<template>
<chart-error v-if="showError" :content="errorMsg" class="entity-detail-event-error"></chart-error>
<chart-no-data v-if="isNoData && !showError"></chart-no-data>
<div v-if="!isNoData && !showError" class="entity-detail-event-block" style="height: 100%;width: 100%;position: relative;">
<div class="behavior-pattern" >
<div class="behavior-pattern-legend" >
<div class="behavior-pattern-legend__item" v-for="(data, index) in tableData" :key="index">
<div class="legend-icon" :style="`background:${chartColorForBehaviorPattern[index%10]};`"></div>
<div class="legend-name">{{data.name}}</div>
<div class="legend-value" >{{ unitConvert(data.value, unitTypes.number).join('')}}</div>
<div class="legend-percent">{{ unitConvert(data.percent, unitTypes.percent).join('') }}</div>
</div>
</div>
<div id="entityIpRoseType" class="behavior-pattern-chart"></div>
<div class="chart-bottom-dot__right"></div>
<div class="chart-bottom-dot__left"></div>
</div>
</div>
</template>
<script>
import { dateFormatByAppearance } from '@/utils/date-util'
import * as echarts from 'echarts'
import { pieChartOption4 } from '@/views/charts2/charts/options/echartOption'
import { shallowRef } from 'vue'
import { entityDetailTabsName, chartColorForBehaviorPattern, unitTypes } from '@/utils/constants'
import unitConvert from '@/utils/unit-convert'
import axios from 'axios'
import { api } from '@/utils/api'
import { useRoute } from 'vue-router'
import chartMixin from '@/views/charts2/chart-mixin'
import ChartError from '@/components/common/Error'
import { toUpperCaseByString, reverseSortBy } from '@/utils/tools'
import ChartNoData from '@/views/charts/charts/ChartNoData'
export default {
name: 'BehaviorPattern',
components: { ChartError, ChartNoData },
mixins: [chartMixin],
data () {
return {
showError: false,
errorMsg: '',
tableData: []
}
},
setup () {
const { query } = useRoute()
const entityType = query.entityType
const entityName = query.entityName
return {
entityType,
entityName,
myChart: shallowRef(null),
chartColorForBehaviorPattern,
unitTypes
}
},
async mounted () {
await this.initData()
this.toggleLoading(true)
const timer = setTimeout(() => {
this.toggleLoading(false)
clearInterval(timer)
}, 200)
},
methods: {
unitConvert,
toUpperCaseByString,
dateFormatByAppearance,
initEcharts () {
this.chartOption = pieChartOption4
this.chartOption.angleAxis.data = []
this.chartOption.series[0].data = []
this.tableData.forEach((item, index) => {
this.chartOption.angleAxis.data.push(item.name)
this.chartOption.series[0].data.push(item.value)
})
const len = this.tableData.length
const endIndex = len + 1
this.tableData.forEach((item, i) => {
if (i !== endIndex) {
this.chartOption.angleAxis.data.push(item.name + '2')
this.chartOption.series[0].data.push(0)
}
})
const axisLabel = this.chartOption.angleAxis.axisLabel
this.chartOption.angleAxis.axisLabel = {
...axisLabel,
formatter: function (params, index) {
if (len > 15) {
if (index === 7) {
return params + '\n'
} else if (index === 8) {
return '\n' + params
}
}
if (index === endIndex) {
return params + '\n'
} else {
return params
}
}
}
const self = this
this.$nextTick(() => {
if (self.myChart) {
self.myChart.dispose()
}
const dom = document.getElementById('entityIpRoseType')
if (dom) {
self.myChart = echarts.init(dom)
self.myChart.setOption(this.chartOption)
self.myChart.dispatchAction({
type: 'takeGlobalCursor',
key: 'brush',
brushOption: {
brushType: 'lineX',
xAxisIndex: 'all',
brushMode: 'single',
throttleType: 'debounce'
}
})
}
})
},
async initData () {
const params = {
resource: this.entityName
}
this.toggleLoading(true)
await axios.get(`${api.entity.behaviorPattern}`, { params: params }).then(response => {
const res = response.data
if (response.status === 200) {
this.isNoData = res.data.result.length === 0
this.showError = false
if (!this.isNoData) {
const data = res.data.result
this.tableData = []
let sum = 0
if (data && data[0]) {
const dataObject = data[0]
Object.keys(dataObject).forEach(key => {
const value = Number(dataObject[key]) ? Number(dataObject[key]) : 0
if (value !== 0) {
sum = sum + value
this.tableData.push({
name: key,
value: value
})
}
})
this.tableData.forEach(item => {
item.percent = item.value / sum
})
this.tableData = this.tableData.sort(reverseSortBy('value'))
this.$emit('checkTag', entityDetailTabsName.behaviorPattern, this.tableData.length)
if (this.tableData.length === 0) {
this.isNoData = true
}
this.initEcharts()
}
}
} else {
this.httpError(res)
}
}).catch(e => {
console.error(e)
this.httpError(e)
}).finally(() => {
this.toggleLoading(false)
})
},
httpError (e) {
this.isNoData = false
this.showError = true
this.errorMsg = this.errorMsgHandler(e)
this.$emit('checkTag', entityDetailTabsName.behaviorPattern, 0)
}
}
}
</script>

View File

@@ -71,7 +71,7 @@
import chartMixin from '@/views/charts2/chart-mixin'
import axios from 'axios'
import { api } from '@/utils/api'
import { entityDetailTabsName, entityDetailTags, tagValueLabelMapping } from '@/utils/constants'
import { entityDetailTabsName, entityDetailTags, psiphon3IpType } from '@/utils/constants'
import { dateFormatByAppearance } from '@/utils/date-util'
import chartNoData from '@/views/charts/charts/ChartNoData'
@@ -95,7 +95,7 @@ export default {
tagValueHandler (k, k2, value) {
if (k === 'psiphon3Ip') {
if (k2 === 'type') {
const find = tagValueLabelMapping.find(t => t.value === value)
const find = psiphon3IpType.find(t => t.value === value)
if (find) {
return find.name
}

View File

@@ -11,7 +11,7 @@
<div class="cn-detection__case entity-detail-performance">
<div class="cn-detection__icon" :style="`background-color: ${eventSeverityColor[item.eventSecurity]}`"></div>
<div class="cn-detection__row">
<div class="cn-detection__header" style="padding-bottom: 0">
<div class="cn-detection__header">
<span
:test-id="`severity-color-block${index}`"
class="detection-event-severity-color-block"
@@ -38,11 +38,6 @@
<span>{{ $t('overall.duration') }}&nbsp;:&nbsp;&nbsp;&nbsp;</span>
<span :test-id="`duration-time${index}`">{{ unitConvert(item.durationMs, 'time', null, null, 0).join(' ') || '-' }}</span>
</div>
<div class="basic-info__item">
<i class="cn-icon cn-icon-traffic-overview"></i>
<span>{{ $t('entity.detail.anomaly') }}&nbsp;:&nbsp;&nbsp;&nbsp;</span>
<div id="anomalyChart" style="height: 20px; width: 100px;"></div>
</div>
</div>
</div>
</div>
@@ -56,19 +51,15 @@
<script>
import { dateFormatByAppearance } from '@/utils/date-util'
import { eventSeverityColor, entityDetailTabsName, unitTypes } from '@/utils/constants'
import { eventSeverityColor, entityDetailTabsName } from '@/utils/constants'
import unitConvert from '@/utils/unit-convert'
import axios from 'axios'
import { api } from '@/utils/api'
import { useRoute } from 'vue-router'
import chartMixin from '@/views/charts2/chart-mixin'
import ChartError from '@/components/common/Error'
import { reverseSortBy, sortBy, toUpperCaseByString } from '@/utils/tools'
import { toUpperCaseByString } from '@/utils/tools'
import ChartNoData from '@/views/charts/charts/ChartNoData'
import { markRaw } from 'vue'
import { metricOption } from '@/views/detections/options/detectionOptions'
import * as echarts from 'echarts'
import _ from 'lodash'
export default {
name: 'PerformanceEvent',
@@ -89,19 +80,18 @@ export default {
return {
entityType,
entityName,
chartOption: metricOption
entityName
}
},
mounted () {
this.initData()
/*this.isNoData = true
// this.initData()
this.isNoData = true
this.$emit('checkTag', entityDetailTabsName.performanceEvent, 0)
this.toggleLoading(true)
const timer = setTimeout(() => {
this.toggleLoading(false)
clearInterval(timer)
}, 200)*/
}, 200)
},
methods: {
unitConvert,
@@ -115,88 +105,31 @@ export default {
}
this.toggleLoading(true)
axios.get(`${api.entity.performance}/${this.entityType}`, { params: params }).then(response => {
const res = response.data
if (this.entityName === 'hqzc.wssp.hainan.gov.cn' || this.entityName === '218.77.183.150') {
setTimeout(() => {
this.toggleLoading(false)
this.isNoData = false
this.eventList = [
{
"serverIp": "1.1.1.1",
"domain": "www.baidu.com",
"appName": "ab",
"eventSeverity": "critical",
"eventType": "Http error",
"durationMs": 840000,
"startTime": new Date().getTime() - 1957 * 1000,
"endTime": 2222222222
}
]
this.metricList = [
[new Date().getTime() / 1000 - 2677, 2],
[new Date().getTime() / 1000 - 2557, 3],
[new Date().getTime() / 1000 - 2437, 2],
[new Date().getTime() / 1000 - 2317, 7],
[new Date().getTime() / 1000 - 2197, 8],
[new Date().getTime() / 1000 - 2077, 38],
[new Date().getTime() / 1000 - 1857, 12],
[new Date().getTime() / 1000 - 1637, 8],
[new Date().getTime() / 1000 - 1517, 7],
[new Date().getTime() / 1000 - 1277, 3],
[new Date().getTime() / 1000 - 1157, 1],
[new Date().getTime() / 1000 - 1037, 2]
]
this.$emit('checkTag', entityDetailTabsName.performanceEvent, 1)
this.$nextTick(() => {
this.initChart()
})
}, 200)
} else {
setTimeout(() => {
this.isNoData = true
this.toggleLoading(false)
this.eventList = []
this.$emit('checkTag', entityDetailTabsName.performanceEvent, 0)
}, 200)
/*axios.get(`${api.entity.performance}/${this.entityType}`, {params: params}).then(response => {
const res = response.data
if (response.status === 200) {
this.isNoData = res.data.result.length === 0
this.$emit('checkTag', entityDetailTabsName.performanceEvent, res.data.result.length)
this.showError = false
if (!this.isNoData) {
this.eventList = res.data.result
}
} else {
this.httpError(res)
if (response.status === 200) {
this.isNoData = res.data.result.length === 0
this.$emit('checkTag', entityDetailTabsName.performanceEvent, res.data.result.length)
this.showError = false
if (!this.isNoData) {
this.eventList = res.data.result
}
}).catch(e => {
console.error(e)
this.httpError(e)
}).finally(() => {
this.toggleLoading(false)
})*/
}
} else {
this.httpError(res)
}
}).catch(e => {
console.error(e)
this.httpError(e)
}).finally(() => {
this.toggleLoading(false)
})
},
httpError (e) {
this.isNoData = false
this.showError = true
this.errorMsg = this.errorMsgHandler(e)
this.$emit('checkTag', entityDetailTabsName.performanceEvent, 0)
},
initChart () {
this.metricChart = markRaw(echarts.init(document.getElementById('anomalyChart')))
this.chartOptionMetric = _.cloneDeep(this.chartOption)
this.chartOptionMetric.series[0].data = this.metricList.slice(0, 4).map(v => [Number(v[0]) * 1000, Number(v[1]), unitTypes.number])
this.chartOptionMetric.series[1].data = this.metricList.slice(3, 9).map(v => [Number(v[0]) * 1000, Number(v[1]), unitTypes.number])
this.chartOptionMetric.series[2].data = this.metricList.slice(8, 11).map(v => [Number(v[0]) * 1000, Number(v[1]), unitTypes.number])
this.chartOptionMetric.series.forEach(item => {
item.name = 'Http error'
})
this.chartOptionMetric && this.metricChart.setOption(this.chartOptionMetric)
}
}
}

View File

@@ -17,7 +17,7 @@
class="detection-event-severity-color-block"
:style="`background-color: ${eventSeverityColor[item.eventSeverity]}`">
</span>
<span class="detection-event-severity-block">{{ item.eventName || '-' }}</span>
<span class="detection-event-severity-block">{{ toUpperCaseByString(item.securityType) || '-' }}</span>
<i class="cn-icon cn-icon-attacker"></i>
<span :test-id="`offender-ip${index}`">{{ item.offenderIp || '-' }}</span>
<div class="domain">{{ item.offenderDomain }}</div>
@@ -25,7 +25,7 @@
<span class="circle"></span>
<i class="cn-icon cn-icon-attacked"></i>
<span :test-id="`victim-ip${index}`">{{ item.victimIp || '-' }}</span>
<div class="domain">{{ item.domain }}</div>
<div class="domain">{{ item.victimDomain }}</div>
</div>
<div class="cn-detection__body">
<div class="body__basic-info">
@@ -58,7 +58,7 @@
<div class="basic-info__item">
<i class="cn-icon cn-icon-time2"></i>
<span>{{ $t('detection.list.startTime') }}&nbsp;:&nbsp;&nbsp;</span>
<span>{{ dateFormatByAppearance(parseFloat(item.startTime)) || '-' }}</span>
<span>{{ dateFormatByAppearance(item.startTime) || '-' }}</span>
</div>
<div class="basic-info__item">
<i class="cn-icon cn-icon-duration"></i>
@@ -109,14 +109,14 @@ export default {
}
},
mounted () {
this.initData()
/*this.isNoData = true
// this.initData()
this.isNoData = true
this.$emit('checkTag', entityDetailTabsName.securityEvent, 0)
this.toggleLoading(true)
const timer = setTimeout(() => {
this.toggleLoading(false)
clearInterval(timer)
}, 200)*/
}, 200)
},
methods: {
unitConvert,
@@ -130,101 +130,25 @@ export default {
}
this.toggleLoading(true)
if (this.entityName === 'hqzc.wssp.hainan.gov.cn' || this.entityName === '218.77.183.150') {
setTimeout(() => {
this.toggleLoading(false)
this.isNoData = false
this.eventList = [
{
eventId: '1717034000326447105',
eventType: 'Command and Control',
eventName: 'Mirai',
eventKey: '5,26.26.26.1,192.168.38.73',
ruleId: '5',
ruleType: 'indicator_match',
isBuiltin: '1',
eventSeverity: 'critical',
offenderIp: '119.102.149.177',
victimIp: '218.77.183.150',
domain: 'hqzc.wssp.hainan.gov.cn',
app: '',
startTime: new Date().getTime() - 3600 * 1000,
endTime: '1698207720',
durationMs: 1613000,
matchTimes: '1',
status: '1',
eventInfo: '{\"knowledge_id\":\"8\",\"name\":\"built_in_ioc_darkweb\",\"ioc_type\":\"ip\",\"ioc_value\":\"26.26.26.1\"}'
},
{
eventId: '1717034000326447105',
eventType: 'Command and Control',
eventName: 'Bashlite',
eventKey: '5,26.26.26.1,192.168.38.73',
ruleId: '5',
ruleType: 'indicator_match',
isBuiltin: '1',
eventSeverity: 'critical',
offenderIp: '142.4.196.195',
victimIp: '218.77.183.150',
domain: 'hqzc.wssp.hainan.gov.cn',
app: '',
startTime: new Date().getTime() - 1600 * 1000,
endTime: '1698207720',
durationMs: 1285000,
matchTimes: '1',
status: '1',
eventInfo: '{\"knowledge_id\":\"8\",\"name\":\"built_in_ioc_darkweb\",\"ioc_type\":\"ip\",\"ioc_value\":\"26.26.26.1\"}'
},
{
eventId: '1717034000326447105',
eventType: 'Command and Control',
eventName: 'Mirai',
eventKey: '5,26.26.26.1,192.168.38.73',
ruleId: '5',
ruleType: 'indicator_match',
isBuiltin: '1',
eventSeverity: 'critical',
offenderIp: '103.119.112.54',
victimIp: '218.77.183.150',
domain: 'hqzc.wssp.hainan.gov.cn',
app: '',
startTime: new Date().getTime() - 2600 * 1000,
endTime: '1698207720',
durationMs: 2280000,
matchTimes: '1',
status: '1',
eventInfo: '{\"knowledge_id\":\"8\",\"name\":\"built_in_ioc_darkweb\",\"ioc_type\":\"ip\",\"ioc_value\":\"26.26.26.1\"}'
}
]
this.$emit('checkTag', entityDetailTabsName.securityEvent, 3)
}, 200)
} else {
setTimeout(() => {
this.isNoData = true
this.toggleLoading(false)
this.eventList = []
this.$emit('checkTag', entityDetailTabsName.securityEvent, 0)
}, 200)
/*axios.get(`${api.entity.security}/${this.entityType}`, { params: params }).then(response => {
const res = response.data
axios.get(`${api.entity.security}/${this.entityType}`, { params: params }).then(response => {
const res = response.data
if (response.status === 200) {
this.isNoData = res.data.result.length === 0
this.$emit('checkTag', entityDetailTabsName.securityEvent, res.data.result.length)
this.showError = false
if (!this.isNoData) {
this.eventList = res.data.result
}
} else {
this.httpError(res)
if (response.status === 200) {
this.isNoData = res.data.result.length === 0
this.$emit('checkTag', entityDetailTabsName.securityEvent, res.data.result.length)
this.showError = false
if (!this.isNoData) {
this.eventList = res.data.result
}
}).catch(e => {
console.error(e)
this.httpError(e)
}).finally(() => {
this.toggleLoading(false)
})*/
}
} else {
this.httpError(res)
}
}).catch(e => {
console.error(e)
this.httpError(e)
}).finally(() => {
this.toggleLoading(false)
})
},
httpError (e) {
this.$emit('checkTag', entityDetailTabsName.securityEvent, 0)

View File

@@ -122,6 +122,7 @@ import { drillDownPanelTypeMapping, storageKey, unitTypes } from '@/utils/consta
import { getSecond } from '@/utils/date-util'
import ChartError from '@/components/common/Error'
import axios from 'axios'
// import { analysis, nextHopAnalysis } from './test-data'
export default {
name: 'LinkBlock',
@@ -192,6 +193,8 @@ export default {
const res = []
res[0] = response[0].data
res[1] = response[1].data
/*res[0] = analysis.data
res[1] = nextHopAnalysis.data*/
if (response[0].status === 200) {
this.showError1 = false
@@ -211,6 +214,9 @@ export default {
hit.outBandwidth = info.bandwidth
} else if (info.direction === 'in') {
hit.inBandwidth = info.bandwidth
} else if (info.direction === '2') {
hit.outBandwidth = info.bandwidth
hit.inBandwidth = info.bandwidth
}
} else {
const hit = {
@@ -222,6 +228,9 @@ export default {
hit.outBandwidth = info.bandwidth
} else if (info.direction === 'in') {
hit.inBandwidth = info.bandwidth
} else if (info.direction === '2') {
hit.outBandwidth = info.bandwidth
hit.inBandwidth = info.bandwidth
}
data.push(hit)
}
@@ -235,6 +244,9 @@ export default {
item.outLinkId = info.originalLinkId
} else if (info.direction === 'in') {
item.inLinkId = info.originalLinkId
} else if (info.direction === '2') {
item.outLinkId = info.originalLinkId
item.inLinkId = info.originalLinkId
}
})
})

View File

@@ -17,6 +17,7 @@ import PopoverContent from './LinkDirectionGrid/PopoverContent'
import { computeScore } from '@/utils/tools'
import axios from 'axios'
import _ from 'lodash'
// import { bigramAnalysis, bigramNextHopAnalysis } from './test-data'
export default {
name: 'LinkDirectionGrid',
@@ -30,8 +31,7 @@ export default {
isLinkShowError: false, // 显示左侧链路报错标识
linkErrorMsg: '', // 左侧链路的报错信息
isNextShowError: false, // 显示右侧下一跳报错标识
nextErrorMsg: '', // 右侧下一跳的报错信息
scoreDataState: false // 评分数据是否加载完成
nextErrorMsg: '' // 右侧下一跳的报错信息
}
},
components: {
@@ -42,23 +42,6 @@ export default {
handler () {
this.init()
}
},
scoreBaseState (n) {
if (n && this.scoreDataState) {
this.handleScoreData(this.linkGridData)
this.handleScoreData(this.nextGridData)
}
},
scoreDataState (n) {
if (n && this.scoreBaseState) {
this.handleScoreData(this.linkGridData)
this.handleScoreData(this.nextGridData)
}
}
},
computed: {
scoreBaseState () {
return this.$store.getters.scoreBaseReady
}
},
mounted () {
@@ -66,7 +49,6 @@ export default {
},
methods: {
init () {
this.scoreDataState = false
// 链路基本信息
let linkInfo = localStorage.getItem(storageKey.linkInfo)
linkInfo = JSON.parse(linkInfo)
@@ -93,6 +75,8 @@ export default {
const res = []
res[0] = response[0].data
res[1] = response[1].data
/*res[0] = bigramAnalysis.data
res[1] = bigramNextHopAnalysis.data*/
if (response[0].status === 200) {
this.isLinkShowError = false
// 链路流量数据
@@ -120,6 +104,12 @@ export default {
}
})
linkGridData.push({ linkId: link.linkId, out: outList })
} else if (link.direction === '2') {
const outList = []
linkInfo.forEach(link1 => {
outList.push({ linkId: link1.linkId, noData: true })
})
linkGridData.push({ linkId: link.linkId, out: outList })
}
})
@@ -136,12 +126,15 @@ export default {
d.usageMore90 = outUsage >= 0.9 || inUsage >= 0.9
// 计算npm分数
// 分数低于3分赋红点
// d.score = this.localComputeScore(d)
d.score = this.localComputeScore(d)
// d.scoreLow3 = d.score < 3 || d.score === '-'
d.scoreLow3 = d.score < 3 || d.score === '-'
/*const xAxis = inLink.linkId.split('Hundredgige').pop() - 1
const yAxis = outLink.linkId.split('Hundredgige').pop() - 1*/
const xAxis = inLink.linkId.split('UFONE-RWP-GI-LANPHY-100G-').pop() - 1
const yAxis = outLink.linkId.split('UFONE-RWP-GI-LANPHY-100G-').pop() - 1
const xAxis = inLink.linkId.split('Hundredgige').pop() - 1
const yAxis = outLink.linkId.split('Hundredgige').pop() - 1
linkGridData[xAxis].out[yAxis] = {
noData: false,
linkId: outLink.linkId,
@@ -156,11 +149,11 @@ export default {
})
// 一行如果无数据则删除该行默认10*10矩阵
const rowXIndex = 0
this.handleXRowNoData(linkGridData, rowXIndex)
// const rowXIndex = 0
// this.handleXRowNoData(linkGridData, rowXIndex)
// 一列如果无数据则删除该列默认10*10矩阵
const rowYIndex = 0
this.handleYRowNoData(linkGridData, rowYIndex)
// const rowYIndex = 0
// this.handleYRowNoData(linkGridData, rowYIndex)
this.isLinkNoData = linkGridData.length === 0
this.linkGridData = linkGridData
}
@@ -185,12 +178,33 @@ export default {
this.isNextNoData = nextLinkData.length === 0
if (!this.isNextNoData) {
const inLinkDirections = []
const outLinkDirections = []
nextLinkData.forEach(l => {
if (inLinkDirections.indexOf(l.inLinkDirection) === -1) {
inLinkDirections.push(l.inLinkDirection)
}
if (outLinkDirections.indexOf(l.outLinkDirection) === -1) {
outLinkDirections.push(l.outLinkDirection)
}
})
inLinkDirections.sort()
outLinkDirections.sort()
// 链路下一跳数据
let nextGridData = []
const nextGridTemplate = [
{ linkId: 'Hundredgige2', nextHop: 'City2', out: [] },
{ linkId: 'Hundredgige1', nextHop: 'City1', out: [] },
{ linkId: 'Hundredgige4', nextHop: 'City3', out: [] }
const nextGridData = inLinkDirections.map(inLink => ({ nextHop: inLink }))
nextGridData.forEach((link, index) => {
link.out = outLinkDirections.map((outLink, index1) => {
return {
coordinate: `${index}-${index1}`,
noData: true,
nextHop: outLink
}
})
})
/*const nextGridTemplate = [
{ linkId: 'Hundredgige2', nextHop: '太原', out: [] },
{ linkId: 'Hundredgige1', nextHop: '西安', out: [] },
{ linkId: 'Hundredgige4', nextHop: '西宁', out: [] }
]
nextGridData = JSON.parse(JSON.stringify(nextGridTemplate))
nextGridData.forEach(link => {
@@ -200,11 +214,11 @@ export default {
link1.coordinate = `${link.linkId}-${link1.linkId}`
delete link1.out
})
})
})*/
nextLinkData.forEach(d => {
const inLink = linkInfo.find(l => l.nextHop === d.inLinkDirection && l.direction === 'in')
const outLink = linkInfo.find(l => l.nextHop === d.outLinkDirection && l.direction === 'out')
const inLink = linkInfo.find(l => l.nextHop === d.inLinkDirection && (l.direction === 'in' || l.direction === '2'))
const outLink = linkInfo.find(l => l.nextHop === d.outLinkDirection && (l.direction === 'out' || l.direction === '2'))
if (inLink && outLink) {
// const data = nextGridData.find(g => g.linkId === inLink.linkId)
@@ -212,10 +226,10 @@ export default {
let outBandwidth = 0
let inBandwidth = 0
linkInfo.forEach((item) => {
if (item.nextHop === d.outLinkDirection && item.direction === 'out') {
if (item.nextHop === d.outLinkDirection && (item.direction === 'out' || item.direction === '2')) {
outBandwidth += item.bandwidth
}
if (item.nextHop === d.inLinkDirection && item.direction === 'in') {
if (item.nextHop === d.inLinkDirection && (item.direction === 'in' || item.direction === '2')) {
inBandwidth += item.bandwidth
}
})
@@ -229,19 +243,19 @@ export default {
d.usageMore90 = outUsage >= 0.9 || inUsage >= 0.9
// 计算npm分数
// 分数低于3分赋红点
// d.score = this.localComputeScore(d)
d.score = this.localComputeScore(d)
// d.scoreLow3 = d.score < 3 || d.score === '-'
d.scoreLow3 = d.score < 3 || d.score === '-'
const xAxis = inLink.linkId
const yAxis = outLink.linkId
const xAxis = inLinkDirections.indexOf(d.inLinkDirection)
const yAxis = outLinkDirections.indexOf(d.outLinkDirection)
nextGridData.forEach((link, index) => {
link.out.forEach((link1, index1) => {
if (link1.coordinate === (xAxis + '-' + yAxis)) {
nextGridData[index].out[index1] = {
coordinate: link1.coordinate,
noData: false,
linkId: outLink.linkId,
// linkId: outLink.linkId,
nextHop: outLink.nextHop,
outUsage: outUsage,
inUsage: inUsage,
@@ -273,7 +287,6 @@ export default {
}
}
}).finally(() => {
this.scoreDataState = true
this.toggleLoading(false)
})
},
@@ -302,23 +315,6 @@ export default {
score = computeScore(dataScore)
return score
},
handleScoreData (data) {
data.forEach(d => {
if (d.out) {
d.out.forEach(t => {
const data = {
establishLatencyMs: t.establishLatencyMs,
httpResponseLatency: t.httpResponseLatency,
sslConLatency: t.sslConLatency,
tcpLostlenPercent: t.tcpLostlenPercent,
pktRetransPercent: t.pktRetransPercent
}
t.score = computeScore(data, this.$store.getters.getScoreBase)
t.scoreLow3 = t.score < 3 || t.score === '-'
})
}
})
},
/**
* 计算popover弹窗和右侧数据模块的宽度
* 弹窗最小宽度为360px右侧数据最小宽度为75px右侧数据每大一位popover弹窗宽度增加7px

View File

@@ -42,7 +42,6 @@
<el-select
size="mini"
v-model="lineMetric"
placeholder=" "
popper-class="common-select"
:popper-append-to-body="false"
@change="metricSelectChange"
@@ -169,9 +168,6 @@ export default {
if (response.status === 200) {
this.showError = false
this.isNoData = res.data.result.length === 0
if (!active) {
this.tabs = dataForLinkTrafficLine.tabs
}
if (this.isNoData) {
this.lineTab = ''
this.tabs = dataForLinkTrafficLine.tabs
@@ -404,17 +400,12 @@ export default {
if (parseFloat(e.analysis.max) <= 0) {
this.lineTab = ''
this.lineRefer = ''
// this.init() // 暂时注掉,后续观察
// this.init()
}
}
})
const emptyData = linkData.filter(d => parseFloat(d.analysis.max) === 0)
this.isNoData = emptyData.length === linkData.length
if (this.isNoData) {
return true
}
this.tabs = linkData
// 如果三者avg都为0时至少保证total显示
// 如果三者max都为0时至少保证total显示
const ingressObj = linkData.find(d => d.name === 'linkMonitor.ingress')
const egressObj = linkData.find(d => d.name === 'linkMonitor.egress')
let ingressAvg = 0

View File

@@ -87,14 +87,7 @@ export default {
bandWidth: 0,
loading: false,
showError: false,
errorMsg: '',
scoreDataState: false,
performanceData: {}
}
},
computed: {
scoreBaseState () {
return this.$store.getters.scoreBaseReady
errorMsg: ''
}
},
watch: {
@@ -102,16 +95,6 @@ export default {
handler (n) {
this.linkTrafficData()
}
},
scoreBaseState (n) {
if (n && this.scoreDataState) {
this.handleScoreData()
}
},
scoreDataState (n) {
if (n && this.scoreBaseState) {
this.handleScoreData()
}
}
},
methods: {
@@ -120,9 +103,6 @@ export default {
startTime: getSecond(this.timeFilter.startTime),
endTime: getSecond(this.timeFilter.endTime)
}
this.loading = true
this.performanceData = {}
this.scoreDataState = false
if (this.queryCondition) {
const condition = this.queryCondition.split(' or ')
if (condition.length > 1) {
@@ -162,6 +142,7 @@ export default {
this.bandWidth = bandwidthAll
}
}
this.loading = true
axios.get(api.linkMonitor.networkAnalysis, { params: params }).then(response => {
const res = response.data
if (response.status === 200) {
@@ -171,7 +152,7 @@ export default {
this.linkTrafficListData = {}
this.linkTrafficListData.npmScore = '-'
} else {
this.performanceData = {
const data = {
establishLatencyMs: _.get(res.data.result[0], 'establishLatencyMs', null),
httpResponseLatency: _.get(res.data.result[0], 'httpResponseLatency', null),
sslConLatency: _.get(res.data.result[0], 'sslConLatency', null),
@@ -179,24 +160,19 @@ export default {
pktRetransPercent: _.get(res.data.result[0], 'pktRetransPercent', null)
}
this.linkTrafficListData = res.data.result[0]
// this.linkTrafficListData.npmScore = computeScore(data)
this.linkTrafficListData.npmScore = computeScore(data)
}
} else {
this.showError = true
this.errorMsg = res.message
}
}).catch(e => {
console.error(e)
this.showError = true
this.errorMsg = e.message
this.isNoData = false
}).finally(() => {
this.loading = false
this.scoreDataState = true
})
},
handleScoreData () {
this.linkTrafficListData.npmScore = computeScore(this.performanceData, this.$store.getters.getScoreBase)
}
},
mounted () {

View File

@@ -0,0 +1,994 @@
export default [
{
value: 'Afghanistan',
label: 'Afghanistan'
},
{
value: 'Albania',
label: 'Albania'
},
{
value: 'Algeria',
label: 'Algeria'
},
{
value: 'American Samoa',
label: 'American Samoa'
},
{
value: 'Andorra',
label: 'Andorra'
},
{
value: 'Angola',
label: 'Angola'
},
{
value: 'Anguilla',
label: 'Anguilla'
},
{
value: 'Antarctica',
label: 'Antarctica'
},
{
value: 'Antigua and Barbuda',
label: 'Antigua and Barbuda'
},
{
value: 'Argentina',
label: 'Argentina'
},
{
value: 'Armenia',
label: 'Armenia'
},
{
value: 'Aruba',
label: 'Aruba'
},
{
value: 'Australia',
label: 'Australia'
},
{
value: 'Austria',
label: 'Austria'
},
{
value: 'Azerbaijan',
label: 'Azerbaijan'
},
{
value: 'Bahamas (the)',
label: 'Bahamas (the)'
},
{
value: 'Bahrain',
label: 'Bahrain'
},
{
value: 'Bangladesh',
label: 'Bangladesh'
},
{
value: 'Barbados',
label: 'Barbados'
},
{
value: 'Belarus',
label: 'Belarus'
},
{
value: 'Belgium',
label: 'Belgium'
},
{
value: 'Belize',
label: 'Belize'
},
{
value: 'Benin',
label: 'Benin'
},
{
value: 'Bermuda',
label: 'Bermuda'
},
{
value: 'Åland Islands',
label: 'Åland Islands'
},
{
value: 'Bhutan',
label: 'Bhutan'
},
{
value: 'Bolivia (Plurinational State of)',
label: 'Bolivia (Plurinational State of)'
},
{
value: 'Bonaire, Sint Eustatius and Saba',
label: 'Bonaire, Sint Eustatius and Saba'
},
{
value: 'Bosnia and Herzegovina',
label: 'Bosnia and Herzegovina'
},
{
value: 'Botswana',
label: 'Botswana'
},
{
value: 'Bouvet Island',
label: 'Bouvet Island'
},
{
value: 'Brazil',
label: 'Brazil'
},
{
value: 'British Indian Ocean Territory (the)',
label: 'British Indian Ocean Territory (the)'
},
{
value: 'Brunei Darussalam',
label: 'Brunei Darussalam'
},
{
value: 'Bulgaria',
label: 'Bulgaria'
},
{
value: 'Burkina Faso',
label: 'Burkina Faso'
},
{
value: 'Burundi',
label: 'Burundi'
},
{
value: 'Cabo Verde',
label: 'Cabo Verde'
},
{
value: 'Cambodia',
label: 'Cambodia'
},
{
value: 'Cameroon',
label: 'Cameroon'
},
{
value: 'Canada',
label: 'Canada'
},
{
value: 'Cayman Islands (the)',
label: 'Cayman Islands (the)'
},
{
value: 'Central African Republic (the)',
label: 'Central African Republic (the)'
},
{
value: 'Chad',
label: 'Chad'
},
{
value: 'Chile',
label: 'Chile'
},
{
value: 'China',
label: 'China'
},
{
value: 'Christmas Island',
label: 'Christmas Island'
},
{
value: 'Cocos (Keeling) Islands (the)',
label: 'Cocos (Keeling) Islands (the)'
},
{
value: 'Colombia',
label: 'Colombia'
},
{
value: 'Comoros (the)',
label: 'Comoros (the)'
},
{
value: 'Congo (the Democratic Republic of the)',
label: 'Congo (the Democratic Republic of the)'
},
{
value: 'Congo (the)',
label: 'Congo (the)'
},
{
value: 'Cook Islands (the)',
label: 'Cook Islands (the)'
},
{
value: 'Costa Rica',
label: 'Costa Rica'
},
{
value: 'Croatia',
label: 'Croatia'
},
{
value: 'Cuba',
label: 'Cuba'
},
{
value: 'Curaçao',
label: 'Curaçao'
},
{
value: 'Cyprus',
label: 'Cyprus'
},
{
value: 'Czech Republic',
label: 'Czech Republic'
},
{
value: "Côte d'Ivoire",
label: "Côte d'Ivoire"
},
{
value: 'Denmark',
label: 'Denmark'
},
{
value: 'Djibouti',
label: 'Djibouti'
},
{
value: 'Dominica',
label: 'Dominica'
},
{
value: 'Dominican Republic (the)',
label: 'Dominican Republic (the)'
},
{
value: 'Ecuador',
label: 'Ecuador'
},
{
value: 'Egypt',
label: 'Egypt'
},
{
value: 'El Salvador',
label: 'El Salvador'
},
{
value: 'Equatorial Guinea',
label: 'Equatorial Guinea'
},
{
value: 'Eritrea',
label: 'Eritrea'
},
{
value: 'Estonia',
label: 'Estonia'
},
{
value: 'Eswatini',
label: 'Eswatini'
},
{
value: 'Ethiopia',
label: 'Ethiopia'
},
{
value: 'Falkland Islands (the) [Malvinas]',
label: 'Falkland Islands (the) [Malvinas]'
},
{
value: 'Faroe Islands (the)',
label: 'Faroe Islands (the)'
},
{
value: 'Fiji',
label: 'Fiji'
},
{
value: 'Finland',
label: 'Finland'
},
{
value: 'France',
label: 'France'
},
{
value: 'French Guiana',
label: 'French Guiana'
},
{
value: 'French Polynesia',
label: 'French Polynesia'
},
{
value: 'French Southern Territories (the)',
label: 'French Southern Territories (the)'
},
{
value: 'Gabon',
label: 'Gabon'
},
{
value: 'Gambia (the)',
label: 'Gambia (the)'
},
{
value: 'Georgia',
label: 'Georgia'
},
{
value: 'Germany',
label: 'Germany'
},
{
value: 'Ghana',
label: 'Ghana'
},
{
value: 'Gibraltar',
label: 'Gibraltar'
},
{
value: 'Greece',
label: 'Greece'
},
{
value: 'Greenland',
label: 'Greenland'
},
{
value: 'Grenada',
label: 'Grenada'
},
{
value: 'Guadeloupe',
label: 'Guadeloupe'
},
{
value: 'Guam',
label: 'Guam'
},
{
value: 'Guatemala',
label: 'Guatemala'
},
{
value: 'Guernsey',
label: 'Guernsey'
},
{
value: 'Guinea',
label: 'Guinea'
},
{
value: 'Guinea-Bissau',
label: 'Guinea-Bissau'
},
{
value: 'Guyana',
label: 'Guyana'
},
{
value: 'Haiti',
label: 'Haiti'
},
{
value: 'Heard Island and McDonald Islands',
label: 'Heard Island and McDonald Islands'
},
{
value: 'Holy See (the)',
label: 'Holy See (the)'
},
{
value: 'Honduras',
label: 'Honduras'
},
{
value: 'Hong Kong',
label: 'Hong Kong'
},
{
value: 'Hungary',
label: 'Hungary'
},
{
value: 'Iceland',
label: 'Iceland'
},
{
value: 'India',
label: 'India'
},
{
value: 'Indonesia',
label: 'Indonesia'
},
{
value: 'Iran (Islamic Republic of)',
label: 'Iran (Islamic Republic of)'
},
{
value: 'Iraq',
label: 'Iraq'
},
{
value: 'Ireland',
label: 'Ireland'
},
{
value: 'Isle of Man',
label: 'Isle of Man'
},
{
value: 'Israel',
label: 'Israel'
},
{
value: 'Italy',
label: 'Italy'
},
{
value: 'Jamaica',
label: 'Jamaica'
},
{
value: 'Japan',
label: 'Japan'
},
{
value: 'Jersey',
label: 'Jersey'
},
{
value: 'Jordan',
label: 'Jordan'
},
{
value: 'Kazakhstan',
label: 'Kazakhstan'
},
{
value: 'Kenya',
label: 'Kenya'
},
{
value: 'Kiribati',
label: 'Kiribati'
},
{
value: 'Korea',
label: 'Korea'
},
{
value: 'Kuwait',
label: 'Kuwait'
},
{
value: 'Kyrgyzstan',
label: 'Kyrgyzstan'
},
{
value: "Lao People's Democratic Republic (the)",
label: "Lao People's Democratic Republic (the)"
},
{
value: 'Latvia',
label: 'Latvia'
},
{
value: 'Lebanon',
label: 'Lebanon'
},
{
value: 'Lesotho',
label: 'Lesotho'
},
{
value: 'Liberia',
label: 'Liberia'
},
{
value: 'Libya',
label: 'Libya'
},
{
value: 'Liechtenstein',
label: 'Liechtenstein'
},
{
value: 'Lithuania',
label: 'Lithuania'
},
{
value: 'Luxembourg',
label: 'Luxembourg'
},
{
value: 'Macao',
label: 'Macao'
},
{
value: 'Madagascar',
label: 'Madagascar'
},
{
value: 'Malawi',
label: 'Malawi'
},
{
value: 'Malaysia',
label: 'Malaysia'
},
{
value: 'Maldives',
label: 'Maldives'
},
{
value: 'Mali',
label: 'Mali'
},
{
value: 'Malta',
label: 'Malta'
},
{
value: 'Marshall Islands (the)',
label: 'Marshall Islands (the)'
},
{
value: 'Martinique',
label: 'Martinique'
},
{
value: 'Mauritania',
label: 'Mauritania'
},
{
value: 'Mauritius',
label: 'Mauritius'
},
{
value: 'Mayotte',
label: 'Mayotte'
},
{
value: 'Mexico',
label: 'Mexico'
},
{
value: 'Micronesia (Federated States of)',
label: 'Micronesia (Federated States of)'
},
{
value: 'Moldova (the Republic of)',
label: 'Moldova (the Republic of)'
},
{
value: 'Monaco',
label: 'Monaco'
},
{
value: 'Mongolia',
label: 'Mongolia'
},
{
value: 'Montenegro',
label: 'Montenegro'
},
{
value: 'Montserrat',
label: 'Montserrat'
},
{
value: 'Morocco',
label: 'Morocco'
},
{
value: 'Mozambique',
label: 'Mozambique'
},
{
value: 'Myanmar',
label: 'Myanmar'
},
{
value: 'Namibia',
label: 'Namibia'
},
{
value: 'Nauru',
label: 'Nauru'
},
{
value: 'Nepal',
label: 'Nepal'
},
{
value: 'Netherlands',
label: 'Netherlands'
},
{
value: 'New Caledonia',
label: 'New Caledonia'
},
{
value: 'New Zealand',
label: 'New Zealand'
},
{
value: 'Nicaragua',
label: 'Nicaragua'
},
{
value: 'Niger',
label: 'Niger'
},
{
value: 'Nigeria',
label: 'Nigeria'
},
{
value: 'Niue',
label: 'Niue'
},
{
value: 'Norfolk Island',
label: 'Norfolk Island'
},
{
value: 'North Macedonia',
label: 'North Macedonia'
},
{
value: 'Northern Mariana Islands (the)',
label: 'Northern Mariana Islands (the)'
},
{
value: 'Norway',
label: 'Norway'
},
{
value: 'Oman',
label: 'Oman'
},
{
value: 'Pakistan',
label: 'Pakistan'
},
{
value: 'Palau',
label: 'Palau'
},
{
value: 'Palestine, State of',
label: 'Palestine, State of'
},
{
value: 'Panama',
label: 'Panama'
},
{
value: 'Papua New Guinea',
label: 'Papua New Guinea'
},
{
value: 'Paraguay',
label: 'Paraguay'
},
{
value: 'Peru',
label: 'Peru'
},
{
value: 'Philippines',
label: 'Philippines'
},
{
value: 'Pitcairn',
label: 'Pitcairn'
},
{
value: 'Poland',
label: 'Poland'
},
{
value: 'Portugal',
label: 'Portugal'
},
{
value: 'Puerto Rico',
label: 'Puerto Rico'
},
{
value: 'Qatar',
label: 'Qatar'
},
{
value: 'Romania',
label: 'Romania'
},
{
value: 'Russian Federation',
label: 'Russian Federation'
},
{
value: 'Rwanda',
label: 'Rwanda'
},
{
value: 'Réunion',
label: 'Réunion'
},
{
value: 'Saint Barthélemy',
label: 'Saint Barthélemy'
},
{
value: 'Saint Helena, Ascension and Tristan da Cunha',
label: 'Saint Helena, Ascension and Tristan da Cunha'
},
{
value: 'Saint Kitts and Nevis',
label: 'Saint Kitts and Nevis'
},
{
value: 'Saint Lucia',
label: 'Saint Lucia'
},
{
value: 'Saint Martin',
label: 'Saint Martin'
},
{
value: 'Saint Pierre and Miquelon',
label: 'Saint Pierre and Miquelon'
},
{
value: 'Saint Vincent and the Grenadines',
label: 'Saint Vincent and the Grenadines'
},
{
value: 'Samoa',
label: 'Samoa'
},
{
value: 'San Marino',
label: 'San Marino'
},
{
value: 'Sao Tome and Principe',
label: 'Sao Tome and Principe'
},
{
value: 'Saudi Arabia',
label: 'Saudi Arabia'
},
{
value: 'Senegal',
label: 'Senegal'
},
{
value: 'Serbia',
label: 'Serbia'
},
{
value: 'Seychelles',
label: 'Seychelles'
},
{
value: 'Sierra Leone',
label: 'Sierra Leone'
},
{
value: 'Singapore',
label: 'Singapore'
},
{
value: 'Sint Maarten',
label: 'Sint Maarten'
},
{
value: 'Slovakia',
label: 'Slovakia'
},
{
value: 'Slovenia',
label: 'Slovenia'
},
{
value: 'Solomon Islands',
label: 'Solomon Islands'
},
{
value: 'Somalia',
label: 'Somalia'
},
{
value: 'South Africa',
label: 'South Africa'
},
{
value: 'South Georgia and the South Sandwich Islands',
label: 'South Georgia and the South Sandwich Islands'
},
{
value: 'South Sudan',
label: 'South Sudan'
},
{
value: 'Spain',
label: 'Spain'
},
{
value: 'Sri Lanka',
label: 'Sri Lanka'
},
{
value: 'Sudan',
label: 'Sudan'
},
{
value: 'Suriname',
label: 'Suriname'
},
{
value: 'Svalbard and Jan Mayen',
label: 'Svalbard and Jan Mayen'
},
{
value: 'Sweden',
label: 'Sweden'
},
{
value: 'Switzerland',
label: 'Switzerland'
},
{
value: 'Syrian Arab Republic',
label: 'Syrian Arab Republic'
},
{
value: 'Taiwan',
label: 'Taiwan'
},
{
value: 'Tajikistan',
label: 'Tajikistan'
},
{
value: 'Tanzania, the United Republic of',
label: 'Tanzania, the United Republic of'
},
{
value: 'Thailand',
label: 'Thailand'
},
{
value: 'Timor-Leste',
label: 'Timor-Leste'
},
{
value: 'Togo',
label: 'Togo'
},
{
value: 'Tokelau',
label: 'Tokelau'
},
{
value: 'Tonga',
label: 'Tonga'
},
{
value: 'Trinidad and Tobago',
label: 'Trinidad and Tobago'
},
{
value: 'Tunisia',
label: 'Tunisia'
},
{
value: 'Turkey',
label: 'Turkey'
},
{
value: 'Turkmenistan',
label: 'Turkmenistan'
},
{
value: 'Turks and Caicos Islands',
label: 'Turks and Caicos Islands'
},
{
value: 'Tuvalu',
label: 'Tuvalu'
},
{
value: 'Uganda',
label: 'Uganda'
},
{
value: 'Ukraine',
label: 'Ukraine'
},
{
value: 'United Arab Emirates',
label: 'United Arab Emirates'
},
{
value: 'United Kingdom of Great Britain and Northern Ireland',
label: 'United Kingdom of Great Britain and Northern Ireland'
},
{
value: 'United States Minor Outlying Islands',
label: 'United States Minor Outlying Islands'
},
{
value: 'United States',
label: 'United States'
},
{
value: 'Uruguay',
label: 'Uruguay'
},
{
value: 'Uzbekistan',
label: 'Uzbekistan'
},
{
value: 'Vanuatu',
label: 'Vanuatu'
},
{
value: 'Venezuela (Bolivarian Republic of)',
label: 'Venezuela (Bolivarian Republic of)'
},
{
value: 'Viet Nam',
label: 'Viet Nam'
},
{
value: 'Virgin Islands (British)',
label: 'Virgin Islands (British)'
},
{
value: 'Virgin Islands (U.S.)',
label: 'Virgin Islands (U.S.)'
},
{
value: 'Wallis and Futuna',
label: 'Wallis and Futuna'
},
{
value: 'Western Sahara*',
label: 'Western Sahara*'
},
{
value: 'Yemen',
label: 'Yemen'
},
{
value: 'Zambia',
label: 'Zambia'
},
{
value: 'Zimbabwe',
label: 'Zimbabwe'
}
]

View File

@@ -38,7 +38,6 @@
size="mini"
v-model="lineRefer"
:disabled="!lineTab"
placeholder=" "
popper-class="common-select"
:popper-append-to-body="false"
@change="referenceSelectChange"
@@ -193,9 +192,6 @@ export default {
if (response.status === 200) {
this.isNoData = res.data.result.length === 0
this.showError = false
if (!active) {
this.tabs = _.cloneDeep(this.tabsTemplate)
}
if (this.isNoData) {
this.lineTab = ''
this.tabs = _.cloneDeep(this.tabsTemplate)
@@ -516,7 +512,6 @@ export default {
})
this.tabs = tabs
this.$nextTick(() => {
this.lineRefer = 'Average'
this.echartsInit(this.tabs, true)
})
} else {
@@ -534,7 +529,7 @@ export default {
const self = this
tabs.forEach(e => {
e.unitType = type
if (e.name !== 'network.total' && parseFloat(e.analysis.max) === 0) {
if (e.name !== 'network.total' && parseFloat(e.analysis.avg) === 0) {
e.show = false
num += 1
} else {
@@ -544,18 +539,13 @@ export default {
}
}
if (self.lineTab === e.class) {
if (parseFloat(e.analysis.max) <= 0) {
if (parseFloat(e.analysis.avg) <= 0) {
self.lineTab = ''
self.lineRefer = ''
// self.init()
self.init()
}
}
})
const emptyData = tabs.filter(d => parseFloat(d.analysis.max) === 0)
this.isNoData = emptyData.length === tabs.length
if (this.isNoData) {
return true
}
this.tabs = tabs
if (num === 5) {
tabs[0].invertTab = false

View File

@@ -379,25 +379,12 @@ export default {
timeFilter: {
handler (n) {
const queryParams = this.getQueryParams()
this.getChartData(queryParams)
this.changeUrlTabState()
this.getChartData(queryParams)
}
},
metric (n) {
this.changeMetric()
},
scoreBaseState (n) {
if (n && Object.keys(_.get(this.tableData, '[0].scoreGroup', {})).length >= 5) {
this.handleScoreData()
}
},
tableData: {
deep: true,
handler (n) {
if (Object.keys(_.get(n, '[0].scoreGroup', {})).length >= 5 && this.scoreBaseState) {
this.handleScoreData()
}
}
}
},
computed: {
@@ -412,9 +399,6 @@ export default {
className = 'tab-table tab-table__no-bottom'
}
return className
},
scoreBaseState () {
return this.$store.getters.scoreBaseReady
}
},
mixins: [chartMixin],
@@ -484,12 +468,12 @@ export default {
return excludeName.indexOf(title.name) > -1 ? false : 'custom'
},
searchColumnWidth (columnType) {
const checkedGroup = this.customTableTitles.filter(item => item.checked)
const checkedNum = checkedGroup.length
let checkedGroup = this.customTableTitles.filter(item => item.checked)
let checkedNum = checkedGroup.length
if (columnType === 'dillDown') {
if (checkedNum === 1) {
if(checkedNum === 1){
return 'auto'
} else if (checkedNum > 1) {
}else if(checkedNum > 1){
return '217px'
}
}
@@ -517,7 +501,7 @@ export default {
name: this.dropDownValue ? this.dropDownValue : ''
}
let url = curTableInCode.url.drilldownList
if (this.isDrilldown() || this.tableType === fromRoute.linkMonitor) {
if(this.isDrilldown() || this.tableType === fromRoute.linkMonitor){
url = curTableInCode.url.relationTabDrilldownList
const condition = this.getQueryCondition()
if (condition) {
@@ -617,8 +601,8 @@ export default {
this.curPageNum = 1
this.initDropdownList(prop)
},
scrollList (prop, tabProp) {
const obj = document.getElementById('tabSearchSelectDropdown' + tabProp)
scrollList (prop,tabProp) {
const obj = document.getElementById('tabSearchSelectDropdown'+tabProp)
if ((obj.scrollTop + obj.clientHeight === obj.scrollHeight) && obj.scrollHeight !== 0) {
this.initDropdownList(prop)
}
@@ -1054,12 +1038,12 @@ export default {
} else {
item.scoreGroup[self.columnNameGroup[tableColumn.prop]] = 0
}
/* if (Object.keys(item.scoreGroup).length >= 5) {
if (Object.keys(item.scoreGroup).length >= 5) {
item.score = computeScore(item.scoreGroup)
if (!_.isNumber(item.score)) {
item.score = '-'
}
} */
}
})
} else {
tableColumn.showError = true
@@ -2239,18 +2223,6 @@ export default {
this.orderBy = 'sessions'
this.metricUnit = 'sessions'
}
},
handleScoreData () {
this.tableData.forEach(t => {
const data = {
establishLatencyMs: t.scoreGroup ? t.scoreGroup.establishLatencyMs : null,
httpResponseLatency: t.scoreGroup ? t.scoreGroup.httpResponseLatency : null,
sslConLatency: t.scoreGroup ? t.scoreGroup.sslConLatency : null,
tcpLostlenPercent: t.scoreGroup ? t.scoreGroup.tcpLostlenPercent : null,
pktRetransPercent: t.scoreGroup ? t.scoreGroup.pktRetransPercent : null
}
t.score = computeScore(data, this.$store.getters.getScoreBase)
})
}
},
async mounted () {

View File

@@ -172,8 +172,7 @@ export default {
curTabState: curTabState,
urlChangeParams: {},
showError: false,
errorMsg: '',
scoreDataState: false // 评分数据是否加载完成
errorMsg: ''
}
},
components: {
@@ -186,21 +185,6 @@ export default {
handler () {
this.init()
}
},
scoreBaseState (n) {
if (n && this.scoreDataState) {
this.handleScoreData()
}
},
scoreDataState (n) {
if (n && this.scoreBaseState) {
this.handleScoreData()
}
}
},
computed: {
scoreBaseState () {
return this.$store.getters.scoreBaseReady
}
},
methods: {
@@ -213,7 +197,6 @@ export default {
const currentTrafficRequest = axios.get(api.npm.overview.appTrafficAnalysis, { params: { ...params, cycle: 0 } })
const lastCycleTrafficRequest = axios.get(api.npm.overview.appTrafficAnalysis, { params: { ...params, cycle: 1 } })
this.toggleLoading(true)
this.scoreDataState = false
Promise.all([currentTrafficRequest, lastCycleTrafficRequest]).then(res => {
if (res[0].status === 200 && res[1].status === 200) {
this.showError = false
@@ -256,9 +239,6 @@ export default {
t[keyPre[i] + 'Score'] = r.data.data.result.find(d => d.appSubcategory === t.appSubcategory)
})
} else {
tableData.forEach(t => {
t[keyPre[i] + 'Score'] = null
})
this.showError = true
msg = msg + ',' + r.data.data.message
if (msg.indexOf(',') === 0) {
@@ -270,7 +250,7 @@ export default {
this.errorMsg = msg
}
})
/* tableData.forEach(t => {
tableData.forEach(t => {
const data = {
establishLatencyMs: t.tcpScore ? t.tcpScore.establishLatencyMs : null,
httpResponseLatency: t.httpScore ? t.httpScore.httpResponseLatency : null,
@@ -279,11 +259,10 @@ export default {
pktRetransPercent: t.packetRetransScore ? t.packetRetransScore.pktRetransPercent : null
}
t.score = computeScore(data)
}) */
})
this.tableData = tableData
}).finally(() => {
this.toggleLoading(false)
this.scoreDataState = true
})
} else {
this.tableData = []
@@ -403,18 +382,6 @@ export default {
overwriteUrl(newUrl)
}
this.urlChangeParams = {}
},
handleScoreData () {
this.tableData.forEach(t => {
const data = {
establishLatencyMs: t.tcpScore ? t.tcpScore.establishLatencyMs : null,
httpResponseLatency: t.httpScore ? t.httpScore.httpResponseLatency : null,
sslConLatency: t.sslScore ? t.sslScore.sslConLatency : null,
tcpLostlenPercent: t.tcpLostScore ? t.tcpLostScore.tcpLostlenPercent : null,
pktRetransPercent: t.packetRetransScore ? t.packetRetransScore.pktRetransPercent : null
}
t.score = computeScore(data, this.$store.getters.getScoreBase)
})
}
},
mounted () {

View File

@@ -3,7 +3,7 @@
<div class="npm-header-body" v-for="(item, index) in chartData" :key=index>
<div class="npm-header-body-severity">
<div class="npm-header-body-severity-icon" :class="item.eventSeverity" :test-id="`icon${index}`"></div>
<div class="npm-header-body-severity-value" :test-id="`severity${index}`">{{ $t(item.eventSeverity) }}</div>
<div class="npm-header-body-severity-value" :test-id="`severity${index}`">{{item.eventSeverity}}</div>
</div>
<chart-error v-if="showError" tooltip :content="errorMsg" />
<div v-else class="npm-header-body-total" :test-id="`total${index}`">{{item.count}}</div>
@@ -45,7 +45,7 @@ export default {
endTime: this.timeFilter && this.timeFilter.endTime ? getSecond(this.timeFilter.endTime) : '',
type: this.type
}
/* this.toggleLoading(true)
/*this.toggleLoading(true)
axios.get(api.npm.events.list, { params: params }).then(response => {
const res = response.data
if (response.status === 200) {
@@ -69,7 +69,7 @@ export default {
this.errorMsg = this.errorMsgHandler(e)
}).finally(() => {
this.toggleLoading(false)
}) */
})*/
this.toggleLoading(false)
}
},

View File

@@ -43,38 +43,21 @@ export default {
trafficDirection: 'Server',
curTabState: curTabState,
showError: false,
errorMsg: '',
scoreDataState: false
errorMsg: ''
}
},
mixins: [chartMixin],
computed: {
scoreBaseState () {
return this.$store.getters.scoreBaseReady
}
},
watch: {
timeFilter: {
handler () {
this.loadAm4ChartMap(this.polygonSeries, this.worldImageSeries)
}
},
scoreBaseState (n) {
if (n && this.scoreDataState) {
this.handleScoreData()
}
},
scoreDataState (n) {
if (n && this.scoreBaseState) {
this.handleScoreData()
}
}
},
methods: {
async initMap () {
// 初始化插件
this.toggleLoading(true)
this.scoreDataState = false // 重置评分数据状态
const geoData = await getGeoData(storageKey.iso36112WorldLow)
const chart = am4Core.create('npmDrillDownMap', am4Maps.MapChart)
chart.geodata = geoData
@@ -98,7 +81,6 @@ export default {
// 清除数据
// polygonSeries.data.splice(0)
this.toggleLoading(true)
this.scoreDataState = false // 重置评分数据状态
// 清除legend
this.myChart.children.each((s, i) => {
if (s && s.className !== 'Container') {
@@ -158,11 +140,10 @@ export default {
tcpLostlenPercent: valueToRangeValue(data.tcpLostlenPercent, unitTypes.percent).join(' '),
pktRetransPercent: valueToRangeValue(data.pktRetransPercent, unitTypes.percent).join(' ')
}
t.performanceData = data
/* t.tooltip.data.score = computeScore(data)
t.tooltip.data.score = computeScore(data)
if (t.tooltip.data.score === '-') {
t.tooltip.data.score = ''
} */
}
t.tooltip.data.total = valueToRangeValue(t.totalBitsRate, unitTypes.bps).join(' ')
t.tooltip.data.inbound = valueToRangeValue(t.inboundBitsRate, unitTypes.bps).join(' ')
t.tooltip.data.outbound = valueToRangeValue(t.outboundBitsRate, unitTypes.bps).join(' ')
@@ -180,7 +161,6 @@ export default {
sslConLatency: this.$t('networkAppPerformance.sslResponseLatency')
}
})
this.scoreDataState = true
this.loadMarkerData(imageSeries, mapData)
})
} else {
@@ -200,10 +180,14 @@ export default {
}
},
loadMarkerData (imageSeries, data) {
imageSeries.data = data.map(r => ({
const _data = data.filter(d => d.tooltip.data.score || d.tooltip.data.score === 0)
imageSeries.data = _data.map(r => ({
...r,
score: r.tooltip.data.score,
name: r.superAdminArea || r.countryRegion,
id: r.serverId
id: r.serverId,
color: this.scoreColor(r.tooltip.data.score),
border: this.scoreColor(r.tooltip.data.score)
}))
},
scoreColor (score) {
@@ -297,9 +281,6 @@ export default {
imageTemplate.adapter.add('longitude', function (longitude, target) {
const polygon = polygonSeries.getPolygonById(target.dataItem.dataContext.id)
if (polygon) {
if (target.dataItem.dataContext.id === 'RU') {
return 90
}
return polygon.visualLongitude
}
return longitude
@@ -326,22 +307,6 @@ export default {
})
return imageSeries
},
handleScoreData () {
const imageSeries = this.location ? this.countryImageSeries : this.worldImageSeries
imageSeries.data = imageSeries.data.map(d => {
let score = computeScore(d.performanceData, this.$store.getters.getScoreBase)
if (score === '-') {
score = ''
}
d.tooltip.data.score = score
return {
...d,
score,
color: this.scoreColor(score),
border: this.scoreColor(score)
}
})
}
},
mounted () {

View File

@@ -324,15 +324,9 @@ export default {
case 5:
return api.npm.location.packetsRetrains
}
},
initI18n () {
dataForNpmLine.chartOptionLineData.forEach(item => {
item.legend = this.$t(item.legend)
})
}
},
mounted () {
this.initI18n()
if (this.chart) {
this.chartData = _.cloneDeep(this.chart)
}

View File

@@ -9,7 +9,6 @@
v-model="trafficDirection"
class="map-select map-select__direction"
popper-class="map-select-down"
placeholder=" "
:popper-append-to-body="false"
>
<el-option value="Server">Server</el-option>
@@ -26,7 +25,7 @@
:popper-append-to-body="false"
>
<template #prefix><i class="cn-icon cn-icon-location" style="color: #575757;"></i></template>
<el-option v-for="country in showLocationOptions" :key="country" :value="country">{{country}}</el-option>
<el-option v-for="(country, index) in locationOptions" :key="index" :value="country.value">{{country.label}}</el-option>
</el-select>
</div>
<div class="map-legend">
@@ -51,7 +50,8 @@ import { shallowRef } from 'vue'
import * as am4Core from '@amcharts/amcharts4/core'
import * as am4Maps from '@amcharts/amcharts4/maps'
import { computeScore, getGeoData } from '@/utils/tools'
import { countryNameIdMapping, storageKey, unitTypes, iso36112 } from '@/utils/constants'
import { countryNameIdMapping, storageKey, unitTypes } from '@/utils/constants'
import locationOptions from '@/views/charts2/charts/locationOptions'
import { valueToRangeValue } from '@/utils/unit-convert'
import { getSecond } from '@/utils/date-util'
import { api } from '@/utils/api'
@@ -65,7 +65,7 @@ export default {
components: { ChartError },
data () {
return {
showLocationOptions: [],
locationOptions,
myChart: null,
polygonSeries: null,
countrySeries: null,
@@ -75,21 +75,14 @@ export default {
trafficDirection: 'Server',
location: '',
showError: false,
errorMsg: '',
scoreDataState: false
errorMsg: ''
}
},
mixins: [chartMixin],
computed: {
scoreBaseState () {
return this.$store.getters.scoreBaseReady
}
},
methods: {
async initMap () {
// 初始化插件
this.toggleLoading(true)
this.scoreDataState = false // 重置评分数据状态
const geoData = await getGeoData(storageKey.iso36112WorldLow)
const chart = am4Core.create('npmMap', am4Maps.MapChart)
chart.geodata = geoData
@@ -108,13 +101,14 @@ export default {
this.worldImageSeries.mapImages.template.events.on('hit', async ev => {
this.$store.commit('setNpmLocationCountry', ev.target.dataItem.dataContext.name)
this.location = ev.target.dataItem.dataContext.name
const countryId = ev.target.dataItem.dataContext.id
ev.target.isHover = false
await this.drill(countryId)
})
},
loadAm4ChartMap (polygonSeries, imageSeries) {
try {
this.toggleLoading(true)
this.scoreDataState = false // 重置评分数据状态
// 清除legend
this.myChart.children.each((s, i) => {
if (s && s.className !== 'Container') {
@@ -178,11 +172,10 @@ export default {
tcpLostlenPercent: valueToRangeValue(data.tcpLostlenPercent, unitTypes.percent).join(' '),
pktRetransPercent: valueToRangeValue(data.pktRetransPercent, unitTypes.percent).join(' ')
}
t.performanceData = data
/* t.tooltip.data.score = computeScore(data)
t.tooltip.data.score = computeScore(data)
if (t.tooltip.data.score === '-') {
t.tooltip.data.score = ''
} */
}
t.tooltip.data.total = valueToRangeValue(t.totalBitsRate, unitTypes.bps).join(' ')
t.tooltip.data.inbound = valueToRangeValue(t.inboundBitsRate, unitTypes.bps).join(' ')
t.tooltip.data.outbound = valueToRangeValue(t.outboundBitsRate, unitTypes.bps).join(' ')
@@ -200,7 +193,6 @@ export default {
sslConLatency: this.$t('networkAppPerformance.sslResponseLatency')
}
})
this.scoreDataState = true
this.loadMarkerData(imageSeries, mapData)
}).catch(e => {
this.showError = true
@@ -227,18 +219,15 @@ export default {
}
},
loadMarkerData (imageSeries, data) {
imageSeries.data = data.map(r => ({
const _data = data.filter(d => d.tooltip.data.score || d.tooltip.data.score === 0)
imageSeries.data = _data.map(r => ({
...r,
score: r.tooltip.data.score,
name: r.superAdminArea || r.countryRegion,
id: r.serverId
id: r.serverId,
color: this.scoreColor(r.tooltip.data.score),
border: this.scoreColor(r.tooltip.data.score)
}))
if (!this.location) {
this.filterLocationOptions(data)
}
},
filterLocationOptions (data) {
const showLocationOptions = Object.keys(countryNameIdMapping).filter(c => data.some(d => d.countryRegion === c))
this.showLocationOptions = Array.from(new Set(showLocationOptions))
},
scoreColor (score) {
if (score >= 0 && score <= 2) {
@@ -331,9 +320,6 @@ export default {
imageTemplate.adapter.add('longitude', function (longitude, target) {
const polygon = polygonSeries.getPolygonById(target.dataItem.dataContext.id)
if (polygon) {
if (target.dataItem.dataContext.id === 'RU') {
return 90
}
return polygon.visualLongitude
}
return longitude
@@ -366,55 +352,34 @@ export default {
if (countryId) {
const targetMapObject = this.polygonSeries.getPolygonById(countryId)
targetMapObject.series.chart.zoomToMapObject(targetMapObject)
if (iso36112[countryId]) {
const geoData = await getGeoData(countryId)
if (geoData) {
if (!this.countrySeries) {
this.countrySeries = this.polygonSeriesFactory()
}
if (!this.countryImageSeries) {
this.countryImageSeries = this.imageSeriesFactory('score', this.countrySeries)
}
this.countrySeries.geodata = geoData
this.polygonSeries.hide()
this.worldImageSeries.hide()
this.countrySeries.show()
this.countryImageSeries.show()
await this.$nextTick(() => {
this.loadAm4ChartMap(this.countrySeries, this.countryImageSeries)
})
} else if (!num || num < 3) {
// 多次测试最多2次查询不到数据
if (!num) {
num = 0
}
num++
await this.drill(countryId, num)
} else {
this.$message.warning(this.$t('tip.noDetailMap'))
const geoData = await getGeoData(countryId)
if (geoData) {
if (!this.countrySeries) {
this.countrySeries = this.polygonSeriesFactory()
}
if (!this.countryImageSeries) {
this.countryImageSeries = this.imageSeriesFactory('score', this.countrySeries)
}
this.countrySeries.geodata = geoData
this.polygonSeries.hide()
this.worldImageSeries.hide()
this.countrySeries.show()
this.countryImageSeries.show()
await this.$nextTick(() => {
this.loadAm4ChartMap(this.countrySeries, this.countryImageSeries)
})
} else if (!num || num < 3) {
// 多次测试最多2次查询不到数据
if (!num) {
num = 0
}
num++
await this.drill(countryId, num)
} else {
this.$message.warning(this.$t('tip.noDetailMap'))
}
}
},
handleScoreData () {
const imageSeries = this.location ? this.countryImageSeries : this.worldImageSeries
imageSeries.data = imageSeries.data.map(d => {
let score = computeScore(d.performanceData, this.$store.getters.getScoreBase)
if (score === '-') {
score = ''
}
d.tooltip.data.score = score
return {
...d,
score,
color: this.scoreColor(score),
border: this.scoreColor(score)
}
})
// this.myChart.invalidateData()
}
},
watch: {
@@ -454,16 +419,6 @@ export default {
this.loadAm4ChartMap(this.polygonSeries, this.worldImageSeries)
}
}
},
scoreBaseState (n) {
if (n && this.scoreDataState) {
this.handleScoreData()
}
},
scoreDataState (n) {
if (n && this.scoreBaseState) {
this.handleScoreData()
}
}
},
mounted () {

View File

@@ -8,7 +8,7 @@
<el-select
size="mini"
v-model="metricFilter"
placeholder=" "
placeholder=""
popper-class="common-select"
:popper-append-to-body="false"
@change="metricChange"
@@ -458,21 +458,9 @@ export default {
}
this.$store.commit('setRangeEchartsData', rangeObj)
}
},
initI18n () {
dataForNpmTrafficLine.tabs.forEach(item => {
item.name = this.$t(item.name)
})
dataForNpmTrafficLine.npmQuantity.forEach(item => {
item.name = this.$t(item.name)
})
dataForNpmTrafficLine.metricOptions.forEach(item => {
item.label = this.$t(item.label)
})
}
},
mounted () {
this.initI18n()
if (this.chart) {
this.chartData = _.cloneDeep(this.chart)
}

View File

@@ -4,10 +4,9 @@ import {
chartColor3,
chartColor5,
chartColor6,
chartColorForBehaviorPattern,
unitTypes
} from '@/utils/constants'
import unitConvert from '@/utils/unit-convert'
import unitConvert, { valueToRangeValue } from '@/utils/unit-convert'
import { axisFormatter } from '@/views/charts/charts/tools'
import { xAxisTimeFormatter, xAxisTimeRich } from '@/utils/date-util'
@@ -157,111 +156,6 @@ export const pieChartOption3 = {
]
}
export const pieChartOption4 = {
color: chartColorForBehaviorPattern,
polar: {
radius: [30, 225],
center: ['400px', '100%']// 为了显示出来半圆底部左侧的边
},
radiusAxis: {
min: 0,
axisLine: {
show: true,
lineStyle: {
color: '#d3d3d3',
type: 'dashed'
}
},
axisTick: {
show: false
},
axisLabel: {
show: false
},
splitLine: {
show: true,
lineStyle: {
color: '#d3d3d3',
type: 'dashed'
}
}
},
angleAxis: {
type: 'category',
data: [], // 'a', 'b', 'c', 'd','aa', 'ab', 'ac', 'ad','a', 'b', 'c', 'd','aa', 'ab', 'ac', 'ad'
startAngle: 180,
// splitNumber: 30,
axisLine: {
show: false
},
axisLabel: {
show: true,
// alignWithLabel:true,//可以保证刻度线和标签对齐
interval: 0, // 强制显示所有标签
// hideOverlap:true//从 v5.2.0 开始支持
formatter: function (params, index) {
if (index === 7) {
return params + '\n'
} else if (index === 8) {
return '\n' + params
} else if (index === 15) {
return params + '\n'
} else {
return params
}
}
},
axisTick: {
show: false,
alignWithLabel: true,
interval: 0,
length: 5
},
splitLine: {
show: true,
lineStyle: {
color: ['#e2e5ec'],
type: 'dashed'
}
}
},
tooltip: {
show: true,
formatter: function (item) {
let str = '<div style="display:flex;flex-direction: row;align-items: center;">'
str += '<div style="width: 8px;\n' +
' height: 8px;\n' +
' margin: 3px 8px 0 0;\n' +
' border-radius: 1px;;background:'
str += item.color
str += ';"></div>'
str += item.name + ': ' + unitConvert(item.value, unitTypes.number).join('')
str += '</div>'
str += '</div>'
return str
}
},
series: [{
type: 'bar',
data: [], // 8,7,6,5,4,3,2,1,0,0,0,0,0,0,0,0
coordinateSystem: 'polar',
axisTick: {
show: false
},
itemStyle: {
color: function (params) {
return chartColorForBehaviorPattern[params.dataIndex]
}
},
label: {
show: false,
position: 'middle',
formatter: '{b}: {c}'
}
}],
animation: false
}
export const stackedLineChartOption = {
color: chartColor3,
tooltip: {
@@ -291,6 +185,7 @@ export const stackedLineChartOption = {
{
type: 'time',
splitNumber: 8,
minInterval: 60000,
axisLabel: {
formatter: xAxisTimeFormatter,
rich: xAxisTimeRich

View File

@@ -1,7 +1,8 @@
<template>
<div class="detection-filter-case">
<div class="new-detection-filter-title">{{$t('detections.filters')}}</div>
<div class="no-data" v-if="isNoData">{{ $t('npm.noData') }}</div>
<div class="new-detection-filter-title">{{$t('detections.filters')}}</div>
<template v-for="(filter, index) in filterData" :key="index">
<div class="detection-filter" v-show="filter.data.length > 0">
<div class="filter__header" @click="filter.collapse = !filter.collapse">

View File

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

View File

@@ -9,19 +9,19 @@
</div>
</div>
<div class="cn-detection__case">
<div class="cn-detection__icon" :style="`background-color: ${eventSeverityColor[detection.severity]}`"></div>
<div class="cn-detection__icon" :style="`background-color: ${eventSeverityColor[detection.eventSecurity]}`"></div>
<div class="cn-detection__row">
<div class="cn-detection__header" v-if="pageType === detectionPageType.securityEvent">
<span
class="detection-event-severity-color-block"
:style="`background-color: ${eventSeverityColor[detection.eventSeverity]}`">
</span>
<span class="detection-event-severity-block">{{ detection.eventName || '-' }}</span>
<i class="cn-icon cn-icon-attacker detection-list-icon" ></i>{{detection.offenderIp || '-'}}
<span class="detection-event-severity-block">{{ detection.securityType || '-' }}</span>
<i class="cn-icon cn-icon-attacker" ></i>{{detection.offenderIp || '-'}}
<div v-if="detection.domain" class="domain">{{detection.domain}}</div>
<span class="line">-------</span>
<span class="circle"></span>
<i class="cn-icon cn-icon-attacked detection-list-icon" ></i>{{detection.victimIp || '-'}}
<i class="cn-icon cn-icon-attacked" ></i>{{detection.victimIp || '-'}}
</div>
<div class="cn-detection__header" v-else-if="pageType === detectionPageType.performanceEvent">
<div class="cn-entity__icon"><i :class="iconClass"></i></div>
@@ -30,10 +30,10 @@
<div class="cn-detection__body">
<div class="body__basic-info">
<div class="basic-info">
<div class="basic-info__item" v-if="detection.severity">
<div class="basic-info__item" v-if="detection.eventSecurity">
<i class="cn-icon cn-icon-severity-level"></i>
<span>{{$t('detection.list.security')}}&nbsp;:&nbsp;&nbsp;</span>
<span>{{detection.severity || '-'}}</span>
<span>{{detection.eventSecurity || '-'}}</span>
</div>
<div class="basic-info__item" v-if="detection.eventSeverity">
<i class="cn-icon cn-icon-severity-level"></i>
@@ -45,15 +45,15 @@
<span>{{$t('detections.eventType')}}&nbsp;:&nbsp;&nbsp;</span>
<span>{{detection.eventType || '-'}}</span>
</div>
<div class="basic-info__item" v-if="detection.malware">
<div class="basic-info__item" v-if="detection.malwareName">
<i class="cn-icon cn-icon-trojan"></i>
<span>{{$t('detection.list.malwareName')}}&nbsp;:&nbsp;&nbsp;</span>
<span>{{ $_.get(detection, 'malware.malwareName', '-') || '-' }}</span>
<span>{{detection.malwareName || '-'}}</span>
</div>
<div class="basic-info__item" v-if="detection.darkweb">
<i class="cn-icon cn-icon-trojan"></i>
<span>{{$t('detection.nodeType')}}&nbsp;:&nbsp;&nbsp;</span>
<span>{{ $_.get(detection, 'darkweb.nodeType', '-') || '-' }}</span>
<div class="basic-info__item" v-if="detection.cryptominingPool">
<i class="cn-icon cn-icon-mining-pool"></i>
<span>{{$t('detection.list.cryptominingPool')}}&nbsp;:&nbsp;&nbsp;</span>
<span>{{detection.cryptominingPool || '-'}}</span>
</div>
<div class="basic-info__item">
<i class="cn-icon cn-icon-time2"></i>
@@ -63,11 +63,9 @@
<div class="basic-info__item">
<i class="cn-icon cn-icon-duration"></i>
<span>{{$t('overall.duration')}}&nbsp;:&nbsp;&nbsp;</span>
<span>
{{ detection.matchTimes || '-'}} {{ $t('detection.list.times') }} /
{{unitConvert(parseInt(detection.durationS), 'time', 's', null, 0).join(' ') || '-'}}
</span>
<div v-if="parseInt(detection.status) === 0" class="margin-l-10 detection-row-active">{{$t('detections.active')}}</div>
<span style="display: inline-block;height: 6px;width: 6px;border-radius: 50%;margin-right: 8px;"
:style="pointColor(detection)"></span>
<span>{{unitConvert(detection.durationMs, 'time', null, null, 0).join(' ') || '-'}}</span>
</div>
</div>
</div>
@@ -110,7 +108,7 @@
<script>
import { eventSeverityColor, detectionPageType, entityType } from '@/utils/constants'
import { getMillisecond, dateFormatByAppearance } from '@/utils/date-util'
import { getMillisecond } from '@/utils/date-util'
import unitConvert from '@/utils/unit-convert'
import DetectionSecurityEventOverview from '@/views/detections/overview/DetectionSecurityEventOverview'
import DetectionPerformanceEventIpOverview from '@/views/detections/overview/DetectionPerformanceEventIpOverview'
@@ -176,19 +174,9 @@ export default {
}
}
},
watch: {
isCollapse (newVal) {
const newQuery = this.$route.query
if (newVal && newQuery.eventId) {
delete newQuery.eventId
this.reloadUrl(newQuery, 'cleanOldParams')
}
}
},
methods: {
unitConvert,
getMillisecond,
dateFormatByAppearance,
/* 切换折叠状态 */
switchCollapse () {
this.isCollapse = !this.isCollapse
@@ -247,17 +235,3 @@ export default {
}
}
</script>
<style>
.detection-row-active {
height: 20px;
line-height: 20px;
padding: 0 7px;
background: #E9EFE1;
border-radius: 2px;
font-family: NotoSansHans-Medium;
font-size: 12px;
color: #7E9F54;
font-weight: 500;
}
</style>

View File

@@ -5,26 +5,23 @@
<advanced-search
ref="search"
:column-list="columnList[pageType]"
:operator-list="operatorList"
:connection-list="connectionList"
:default-mode="defaultMode"
:full-text="false"
class="advanced-search--show-list"
:full-text="true"
:show-list="showList"
@search="search"
></advanced-search>
</div>
<!-- <div class="search-symbol-inline">-->
<!-- <i class="cn-icon cn-icon-help"></i>-->
<!-- </div>-->
<div class="search-symbol-inline">
<i class="cn-icon cn-icon-help"></i>
</div>
</div>
</div>
</template>
<script>
import AdvancedSearch from '@/components/advancedSearch/Index'
import { schemaDetectionSecurity } from '@/utils/static-data'
import { useRoute } from 'vue-router'
import { ref } from 'vue'
import { humpToLine } from '@/utils/tools'
export default {
name: 'DetectionSearch',
props: {
@@ -36,7 +33,74 @@ export default {
data () {
return {
columnList: {
securityEvent: schemaDetectionSecurity,
securityEvent: [
{
name: 'event_severity',
type: 'string',
// label: 'Event severity',
label: 'event_severity',
doc: {
constraints: {
operator_functions: '=,in'
}
}
},
{
name: 'security_type',
type: 'string',
// label: 'Security type',
label: 'security_type',
doc: {
constraints: {
operator_functions: '=,in'
}
}
},
{
name: 'victim_ip',
type: 'string',
// label: 'Victim IP'
label: 'victim_ip',
doc: {
constraints: {
operator_functions: '=,in'
}
}
},
{
name: 'victim_location_country',
type: 'string',
// label: 'Victim location'
label: 'victim_location_country',
doc: {
constraints: {
operator_functions: '=,in'
}
}
},
{
name: 'offender_ip',
type: 'string',
// label: 'Offender IP'
label: 'offender_ip',
doc: {
constraints: {
operator_functions: '=,in'
}
}
},
{
name: 'offender_location_country',
type: 'string',
// label: 'Offender location'
label: 'offender_location_country',
doc: {
constraints: {
operator_functions: '=,in'
}
}
}
],
performanceEvent: [
{
name: 'event_severity',
@@ -49,6 +113,17 @@ export default {
}
}
},
{
name: 'event_type',
type: 'string',
// label: 'Event type'
label: 'event_type',
doc: {
constraints: {
operator_functions: '=,in'
}
}
},
{
name: 'app_name',
type: 'string',
@@ -94,19 +169,10 @@ export default {
value: 'OR',
label: 'OR'
}
],
showList: true
]
}
},
emits: ['search'],
setup () {
// 根据地址栏添加mode即text和tag模式默认text
const { query } = useRoute()
const defaultMode = ref(query.mode || 'text')
return {
defaultMode
}
},
methods: {
/* search (metaList, formatSql) {
let sql = formatSql
@@ -130,7 +196,7 @@ export default {
if (params.oldValue.length === 0 && params.newValue.length === 1) {
// 1.参数值数量从0到1直接addParams
const p = {
column: params.column,
column: humpToLine(params.column),
operator: '=',
value: params.newValue
}
@@ -138,7 +204,7 @@ export default {
} else if (params.oldValue.length === 1 && params.newValue.length === 0) {
// 2.参数值数量从1到0直接removeParams
const p = {
column: params.column,
column: humpToLine(params.column),
operator: '=',
value: params.oldValue
}
@@ -146,12 +212,12 @@ export default {
} else if (params.oldValue.length === 2 && params.newValue.length === 1) {
// 3.参数值数量从多到1operator由'in'改为'='
const oldParam = {
column: params.column,
column: humpToLine(params.column),
operator: 'IN',
value: params.oldValue
}
const newParam = {
column: params.column,
column: humpToLine(params.column),
operator: '=',
value: params.newValue
}
@@ -159,12 +225,12 @@ export default {
} else if (params.oldValue.length === 1 && params.newValue.length === 2) {
// 4.参数值数量从1到多, operator由'='改为'in'
const oldParam = {
column: params.column,
column: humpToLine(params.column),
operator: '=',
value: params.oldValue
}
const newParam = {
column: params.column,
column: humpToLine(params.column),
operator: 'IN',
value: params.newValue
}
@@ -172,12 +238,12 @@ export default {
} else {
// 5.参数值数量从多到多加1或者减1
const oldParam = {
column: params.column,
column: humpToLine(params.column),
operator: 'IN',
value: params.oldValue
}
const newParam = {
column: params.column,
column: humpToLine(params.column),
operator: 'IN',
value: params.newValue
}

View File

@@ -1,5 +1,5 @@
<template>
<div class="entity-explorer entity-explorer--show-list detections">
<div class="entity-explorer entity-explorer--show-list">
<!-- 顶部工具栏在列表页显示 -->
<div class="explorer-top-tools explorer-detection-top-tools">
<div class="explorer-top-tools-title">{{$t('overall.detections')}}</div>
@@ -33,7 +33,7 @@
@search="search"
></detection-search>
<!-- 内容区 -->
<div class="detections__container">
<div class="explorer-container" style="height: calc(100% - 20px);flex-direction: column">
<loading :loading="loading"></loading>
<template v-if="isEventSeverityNoData">
<div class="no-data detection__event-severity-bar" >{{ $t('npm.noData') }}</div>
@@ -54,36 +54,39 @@
<div class="detection__list-statistics detection-border">
<div class="statistics__severity">
<div class="chart-header">
<div class="chart-header__title">{{$t('detections.severity')}}</div>
<div class="chart-header__title">{{$t('detection.severity')}}</div>
</div>
<template v-if="isStatisticsSeverityNoData">
<div class="no-data chart-content" >{{ $t('npm.noData') }}</div>
</template>
<template v-else>
<div class="chart-content" :id="`eventSeverityPie${pageType}`"></div>
<div class="chart-content" :id="`eventSeverityPie${pageType}`">
</div>
</template>
</div>
<div class="statistics__category">
<div class="chart-header">
<div class="chart-header__title">{{$t('detections.eventType')}}</div>
<div class="chart-header__title">{{$t('detection.categoryProportion')}}</div>
</div>
<template v-if="isStatisticsCategoryNoData">
<div class="no-data chart-content" >{{ $t('npm.noData') }}</div>
</template>
<template v-else>
<div class="chart-content" :id="`detectionCategoryPer${pageType}`"></div>
<div class="chart-content" :id="`detectionCategoryPer${pageType}`">
</div>
</template>
</div>
<div class="statistics__active-attack">
<div class="chart-header">
<div class="chart-header__title">{{pageType === detectionPageType.securityEvent ? $t('detection.activeOffender') : $t('detections.activeEntity')}}</div>
<div class="chart-header__title">{{pageType === detectionPageType.securityEvent ? $t('detection.activeAttacker') : $t('detections.activeEntity')}}</div>
</div>
<template v-if="isStatisticsActiveAttackNoData">
<div class="no-data chart-content" >{{ $t('npm.noData') }}</div>
</template>
<template v-else>
<div class="chart-content" style="padding-left: 5px;" :id="`detectionActiveAttacker${pageType}`"></div>
<div class="chart-content" style="padding-left: 5px;" :id="`detectionActiveAttacker${pageType}`">
</div>
</template>
</div>
</div>
@@ -121,7 +124,7 @@ import DetectionFilter from '@/views/detections/DetectionFilter'
import DetectionList from '@/views/detections/DetectionList'
import Pagination from '@/components/common/Pagination'
import { defaultPageSize, detectionPageType } from '@/utils/constants'
import { getNowTime, getSecond, getMillisecond } from '@/utils/date-util'
import { getNowTime, getSecond, toTime } from '@/utils/date-util'
import { ref, shallowRef } from 'vue'
import * as echarts from 'echarts'
import {
@@ -131,13 +134,12 @@ import {
multipleBarOption,
pieForSeverity
} from '@/views/detections/options/detectionOptions'
import { api } from '@/utils/api'
import { api, getData } from '@/utils/api'
import axios from 'axios'
import { urlParamsHandler, overwriteUrl, extensionEchartY, reverseSortBy } from '@/utils/tools'
import { extensionEchartY, reverseSortBy } from '@/utils/tools'
import { useRoute } from 'vue-router'
import Loading from '@/components/common/Loading'
import ChartTabs from '@/components/common/ChartTabs'
import { useStore } from 'vuex'
export default {
name: 'Index',
@@ -158,18 +160,18 @@ export default {
i18n: 'entities.securityEvents',
path: '/detection/securityEvent',
icon: 'cn-icon cn-icon-a-SecurityEvent'
}
},
// {
// i18n: 'entities.regulatoryRiskEvents',
// path: '/detection/securityEvent',
// icon: 'cn-icon cn-icon-a-RegulatoryRiskEvent',
// disable: true
// },
// {
// i18n: 'overall.performanceEvents',
// path: '/detection/performanceEvent',
// icon: 'cn-icon cn-icon-a-PerformanceEvent'
// }
{
i18n: 'overall.performanceEvents',
path: '/detection/performanceEvent',
icon: 'cn-icon cn-icon-a-PerformanceEvent'
}
],
chartInit: [],
pageObj: {
@@ -183,73 +185,61 @@ export default {
filterData: {
securityEvent: [
{
title: this.$t('overall.status'),
column: 'status',
topColumn: 'status',
title: this.$t('detections.eventSeverity'),
column: 'eventSeverity',
collapse: false,
value: [], // value之间是or的关系
data: [] // 从接口动态获取,本项在获得数据后需要特殊处理左边框颜色
},
{
title: this.$t('detections.severity'),
column: 'severity',
topColumn: 'severity',
collapse: false,
value: [], // value之间是or的关系
data: [] // 从接口动态获取,本项在获得数据后需要特殊处理左边框颜色
},
{
title: this.$t('detections.eventType'),
column: 'eventType',
topColumn: 'event_type',
title: this.$t('detections.securityType'),
column: 'securityType',
collapse: false,
value: [],
data: [] // 从接口动态获取
},
{
title: this.$t('detections.victimIp'),
column: 'victimIP',
topColumn: 'victim_ip',
column: 'victimIp',
collapse: false,
value: [],
showMore: true,
showIndex: 9,
data: [] // 从接口动态获取
},
// {
// title: this.$t('detections.victimLocation'),
// column: 'victimLocationCountry',
// collapse: false,
// value: [],
// showMore: false,
// showIndex: 9,
// data: [
// {
// label: 'China',
// value: 'china',
// count: 50
// }
// ] // 从接口动态获取
// },
{
title: this.$t('detections.victimLocation'),
column: 'victimLocationCountry',
collapse: false,
value: [],
showMore: false,
showIndex: 9,
data: [
{
label: 'China',
value: 'china',
count: 50
}
] // 从接口动态获取
},
{
title: this.$t('detections.offenderIp'),
column: 'offenderIP',
topColumn: 'offender_ip',
column: 'offenderIp',
collapse: false,
value: [],
showMore: false,
showIndex: 9,
data: [] // 从接口动态获取
},
{
title: this.$t('detections.offenderLocation'),
column: 'offenderLocationCountry',
collapse: false,
value: [],
showMore: false,
showIndex: 9,
data: [] // 从接口动态获取
}
// {
// title: this.$t('detections.offenderLocation'),
// column: 'offenderLocationCountry',
// collapse: false,
// value: [],
// showMore: false,
// showIndex: 9,
// data: [] // 从接口动态获取
// }
],
performanceEvent: [
{
@@ -285,47 +275,24 @@ export default {
isStatisticsCategoryNoData: false,
isStatisticsActiveAttackNoData: false,
loading: false,
oldActiveEntitySearchValue: '',
initFlag: true // 初始化标识初始化时保证mounted执行
oldActiveEntitySearchValue: ''
}
},
methods: {
initStatusData (params) {
axios.get(api.detection[this.pageType].statusStatistics, { params }).then(res => {
if (res.status === 200) {
const data = res.data.data.result
this.filterData[this.pageType][0].data = data.map(r => {
let label = ''
if (r.status === '0') {
label = this.$t('detections.active')
} else if (r.status === '1') {
label = this.$t('detections.ended')
}
return { label, value: r.status, count: r.count }
})
this.isCheckFilterByQ(params, 0)
}
}).catch(e => {
console.error(e)
this.filterData[this.pageType][0].data = []
this.$message.error(this.errorMsgHandler(e))
})
},
/** 初始化顶部大柱状图 */
// 初始化顶部大柱状图
initEventSeverityTrendData (params) {
this.loading = true
axios.get(api.detection[this.pageType].timeDistribution, { params }).then(res => {
const data = res.data.data.result
/* getData(api.detection[this.pageType].eventSeverityTrend, params).then(data => {
this.eventSeverityData = data
if (!this.$_.isEmpty(data)) {
const dataMap = new Map()
data.forEach(item => {
if (item.severity) {
if (!dataMap.has(item.severity)) {
const count = [[getMillisecond(parseFloat(item.statTime)), item.count]]
dataMap.set(item.severity, count)
if (item.eventSeverity) {
if (!dataMap.has(item.eventSeverity)) {
const count = [[toTime(item.statTime), item.count]]
dataMap.set(item.eventSeverity, count)
} else {
dataMap.get(item.severity).push([getMillisecond(parseFloat(item.statTime)), item.count])
dataMap.get(item.eventSeverity).push([toTime(item.statTime), item.count])
}
}
})
@@ -364,6 +331,12 @@ export default {
serie.data = seriesData
})
// eventSeverityTrendOption.xAxis.data = dataMap.get('info').map(v => rTime(v[0]))
eventSeverityTrendOption.xAxis = [{
type: 'time',
splitNumber: 8
}]
let detectionChart = echarts.getInstanceByDom(chartDom)
if (detectionChart) {
echarts.dispose(detectionChart)
@@ -375,26 +348,32 @@ export default {
} else {
// this.isEventSeverityNoData = true
}
}).catch(e => {
console.error(e)
this.$message.error(this.errorMsgHandler(e))
}).catch(error => {
console.log(error)
}).finally(() => {
this.$nextTick(() => {
this.loading = false
})
})
}) */
const timer = setTimeout(() => {
this.loading = false
this.isEventSeverityNoData = true
this.isStatisticsCategoryNoData = true
this.isStatisticsSeverityNoData = true
this.isStatisticsActiveAttackNoData = true
clearTimeout(timer)
}, 150)
},
/** 初始化左侧事件严重等级和第一个小饼图 */
// 初始化左侧事件严重等级和小饼图
initEventSeverityData (params) {
axios.get(api.detection[this.pageType].severityStatistics, { params }).then(res => {
const data = res.data.data.result
getData(api.detection[this.pageType].eventSeverity, params).then(data => {
this.statisticsSeverityData = data
if (!this.$_.isEmpty(data)) {
this.filterData[this.pageType][1].data = data.map(r => ({ label: r.severity, value: r.severity, count: r.count }))
this.isCheckFilterByQ(params, 1)
this.filterData[this.pageType][0].data = data.map(r => ({ label: r.eventSeverity, value: r.eventSeverity, count: r.count }))
const eventSeverityOption = this.$_.cloneDeep(pieForSeverity)
eventSeverityOption.series[0].data = data.map(d => {
return { value: d.count, name: d.severity, itemStyle: { color: getSeverityColor(d.severity) } }
return { value: d.count, name: d.eventSeverity, itemStyle: { color: getSeverityColor(d.eventSeverity) } }
})
const chartDom = document.getElementById(`eventSeverityPie${this.pageType}`)
let detectionChart = echarts.getInstanceByDom(chartDom)
@@ -410,22 +389,19 @@ export default {
if (this.pageType === 'performanceEvent') {
vm.filterData.performanceEvent[0].value = vm.triggerFilterDataValue(vm.filterData.performanceEvent[0].value, e.data.name)
} else if (this.pageType === 'securityEvent') {
vm.filterData.securityEvent[1].value = vm.triggerFilterDataValue(vm.filterData.securityEvent[1].value, e.data.name)
vm.filterData.securityEvent[0].value = vm.triggerFilterDataValue(vm.filterData.securityEvent[0].value, e.data.name)
}
})
}
}).catch(e => {
console.error(e)
this.filterData[this.pageType][1].data = []
this.$message.error(this.errorMsgHandler(e))
}).catch(error => {
console.log(error)
})
},
initEventTypeData (params) {
axios.get(api.detection[this.pageType].eventType, { params }).then(res => {
const data = res.data.data.result
getData(api.detection[this.pageType].eventType, params).then(data => {
this.statisticsCategoryData = data
if (!this.$_.isEmpty(data)) {
this.filterData[this.pageType][2].data = data.map(r => ({
this.filterData[this.pageType][1].data = data.map(r => ({
label: r.eventType,
value: r.eventType,
count: r.count
@@ -449,24 +425,19 @@ export default {
vm.filterData.performanceEvent[1].value = vm.triggerFilterDataValue(vm.filterData.performanceEvent[1].value, e.data.name)
})
}
}).catch(e => {
console.error(e)
this.filterData[this.pageType][2].data = []
this.$message.error(this.errorMsgHandler(e))
}).catch(error => {
console.log(error)
})
},
/** 第二个饼图和左侧filter的eventType */
initSecurityTypeData (params) {
axios.get(api.detection[this.pageType].eventTypeStatistics, { params }).then(res => {
const data = res.data.data.result
getData(api.detection[this.pageType].securityType, params).then(data => {
this.statisticsCategoryData = data
if (!this.$_.isEmpty(data)) {
this.filterData[this.pageType][2].data = data.map(r => ({
label: r.eventType,
value: r.eventType,
this.filterData[this.pageType][1].data = data.map(r => ({
label: r.securityType,
value: r.securityType,
count: r.count
}))
this.isCheckFilterByQ(params, 2)
const chartDom = document.getElementById(`detectionCategoryPer${this.pageType}`)
let detectionChart = echarts.getInstanceByDom(chartDom)
if (detectionChart) {
@@ -476,26 +447,22 @@ export default {
this.chartInit.push(shallowRef(detectionChart))
const securityTypeOption = this.$_.cloneDeep(pieForSeverity)
securityTypeOption.series[0].data = data.map(d => {
return { value: d.count, name: d.eventType, itemStyle: { color: getAttackColor(d.eventType) } }
return { value: d.count, name: d.securityType, itemStyle: { color: getAttackColor(d.securityType) } }
})
detectionChart.setOption(securityTypeOption)
const vm = this
detectionChart.off('click')
detectionChart.on('click', e => {
vm.filterData.securityEvent[2].value = vm.triggerFilterDataValue(vm.filterData.securityEvent[2].value, e.data.name)
vm.filterData.securityEvent[1].value = vm.triggerFilterDataValue(vm.filterData.securityEvent[1].value, e.data.name)
})
}
}).catch(e => {
console.error(e)
this.filterData[this.pageType][2].data = []
this.$message.error(this.errorMsgHandler(e))
}).catch(error => {
console.log(error)
})
},
/** 横向柱状图和左侧filter的offenderIp */
initOffenderIpData (params) {
axios.get(api.detection[this.pageType].offenderIpStatistics, { params }).then(res => {
let data = res.data.data.result
getData(api.detection[this.pageType].offenderIp, params).then(data => {
this.statisticsActiveAttackData = data
if (!this.$_.isEmpty(data)) {
this.filterData[this.pageType][4].data = data.map(r => ({
@@ -503,7 +470,6 @@ export default {
value: r.offenderIp,
count: r.count
}))
this.isCheckFilterByQ(params, 4)
const { showMore, showIndex } = this.computeFilterPage(this.filterData[this.pageType][4].data)
this.filterData[this.pageType][4].showMore = showMore
this.filterData[this.pageType][4].showIndex = showIndex
@@ -529,34 +495,43 @@ export default {
vm.filterData.securityEvent[4].value = vm.triggerFilterDataValue(vm.filterData.securityEvent[4].value, e.data[1])
})
}
}).catch(e => {
console.error(e)
this.filterData[this.pageType][4].data = []
this.filterData[this.pageType][4].showMore = false
this.filterData[this.pageType][4].showIndex = 9
this.$message.error(this.errorMsgHandler(e))
}).catch(error => {
console.log(error)
})
},
initVictimIpData (params) {
axios.get(api.detection[this.pageType].victimIpStatistics, { params }).then(res => {
const data = res.data.data.result
this.filterData[this.pageType][3].data = data.map(r => ({ label: r.victimIp, value: r.victimIp, count: r.count }))
this.isCheckFilterByQ(params, 3)
getData(api.detection[this.pageType].victimIp, params).then(data => {
this.filterData[this.pageType][2].data = data.map(r => ({ label: r.victimIp, value: r.victimIp, count: r.count }))
const { showMore, showIndex } = this.computeFilterPage(this.filterData[this.pageType][2].data)
this.filterData[this.pageType][2].showMore = showMore
this.filterData[this.pageType][2].showIndex = showIndex
}).catch(error => {
console.log(error)
})
},
initVictimLocationData (params) {
getData(api.detection[this.pageType].victimLocation, params).then(data => {
this.filterData[this.pageType][3].data = data.map(r => ({ label: r.victimLocationCountry, value: r.victimLocationCountry, count: r.count }))
const { showMore, showIndex } = this.computeFilterPage(this.filterData[this.pageType][3].data)
this.filterData[this.pageType][3].showMore = showMore
this.filterData[this.pageType][3].showIndex = showIndex
}).catch(e => {
console.error(e)
this.filterData[this.pageType][3].data = []
this.filterData[this.pageType][3].showMore = false
this.filterData[this.pageType][3].showIndex = 9
this.$message.error(this.errorMsgHandler(e))
}).catch(error => {
console.log(error)
})
},
initOffenderLocationData (params) {
getData(api.detection[this.pageType].offenderLocation, params).then(data => {
this.filterData[this.pageType][5].data = data.map(r => ({ label: r.offenderLocationCountry, value: r.offenderLocationCountry, count: r.count }))
const { showMore, showIndex } = this.computeFilterPage(this.filterData[this.pageType][5].data)
this.filterData[this.pageType][5].showMore = showMore
this.filterData[this.pageType][5].showIndex = showIndex
}).catch(error => {
console.log(error)
})
},
initActiveEntity (params) {
axios.get(api.detection[this.pageType].activeEntity, { params }).then(res => {
let data = res.data.data.result
getData(api.detection[this.pageType].activeEntity, params).then(data => {
this.statisticsActiveAttackData = data
if (!this.$_.isEmpty(data)) {
const chartDom = document.getElementById(`detectionActiveAttacker${this.pageType}`)
@@ -616,7 +591,7 @@ export default {
})
}
}).catch(error => {
console.error(error)
console.log(error)
})
},
triggerFilterDataValue (array, value) {
@@ -635,49 +610,34 @@ export default {
showIndex: 9
}
},
queryList (q) {
queryList () {
const params = {
startTime: getSecond(this.timeFilter.startTime),
endTime: getSecond(this.timeFilter.endTime),
resource: q,
q: this.q,
pageSize: this.pageObj.pageSize,
pageNo: this.pageObj.pageNo
}
axios.get(api.detection[this.pageType].securityList, { params }).then(response => {
/* axios.get(api.detection[this.pageType].listBasic, { params }).then(response => {
if (response.status === 200) {
const data = response.data.data.result
if (data.length > 0) {
data.forEach(item => {
item.eventInfoObj = JSON.parse(item.eventInfo)
item.startTime = parseFloat(item.startTime)
})
this.listData = data
} else {
this.listData = []
}
this.listData = response.data.data.result
} else {
this.listData = []
console.error(response.data.message)
this.$message.error(response.data.message)
}
})
axios.get(api.detection[this.pageType].securityCount, { params }).then(res => {
this.pageObj.total = parseInt(this.$_.get(res, 'data.data.result', 0))
getData(api.detection[this.pageType].listCount, params).then(data => {
this.pageObj.total = data
}).catch(error => {
console.error(error)
})
console.log(error)
}) */
},
timeRefreshChange () {
// 不是自选时间
if (this.$refs.dateTimeRange) {
if (!this.$refs.dateTimeRange.isCustom) {
const value = this.timeFilter.dateRangeValue
this.$refs.dateTimeRange.quickChange(value)
} else {
this.timeFilter = JSON.parse(JSON.stringify(this.timeFilter))
}
} else {
this.timeFilter = JSON.parse(JSON.stringify(this.timeFilter))
this.initNoData()
if (!this.$refs.dateTimeRange.isCustom) {
const value = this.timeFilter.dateRangeValue
this.$refs.dateTimeRange.quickChange(value)
}
},
initNoData () {
@@ -686,16 +646,9 @@ export default {
this.isStatisticsCategoryNoData = false
this.isStatisticsActiveAttackNoData = false
},
reload (startTime, endTime, dateRangeValue) {
reload (s, e, v) {
this.initNoData()
this.dateTimeRangeChange(startTime, endTime, dateRangeValue)
const { query } = this.$route
const newUrl = urlParamsHandler(window.location.href, query, {
startTime: this.timeFilter.startTime,
endTime: this.timeFilter.endTime,
range: dateRangeValue.value
})
overwriteUrl(newUrl)
this.dateTimeRangeChange(s, e, v)
},
// methods
dateTimeRangeChange (s, e, v) {
@@ -721,26 +674,8 @@ export default {
} else {
this.pageObj.resetPageNo = true
}
// 参数q避免切换页码时地址栏参数q为空
let urlQ = ''
if (param && param.str) {
// urlQ = encodeURI(param.str)
urlQ = param.str
} else if (this.q) {
// urlQ = encodeURI(this.q)
urlQ = this.q
}
const mode = this.$route.query.mode || 'text'
const newUrl = urlParamsHandler(window.location.href, this.$route.query, {
startTime: this.timeFilter.startTime,
endTime: this.timeFilter.endTime,
range: this.timeFilter.dateRangeValue,
q: urlQ,
mode: mode
})
overwriteUrl(newUrl)
this.queryFilter(urlQ)
this.queryList(urlQ)
this.queryFilter()
this.queryList()
},
resetFilterData () {
this.filterData.securityEvent.forEach(d => {
@@ -750,23 +685,25 @@ export default {
d.data = []
})
},
queryFilter (q) {
queryFilter () {
this.resetFilterData()
const params = {
startTime: getSecond(this.timeFilter.startTime),
endTime: getSecond(this.timeFilter.endTime),
resource: q
q: this.q
}
this.initStatusData(params)
this.listData = []
this.initEventSeverityTrendData(params)
this.initEventSeverityData(params)
// this.initEventSeverityData(params)
if (this.pageType === detectionPageType.securityEvent) {
this.initOffenderIpData(params)
this.initVictimIpData(params)
this.initSecurityTypeData(params)
// this.initOffenderIpData(params)
// this.initOffenderLocationData(params)
// this.initVictimIpData(params)
// this.initVictimLocationData(params)
// this.initSecurityTypeData(params)
} else if (this.pageType === detectionPageType.performanceEvent) {
this.initActiveEntity(params)
this.initEventTypeData(params)
// this.initActiveEntity(params)
// this.initEventTypeData(params)
}
},
pageSize (val) {
@@ -776,11 +713,7 @@ export default {
pageNo (val) {
this.pageObj.pageNo = val || 1
this.pageObj.resetPageNo = false
// 初始化时mounted和pageNo都会调用列表接口且pageNo先执行
// 初始化保证mounted执行后续pageNo变动则不影响接口调用
if (!this.initFlag) {
this.search(this.metaList, this.q)
}
this.search(this.metaList, this.q)
},
// 点击上一页箭头
prev () {
@@ -806,48 +739,16 @@ export default {
},
jumpNewDetetion () {
this.$router.push({
path: '/detectionPolicy',
path: '/detectionsNew',
query: {
t: +new Date()
}
})
},
isCheckFilterByQ (params, index) {
if (params.resource) {
let obj
if (index === 0) {
obj = this.filterData[this.pageType][index].data.find(d => params.resource.indexOf(d.value) > -1 && params.resource.indexOf('status') > -1)
} else {
obj = this.filterData[this.pageType][index].data.find(d => params.resource.indexOf(d.value) > -1)
}
if (obj) {
this.filterData[this.pageType][index].value = [obj.value]
this.filterData[this.pageType][index].flag = true
}
} else {
this.filterData[this.pageType][index].value = []
this.filterData[this.pageType][index].flag = true
}
}
},
mounted () {
let { q } = this.$route.query
// 如果地址栏有listMode即列表页并非首页则开始搜索
if (q) {
// %位置为0是输入中文时能解码%2025%分别是空格和%的情况
if (q && (q.indexOf('%') === 0 || q.indexOf('%20') > -1 || q.indexOf('%25') > -1)) {
q = decodeURI(q)
}
// %位置不为0即内容包含非英文时
const str1 = q.substring(q.indexOf('%'), q.indexOf('%') + 3)
if (q && q.indexOf('%') > 0 && (str1 !== '%20' || str1 === '%25')) {
q = decodeURI(q)
}
}
this.queryFilter(q)
this.initFlag = false
this.queryList(q)
this.queryFilter()
this.queryList()
this.debounceFunc = this.$_.debounce(this.resize, 300)
window.addEventListener('resize', this.debounceFunc)
},
@@ -920,61 +821,37 @@ export default {
'filterData.securityEvent.0.value': {
deep: true,
handler (n, o) {
if (!this.filterData.securityEvent[0].flag) {
this.$refs.search.changeParams({ column: this.filterData.securityEvent[0].column, oldValue: o, newValue: n })
} else {
this.filterData.securityEvent[0].flag = false
}
this.$refs.search.changeParams({ column: this.filterData.securityEvent[0].column, oldValue: o, newValue: n })
}
},
'filterData.securityEvent.1.value': {
deep: true,
handler (n, o) {
if (!this.filterData.securityEvent[1].flag) {
this.$refs.search.changeParams({ column: this.filterData.securityEvent[1].column, oldValue: o, newValue: n })
} else {
this.filterData.securityEvent[1].flag = false
}
this.$refs.search.changeParams({ column: this.filterData.securityEvent[1].column, oldValue: o, newValue: n })
}
},
'filterData.securityEvent.2.value': {
deep: true,
handler (n, o) {
if (!this.filterData.securityEvent[2].flag) {
this.$refs.search.changeParams({ column: this.filterData.securityEvent[2].column, oldValue: o, newValue: n })
} else {
this.filterData.securityEvent[2].flag = false
}
this.$refs.search.changeParams({ column: this.filterData.securityEvent[2].column, oldValue: o, newValue: n })
}
},
'filterData.securityEvent.3.value': {
deep: true,
handler (n, o) {
if (!this.filterData.securityEvent[3].flag) {
this.$refs.search.changeParams({ column: this.filterData.securityEvent[3].column, oldValue: o, newValue: n })
} else {
this.filterData.securityEvent[3].flag = false
}
this.$refs.search.changeParams({ column: this.filterData.securityEvent[3].column, oldValue: o, newValue: n })
}
},
'filterData.securityEvent.4.value': {
deep: true,
handler (n, o) {
if (!this.filterData.securityEvent[4].flag) {
this.$refs.search.changeParams({ column: this.filterData.securityEvent[4].column, oldValue: o, newValue: n })
} else {
this.filterData.securityEvent[4].flag = false
}
this.$refs.search.changeParams({ column: this.filterData.securityEvent[4].column, oldValue: o, newValue: n })
}
},
'filterData.securityEvent.5.value': {
deep: true,
handler (n, o) {
if (!this.filterData.securityEvent[5].flag) {
this.$refs.search.changeParams({ column: this.filterData.securityEvent[5].column, oldValue: o, newValue: n })
} else {
this.filterData.securityEvent[5].flag = false
}
this.$refs.search.changeParams({ column: this.filterData.securityEvent[5].column, oldValue: o, newValue: n })
}
},
'filterData.performanceEvent.0.value': {
@@ -994,37 +871,12 @@ export default {
window.removeEventListener('resize', this.debounceFunc)
},
setup () {
const store = useStore()
let { params, query, path } = useRoute()
// 获取路由跳转过的历史状态,赋值给当前界面,起到保留状态的作用,如浏览器的回退前进等
const routerObj = store.getters.getRouterHistoryList.find(item => item.t === query.t)
if (routerObj) {
params = routerObj.params
query = routerObj.query
path = routerObj.path
// 如果当前界面之前载入过,获取状态后更新地址栏,以便后续的赋值操作
const newUrl = urlParamsHandler(window.location.href, useRoute().query, query)
overwriteUrl(newUrl)
}
const { params } = useRoute()
const pageType = params.typeName
// 获取url携带的range、startTime、endTime
const rangeParam = query.range
const startTimeParam = query.startTime
const endTimeParam = query.endTime
const dateRangeValue = rangeParam ? parseInt(query.range) : 60
const timeFilter = ref({ dateRangeValue })
if (!startTimeParam || !endTimeParam) {
const { startTime, endTime } = getNowTime(60)
timeFilter.value.startTime = getSecond(startTime)
timeFilter.value.endTime = getSecond(endTime)
// 如果没有时间参数就将参数写入url
const newUrl = urlParamsHandler(window.location.href, useRoute().query, { startTime: timeFilter.value.startTime, endTime: timeFilter.value.endTime, range: dateRangeValue })
overwriteUrl(newUrl)
} else {
timeFilter.value.startTime = parseInt(startTimeParam)
timeFilter.value.endTime = parseInt(endTimeParam)
}
const dateRangeValue = 60
const { startTime, endTime } = getNowTime(dateRangeValue)
const timeFilter = ref({ startTime, endTime, dateRangeValue })
return {
timeFilter,
pageType

View File

@@ -1,252 +0,0 @@
<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${drawerInfo.status}`">
{{ $t(switchStatus(drawerInfo.status)) }}
</div>
</div>
<div class="drawer-basic-function">
<div class="detection-drawer-title">{{ $t('overall.name') }}</div>
<div class="basic-function-value">{{ $_.get(detailData, 'name', '-') || '-'}}</div>
</div>
<div class="drawer-basic-function">
<div class="detection-drawer-title">{{ $t('overall.type') }}</div>
<div class="basic-function-value">{{ $_.get(detailData, 'eventType', '-') || '-'}}</div>
</div>
<div class="drawer-basic-description">
<div class="detection-drawer-title">{{ $t('config.dataSet.description') }}</div>
<div class="basic-description-value">{{ $_.get(detailData, 'description', '-') || '-' }}</div>
</div>
</div>
<div class="detection-drawer-collapse">
<el-collapse v-model="activeRule">
<el-collapse-item :title="$t('detection.ruleDefinition')" name="rule">
<div class="drawer-collapse-content">
<div class="drawer-basic-function">
<div class="detection-drawer-title">{{ $t('config.user.source') }}</div>
<div class="basic-function-value">{{ changeCategory(detailData.category) }}</div>
</div>
<div v-if="detailData.ruleType==='indicator_match'">
<div class="drawer-basic-function">
<div class="detection-drawer-title">{{ $t('detection.library') }}</div>
<span class="basic-function-value">{{ $_.get(detailData, 'ruleConfigObj.knowledgeBase.name', '-') || '-' }}</span>
</div>
<div class="drawer-basic-function">
<div class="detection-drawer-title">{{ $t('detection.level') }}</div>
<div class="detection-drawer-title">
<div class="detection__icon" :style="`background-color: ${eventSeverityColor[detailData.ruleConfigObj.level]}`"></div>
<div class="basic-function-value">{{ changeSecurityLevel(detailData.ruleConfigObj) }}</div>
</div>
</div>
</div>
<div v-else>
<div class="drawer-basic-function">
<div class="detection-drawer-title">{{ $t('detection.create.dimensions') }}</div>
<span class="detection-tag-blue">{{ detailData.dimensions }}</span>
</div>
<div class="drawer-basic-function">
<div class="detection-drawer-title">{{ $t('detections.filters') }}</div>
<span class="detection-tag-blue">Source Port</span>
<span style="margin: 0 6px;">{{ $t('detections.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">{{ $t('detections.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="$t('detection.create.trigger')" name="trigger">
<div class="drawer-collapse-content" v-if="language==='en'">
<div class="drawer-collapse-trigger">
Triggered when conditions occur at least
<span style="color: #046ECA">
{{ atLeast }} {{ times }}
</span> in
<span style="color: #046ECA">
{{ getNumberFromStr($_.get(detailData, 'ruleTriggerObj.interval', '0')) || '-' }}
{{ $_.get(detailData, 'ruleTriggerObj.intervalVal', '-') || '-' }}
</span>
</div>
<div class="drawer-basic-function">
<div class="detection-drawer-title">{{ $t('detection.evaluationFrequency') }}</div>
<div class="drawer-trigger-minutes">
{{ getNumberFromStr($_.get(detailData, 'ruleTriggerObj.resetInterval', '0')) || '-' }}
{{ $_.get(detailData, 'ruleTriggerObj.intervalVal', '-') || '-' }}
</div>
</div>
</div>
<div class="drawer-collapse-content" v-if="language==='cn'">
<div class="drawer-collapse-trigger">
当条件为
<span style="color: #046ECA">
{{ getNumberFromStr($_.get(detailData, 'ruleTriggerObj.interval', '0')) || '-' }}
{{ changeValueToLabel(detailData.ruleTriggerObj) }}
</span>内至少出现
<span style="color: #046ECA">
{{ $_.get(detailData, 'ruleTriggerObj.atLeast', '-') || '-' }}
</span>时触发
</div>
<div class="drawer-basic-function">
<div class="detection-drawer-title">评估频率</div>
<div class="drawer-trigger-minutes">
{{ getNumberFromStr($_.get(detailData, 'ruleTriggerObj.resetInterval', '0')) || '-' }}
{{ changeValueToLabel(detailData.ruleTriggerObj) }}
</div>
</div>
</div>
</el-collapse-item>
</el-collapse>
</div>
</div>
</template>
<script>
import { switchStatus, toUpperCaseByString } from '@/utils/tools'
import { detectionUnitList, eventSeverityColor, securityLevel, storageKey } from '@/utils/constants'
import axios from 'axios'
import { api } from '@/utils/api'
export default {
name: 'DetectionDrawer',
props: {
drawerInfo: {
type: Object
}
},
data () {
return {
activeRule: 'rule',
activeTrigger: 'trigger',
detailData: {},
eventSeverityColor,
severityList: [],
language: 'en',
atLeast: 0,
times: 'time'
}
},
watch: {
drawerInfo: {
immediate: true,
deep: true,
handler (n) {
if (n) {
this.getDetailData()
}
}
}
},
mounted () {
this.language = localStorage.getItem(storageKey.language) || 'en'
},
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.status === 200) {
this.detailData = res.data.data
this.atLeast = this.$_.get(this.detailData, 'ruleTriggerObj.atLeast', '-')
if (!isNaN(this.atLeast) && this.atLeast > 1) {
this.times = 'times'
} else {
this.times = 'time'
}
}
}).catch(err => {
console.error(err)
})
},
getNumberFromStr (str) {
return str.match(/\d+(\.\d+)?/g)[0]
},
changeCategory (value) {
if (value) {
const obj = detectionUnitList.categoryList.find(d => d.value === value)
let label = value
if (obj) {
label = this.$t(obj.label)
}
return label
} else {
return '-'
}
},
changeSecurityLevel (config) {
if (config) {
if (config.level) {
const obj = securityLevel.find(d => d.value === config.level)
let label = config.level
if (obj) {
label = this.$t(obj.label)
}
return label
} else {
return '-'
}
} else {
return '-'
}
},
changeValueToLabel (config) {
if (config) {
if (config.intervalVal) {
const obj = detectionUnitList.intervalListCN.find(d => d.value === config.intervalVal)
let label = config.intervalVal
if (obj) {
label = this.$t(obj.label)
}
return label
} else {
return '-'
}
} else {
return '-'
}
}
}
}
</script>
<style lang="scss">
</style>

View File

@@ -1,128 +0,0 @@
<template>
<div>
<div class="new-detection-filter-title">
{{ $t('detections.filters') }}
</div>
<div class="new-detection-filter-content">
<div>
<div class="new-filter-content-title">{{ $t('overall.status') }}</div>
<div class="new-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.name" class="new-filter-content-checkbox" :label="item.status">
<div>{{ item.name }}</div>
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div>
<div class="new-filter-content-title">{{ $t('overall.category') }}</div>
<div class="new-filter-content-content">
<el-checkbox-group v-model="checkCategory" @change="onChangeCategory">
<el-checkbox v-for="item in categoryList" :key="item.name" class="new-filter-content-checkbox" :label="item.name">
{{ item.label }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div>
<div class="new-filter-content-title">{{ $t('overall.type') }}</div>
<div class="new-filter-content-content">
<el-checkbox-group v-model="checkEventType" @change="onChangeCategory" style="display: flex;flex-direction: column">
<el-checkbox v-for="item in eventTypeList" :key="item.name" class="new-filter-content-checkbox" :label="item.name">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
import { api } from '@/utils/api'
import { switchStatus } from '@/utils/tools'
import { detectionUnitList } from '@/utils/constants'
export default {
name: 'DetectionFilter',
data () {
return {
statusList: [], // 状态列表数据
categoryList: [], // 类别列表
eventTypeList: [], // 事件类型列表
checkStatus: [], // checkbox选择的状态列表
checkCategory: [], // checkbox选择的类别
checkEventType: [], // checkbox选择的事件类型
policyTotal: 0, // 策略总条数,与筛选条件无关
policyEnabledNum: 0 // 策略中状态为enabled的数量
}
},
mounted () {
this.getFilterData()
},
methods: {
getFilterData () {
axios.get(api.detection.statistics).then(response => {
if (response.status === 200) {
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 = []
}
if (data.categoryList) {
this.categoryList = []
data.categoryList.forEach(item => {
const obj = detectionUnitList.categoryList.find(d => d.value === item.name)
if (obj) {
this.categoryList.push({ ...item, label: this.$t(obj.label) })
}
})
} else {
this.categoryList = []
}
if (data.eventTypeList) {
this.eventTypeList = data.eventTypeList
} else {
this.eventTypeList = []
}
} else {
console.error(response.data)
}
}).catch((e) => {
console.error(e)
this.statusList = []
this.categoryList = []
this.eventTypeList = []
})
},
onChangeCategory () {
const obj = {}
if (this.checkStatus.length === 1) {
obj.status = this.checkStatus.join(',')
} else {
delete obj.status
}
if (this.checkCategory.length > 0) {
obj.category = this.checkCategory.join(',')
}
if (this.checkEventType.length > 0) {
obj.eventType = this.checkEventType.join(',')
}
this.$emit('filterParams', obj)
}
}
}
</script>

View File

@@ -1,497 +0,0 @@
<template>
<div class="detection-form">
<loading :loading="myLoading"></loading>
<div class="detection-form-header">
{{ ruleId ? $t('detection.editEventPolicies') : $t('detection.createEventPolicies') }}
</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">{{ $t('detection.create.generalSettings') }}</div>
</div>
</template>
<div class="form-collapse-content">
<general-settings ref="form" :editObj="editObj" :isComplete="isComplete" @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">{{ $t('detection.create.ruleDefinition') }}</div>
</div>
</template>
<div class="form-collapse-content">
<rule-definition :settingObj="settingObj" :editObj="editObj" :isComplete="isComplete" @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">{{ $t('detection.create.trigger') }}</div>
</div>
</template>
<div class="form-collapse-content margin-t-18">
<el-form v-if="language==='en'" 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" maxlength="5" v-model="triggerObj.atLeast" oninput="value=value.replace(/[^\d]/g,'')"></el-input>
</el-form-item>
<div>times within</div>
<el-form-item prop="interval" class="policy-form-item">
<el-input size="mini" maxlength="5" 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="resetInterval" class="policy-form-item">
<el-input size="mini" maxlength="5" v-model="triggerObj.resetInterval" oninput="value=value.replace(/[^\d]/g,'')"></el-input>
</el-form-item>
<el-form-item prop="resetIntervalVal">
<el-select v-model="triggerObj.resetIntervalVal" 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>
</el-form>
<el-form v-if="language==='cn'" class="trigger-block margin-b-20" ref="form3" :model="triggerObj" :rules="rules">
<div class="trigger-block-item margin-b-10">
<el-form-item prop="interval">
<el-input size="mini" maxlength="5" 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>
内至少发生
<el-form-item prop="atLeast">
<el-input size="mini" maxlength="5" v-model="triggerObj.atLeast" oninput="value=value.replace(/[^\d]/g,'')"></el-input>
</el-form-item>
</div>
<div class="trigger-block-item">
若连续
<el-form-item prop="resetInterval">
<el-input size="mini" maxlength="5" v-model="triggerObj.resetInterval" oninput="value=value.replace(/[^\d]/g,'')"></el-input>
</el-form-item>
<el-form-item prop="resetIntervalVal">
<el-select v-model="triggerObj.resetIntervalVal" 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>
</el-form>
<div class="form-setting__btn1">
<div class="btn1">
<el-button @click="createPolicy('')">{{ $t('overall.save') }}</el-button>
</div>
<el-button @click="createPolicy('enabled')">{{ $t('overall.save') }} & {{ $t('detection.create.enablePolicy') }}</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 { api } from '@/utils/api'
import axios from 'axios'
import { useRoute } from 'vue-router'
import { ref } from 'vue'
import { getDurationsTimeByType, getTimeByDurations } from '@/utils/date-util'
import Loading from '@/components/common/Loading'
import { storageKey, detectionUnitList } from '@/utils/constants'
export default {
name: 'DetectionForm',
data () {
const intervalValidator = (rule, value, callback) => {
const obj = this.handleIntervalByDateType(rule, value, this.triggerObj.intervalVal)
if (!obj.flag && obj.msg) {
callback(new Error(obj.msg))
} else {
callback()
}
}
const intervalValValidator = (rule, value, callback) => {
const obj = this.handleIntervalByDateType(rule, this.triggerObj.intervalVal, value)
if (!obj.flag && obj.msg) {
this.$refs.form3.validateField('interval')
callback()
} else {
callback()
}
}
const resetIntervalValidator = (rule, value, callback) => {
const obj = this.handleIntervalByDateType(rule, value, this.triggerObj.resetIntervalVal)
if (!obj.flag && obj.msg) {
callback(new Error(obj.msg))
} else {
callback()
}
}
const resetIntervalValValidator = (rule, value, callback) => {
const obj = this.handleIntervalByDateType(rule, this.triggerObj.resetIntervalVal, value)
if (!obj.flag && obj.msg) {
this.$refs.form3.validateField('resetInterval')
callback()
} else {
callback()
}
}
return {
activeNames: ['1'],
rules: {
atLeast: [
{
required: true,
message: this.$t('validate.required'),
trigger: 'blur'
}
],
interval: [
{
required: true,
message: this.$t('validate.required'),
trigger: 'blur'
},
{
validator: intervalValidator,
trigger: 'blur'
}
],
intervalVal: [
{
required: true,
message: this.$t('validate.required'),
trigger: 'change'
},
{
validator: intervalValValidator,
trigger: 'change'
}
],
resetInterval: [
{
required: true,
message: this.$t('validate.required'),
trigger: 'blur'
},
{
validator: resetIntervalValidator,
trigger: 'blur'
}
],
resetIntervalVal: [
{
required: true,
message: this.$t('validate.required'),
trigger: 'change'
},
{
validator: resetIntervalValValidator,
trigger: 'change'
}
]
},
intervalList: [],
editObj: {},
isComplete: true, // 参数完整标识默认完整照顾编辑模式false即不完整
language: 'en'
}
},
components: {
GeneralSettings,
RuleDefinition,
Loading
},
mounted () {
this.language = localStorage.getItem(storageKey.language) || 'en'
if (this.language === 'en') {
this.intervalList = detectionUnitList.intervalList
} else if (this.language === 'cn') {
this.intervalList = detectionUnitList.intervalListCN
}
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: '',
resetIntervalVal: '',
finishFlag: false
})
return {
ruleId,
myLoading,
pageNoForTable,
settingObj,
ruleObj,
triggerObj
}
},
methods: {
/** 编辑时获取详情 */
getDetailInfo () {
if (this.ruleId) {
axios.get(`${api.detection.detail}/${this.ruleId}`).then(response => {
if (response.status === 200) {
if (!response.data.data) {
throw new Error('No data found, id: ' + this.ruleId)
}
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.resetIntervalVal = getTimeByDurations(this.triggerObj.resetInterval).type
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: '/detectionPolicy',
query: {
pageNo: this.pageNoForTable ? Number(this.pageNoForTable) : 1,
t: +new Date()
}
})
})
}
},
/** 获取General Settings折叠板form数据 */
getFormSetting (data) {
this.handleActiveNames('1', this.activeNames, data.settingNoContinue)
this.settingObj = JSON.parse(JSON.stringify(data))
},
/** 获取Rule Definition折叠板form数据 */
getRuleObj (data) {
this.handleActiveNames('2', this.activeNames, data.ruleNoContinue)
this.ruleObj = JSON.parse(JSON.stringify(data))
},
/** 自动展开收起折叠板 */
handleActiveNames (name, arr, flag) {
if (!flag) {
const list = arr
list.splice(list.indexOf(name), 1)
if (name === '1' && list.indexOf('2') < 0) {
list.push('2')
}
if (name === '2' && list.indexOf('3') < 0) {
list.push('3')
}
this.activeNames = []
list.forEach(t => {
this.activeNames.push(t)
})
}
},
/** 创建policy */
createPolicy (flag) {
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.$_.cloneDeep({ ...this.settingObj, ruleConfig: JSON.stringify(this.ruleObj), ruleTrigger: this.triggerObj })
if (flag) {
formObj.status = 1
}
// 将时间转为参数所需如5分钟转为PT5M
formObj.ruleTrigger.resetInterval = getDurationsTimeByType(formObj.ruleTrigger.resetInterval, formObj.ruleTrigger.resetIntervalVal)
formObj.ruleTrigger.interval = getDurationsTimeByType(formObj.ruleTrigger.interval, formObj.ruleTrigger.intervalVal)
formObj.ruleTrigger.atLeast = parseInt(formObj.ruleTrigger.atLeast)
formObj.ruleTrigger = JSON.stringify(formObj.ruleTrigger)
// 删除多余参数
delete formObj.ruleConfigObj
delete formObj.ruleTriggerObj
delete formObj.editFlag
delete formObj.saveFlag
if (!this.ruleId) {
// post调用是新增put是编辑
this.myLoading = true
axios.post(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: '/detectionPolicy',
query: {
t: +new Date()
}
})
} else {
console.error(response.data.message)
this.$message.error(this.errorMsgHandler(response))
}
}).catch(e => {
console.error(e)
this.$message.error(this.errorMsgHandler(e))
}).finally(() => {
this.myLoading = false
})
} else {
console.log('进来')
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: '/detectionPolicy',
query: {
pageNo: self.pageNoForTable ? Number(self.pageNoForTable) : 1,
t: +new Date()
}
})
} else {
console.error(response.data.message)
this.$message.error(this.errorMsgHandler(response))
}
}).catch(e => {
console.error(e)
this.$message.error(this.errorMsgHandler(e))
}).finally(() => {
this.myLoading = false
})
}
} else {
this.isComplete = false
}
})
} else if (settingLen === 0) {
this.isComplete = false
this.handleFormError('1')
} else if (ruleLen === 0) {
this.isComplete = false
this.handleFormError('2')
}
},
handleFormError (name) {
const list = this.activeNames
if (list.indexOf(name) < 0) {
list.push(name)
this.activeNames = []
list.forEach(t => {
this.activeNames.push(t)
})
}
this.$message.error(this.$t('detection.create.informationFilled'))
},
handleIntervalByDateType (rule, value, type) {
if (value && (type === 'hours' || type === '小时')) {
if (parseInt(value) <= 24) {
return { flag: true }
} else {
return { flag: false, msg: this.$t('policy.dateTimeRangeHours') }
}
}
if (value && (type === 'minutes' || type === '分钟')) {
if (parseInt(value) <= 1440) {
return { flag: true }
} else {
return { flag: false, msg: this.$t('policy.dateTimeRangeMinutes') }
}
}
if (value && (type === 'seconds' || type === '秒')) {
if (parseInt(value) <= 86400) {
return { flag: true }
} else {
return { flag: false, msg: this.$t('policy.dateTimeRangeSeconds') }
}
}
}
}
}
</script>

View File

@@ -4,7 +4,7 @@ import {
} from '@/views/charts/charts/tools'
import unitConvert from '@/utils/unit-convert'
import _ from 'lodash'
import { dateFormatByAppearance, xAxisTimeFormatter, xAxisTimeRich } from '@/utils/date-util'
import { dateFormatByAppearance } from '@/utils/date-util'
import { unitTypes } from '@/utils/constants'
const severitySeriesIndexMappings = [
@@ -67,8 +67,8 @@ export const multipleBarOption = {
source: [
]
},
xAxis: [{
type: 'time',
xAxis: {
type: 'category',
axisTick: { show: false },
axisLine: {
show: true,
@@ -77,16 +77,13 @@ export const multipleBarOption = {
}
},
axisLabel: {
formatter: xAxisTimeFormatter,
rich: xAxisTimeRich,
color: '#737373',
interval: 'auto'
},
splitNumber: 8,
splitLine: {
show: false
}
}],
},
yAxis: {
axisTick: { show: false },
axisLine: {
@@ -308,10 +305,6 @@ export const metricOption = {
str += `<span class="cn-chart-tooltip-value">
${unitConvert(item.data[1], unitTypes.time).join(' ')}
</span>`
} else if (item.seriesName === 'Http error') {
str += `<span class="cn-chart-tooltip-value">
${unitConvert(item.data[1], unitTypes.number, '', '', 0).join(' ')}
</span>`
} else {
str += `<span class="cn-chart-tooltip-value">
${unitConvert(item.data[1], unitTypes.percent, '', '', 0).join(' ')}

View File

@@ -3,243 +3,162 @@
<div class="overview__left">
<div class="overview__title">{{ $t('overall.remark') }}</div>
<div class="overview__row">
<div class="row__content1" v-if="detection.eventType === 'Command and Control' && detection.isBuiltin == 1">
<span class="row__content--link">{{detection.victimIp}}</span>&nbsp;&nbsp;communicated with&nbsp;<span class="row__content--link">{{detection.offenderIp}}</span>&nbsp;&nbsp;that was associated with the indicator of {{detection.eventName}} activity, {{$_.get(detection, 'eventInfoObj.ioc_value', '') || ''}}.
</div>
<div class="row__content1" v-else-if="detection.eventType === 'Anonymity' && detection.isBuiltin == 1">
<span class="row__content--link">{{detection.victimIp}}</span>&nbsp;&nbsp;communicated with&nbsp;<span class="row__content--link">{{detection.offenderIp}}</span>&nbsp;&nbsp;that was associated with the indicator of {{detection.eventName}}.
</div>
<div class="row__content1" v-else>
{{basicInfo.ruleDescription || '-'}}
<div class="row__content">
<template v-if="detection.securityType === 'command and control'">
<span
class="row__content--link"
@click="goDetail('ip', detection.victimIp)"
>{{ detection.victimIp }}</span>&nbsp;
<span>
{{$t('detection.commandAndControl')}}
</span>
<span class="row__content--link" @click="goDetail('ip', basicInfo.iocValue)">{{basicInfo.iocValue || '-'}}</span></template
>
<template v-else>
<span
class="row__content--link"
@click="goDetail('ip', detection.victimIp)"
>{{ detection.victimIp }}</span
>&nbsp;
<span>
{{$t('detection.payloadAndDelivery')}}
</span>
<span class="row__content--link"
@click="goDetail('ip', basicInfo.iocValue)"
>{{
basicInfo.iocValue
}}</span></template>
</div>
</div>
<div class="overview__title">Fields</div>
<div class="overview__row">
<div class="row__label">{{ $t('detection.list.startTime') }}</div>
<div class="row__content">
<i class="cn-icon cn-icon-time2 row__content__icon"></i>
{{ detection.startTime ? dateFormatByAppearance(detection.startTime) : '-' }}
{{
basicInfo.startTime
? dateFormatByAppearance(basicInfo.startTime)
: '-'
}}
</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detections.victimIp') }}</div>
<div class="row__content">{{ detection.victimIp || '-' }}</div>
<div class="row__content">{{ basicInfo.victimIp || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detections.victimLocation') }}</div>
<div class="row__content">
<div v-if="$_.get(basicInfo, 'victimInfo.location.country')">
<img v-if="basicInfo.victimInfo.location.country===countryNameIdMapping.Unknown || !countryNameIdMapping[basicInfo.victimInfo.location.country]" src="../../../../public/images/flag/Unknown.svg" class="filter-country-flag">
<img v-else :src="require(`../../../../public/images/flag/${countryNameIdMapping[basicInfo.victimInfo.location.country]}.png`)" class="filter-country-flag" >
</div>
{{ locationRegion(basicInfo.victimInfo) }}
{{ basicInfo.victimLocationCountry || '-' }}
</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detections.victimAsn') }}</div>
<div class="row__content">{{ $_.get(basicInfo, 'victimInfo.asn.asn', '-') || '-' }}</div>
<div class="row__content">{{ basicInfo.victimAsn || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detections.offenderIp') }}</div>
<div class="row__content">{{ detection.offenderIp || '-' }}</div>
<div class="row__content">{{ basicInfo.offenderIp || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detections.offenderLocation') }}</div>
<div class="row__content">
<div v-if="$_.get(basicInfo, 'offenderInfo.location.country')">
<img v-if="basicInfo.offenderInfo.location.country===countryNameIdMapping.Unknown || !countryNameIdMapping[basicInfo.offenderInfo.location.country]" src="../../../../public/images/flag/Unknown.svg" class="filter-country-flag">
<img v-else :src="require(`../../../../public/images/flag/${countryNameIdMapping[basicInfo.offenderInfo.location.country]}.png`)" class="filter-country-flag" >
</div>
{{ locationRegion(basicInfo.offenderInfo) }}
{{ basicInfo.offenderLocationCountry || '-' }}
</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detections.offenderAsn') }}</div>
<div class="row__content">{{ $_.get(basicInfo, 'offenderInfo.asn.asn', '-') || '-' }}</div>
<div class="row__content">{{ basicInfo.offenderAsn || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('overall.domain') }}</div>
<div class="row__content">{{ detection.domain || '-' }}</div>
<div class="row__content">{{ basicInfo.domain || '-' }}</div>
</div>
<template v-if="detection.domain">
<div class="overview__row">
<div class="row__label">{{ $t('entities.domainCategory') }}</div>
<div class="row__content">{{ $_.get(basicInfo, 'domainInfo.category.categoryName', '-') || '-' }}</div>
<div class="overview__row">
<div class="row__label">{{ $t('entities.domainCategory') }}</div>
<div class="row__content">
{{ basicInfo.domainCategoryName || '-' }}
</div>
<div class="overview__row">
<div class="row__label">{{ $t('entities.domainDetail.categoryGroup') }}</div>
<div class="row__content">{{ $_.get(basicInfo, 'domainInfo.category.categoryGroup', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">
{{ $t('entities.domainDetail.categoryGroup') }}
</div>
<div class="overview__row">
<div class="row__label">{{ $t('entities.reputationLevel') }}</div>
<div class="row__content" v-if="$_.get(basicInfo, 'domainInfo.category.reputationLevel')">
<div
class="row__tag row__tag__level"
:style="`background-color:${riskLevelColor1[basicInfo.domainInfo.category.reputationLevel]}`">
{{ reputationLevel(basicInfo.domainInfo.category.reputationLevel) || '-' }}
</div>
</div>
<div class="row__content" v-else>-</div>
<div class="row__content">
{{ basicInfo.domainCategoryGroup || '-' }}
</div>
</template>
<template v-if="detection.app">
<div class="overview__row">
<div class="row__label">APP</div>
<div class="row__content">{{ $_.get(basicInfo, 'appInfo.category.appName', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('entities.reputationLevel') }}</div>
<div class="row__content">
<div
class="row__tag"
:style="`background-color:${eventSeverityColor[basicInfo.domainReputationLevel]}`"
>
{{ basicInfo.domainReputationLevel || '-' }}
</div>
</div>
<div class="overview__row">
<div class="row__label">APP {{ $t('entities.category') }}</div>
<div class="row__content">{{ $_.get(basicInfo, 'appInfo.category.appCategory', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">APP</div>
<div class="row__content">{{ basicInfo.appName || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">APP {{ $t('entities.category') }}</div>
<div class="row__content">{{ basicInfo.appCategory || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">APP {{ $t('entities.subcategory') }}</div>
<div class="row__content">{{ basicInfo.appSubcategory || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('overall.appRisk') }}</div>
<div class="row__content">
<div
class="row__tag"
:style="`background-color:${eventSeverityColor[basicInfo.appRisk]}`"
>
{{ basicInfo.appRisk || '-' }}
</div>
</div>
<div class="overview__row">
<div class="row__label">APP {{ $t('entities.subcategory') }}</div>
<div class="row__content">{{ $_.get(basicInfo, 'appInfo.category.appSubcategory', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detections.malware') }}</div>
<div class="row__content">{{ basicInfo.malwareName || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detections.malwareAlias') }}</div>
<div class="row__content">{{ basicInfo.malwareAlias || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detections.malwareDescription') }}</div>
<div class="row__content">
{{ basicInfo.malwareDescription || '-' }}
</div>
<div class="overview__row">
<div class="row__label">{{ $t('overall.appRisk') }}</div>
<div class="row__content" v-if="$_.get(basicInfo, 'appInfo.category.appRisk')">
<div
class="row__tag row__tag__level"
:style="`background-color:${riskLevelColor[basicInfo.appInfo.category.appRisk]}`">
{{ appRisk(basicInfo.appInfo.category.appRisk) || '-' }}
</div>
</div>
<div class="row__content" v-else>-</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detections.malwarePlatforms') }}</div>
<div class="row__content">{{ basicInfo.malwarePlatforms || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detections.malwareTechniques') }}</div>
<div class="row__content">
{{ basicInfo.malwareTechniques || '-' }}
</div>
</template>
<template v-if="detection.malware">
<div class="overview__row">
<div class="row__label">{{ $t('detections.malware') }}</div>
<div class="row__content">{{ $_.get(detection, 'malware.malwareName', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detections.malwareGroups') }}</div>
<div class="row__content">
{{ basicInfo.malwareGroups || '-' }}
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detections.malwareAlias') }}</div>
<div class="row__content">{{ $_.get(detection, 'malware.malwareAlias', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detections.reference') }}</div>
<div class="row__content row__content--link">
{{ reference || '-' }}
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detections.malwareDescription') }}</div>
<div class="row__content">{{ $_.get(detection, 'malware.mitreAttackDescription', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detections.malwarePlatforms') }}</div>
<div class="row__content" v-if="$_.get(detection, 'malware.mitreAttackPlatforms')">
<svg class="icon item-popover-up row__content__svg" aria-hidden="true">
<use xlink:href="#cn-icon-windows"></use>
</svg>
{{ detection.malware.mitreAttackPlatforms }}
</div>
<div class="row__content" v-else>-</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detections.malwareTechniques') }}</div>
<div class="row__content">{{ $_.get(detection, 'malware.mitreAttackTechniques', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detections.malwareGroups') }}</div>
<div class="row__content">{{ $_.get(detection, 'malware.mitreAttackGroups', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detections.reference') }}</div>
<div class="row__content row__content--link" v-if="$_.get(detection, 'malware.reference')">
{{ detection.malware.reference }}
</div>
<div class="row__content">-</div>
</div>
</template>
<template v-else-if="detection.darkweb">
<div class="overview__row">
<div class="row__label">{{ $t('detection.nodeTypeLower') }}</div>
<div class="row__content">{{ $_.get(detection, 'darkweb.nodeType', '-') || '-' }}</div>
</div>
<template v-if="$_.get(detection.darkweb, 'nodeType', '') === 'tor'">
<div class="overview__row">
<div class="row__label">{{ $t('detection.tor.torFingerprint') }}</div>
<div class="row__content">{{ $_.get(detection, 'darkweb.torFingerprint', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detection.tor.torFlags') }}</div>
<div class="row__content">{{ $_.get(detection, 'darkweb.torFlags', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detection.tor.torVersion') }}</div>
<div class="row__content">{{ $_.get(detection, 'darkweb.torVersion', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">Tor ORPort</div>
<div class="row__content">{{ $_.get(detection, 'darkweb.torOrPort', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">Tor DirPort</div>
<div class="row__content">{{ $_.get(detection, 'darkweb.torDirPort', '-') || '-' }}</div>
</div>
</template>
<template v-else-if="$_.get(detection.darkweb, 'nodeType', '') === 'i2p'">
<div class="overview__row">
<div class="row__label">I2P Hash</div>
<div class="row__content">{{ $_.get(detection, 'darkweb.i2pHash', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detection.tor.i2pVersion') }}</div>
<div class="row__content">{{ $_.get(detection, 'darkweb.i2pVersion', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detection.tor.i2pBandwidth') }}</div>
<div class="row__content">{{ $_.get(detection, 'darkweb.i2pBandwidth', '-') || '-' }}</div>
</div>
</template>
<template v-else-if="$_.get(detection.darkweb, 'nodeType', '') === 'mtproxy'">
<div class="overview__row">
<div class="row__label">MTProxy Secret</div>
<div class="row__content">{{ $_.get(detection, 'darkweb.mtproxySecret', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detection.tor.mtproxyPort') }}</div>
<div class="row__content">{{ $_.get(detection, 'darkweb.mtproxyPort', '-') || '-' }}</div>
</div>
</template>
<template v-else-if="$_.get(detection.darkweb, 'nodeType', '') === 'obfs4'">
<div class="overview__row">
<div class="row__label">{{ $t('detection.tor.obfs4Fingerprint') }}</div>
<div class="row__content">{{ $_.get(detection, 'darkweb.obfs4Fingerprint', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detection.tor.obfs4Cert') }}</div>
<div class="row__content">{{ $_.get(detection, 'darkweb.obfs4Cert', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detection.tor.obfs4IatMode') }}</div>
<div class="row__content">{{ $_.get(detection, 'darkweb.obfs4IatMode', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detection.tor.obfs4Port') }}</div>
<div class="row__content">{{ $_.get(detection, 'darkweb.obfs4Port', '-') || '-' }}</div>
</div>
</template>
<template v-else-if="$_.get(detection.darkweb, 'nodeType', '') === 'snowflake'">
<div class="overview__row">
<div class="row__label">{{ $t('detection.tor.snowflakePort') }}</div>
<div class="row__content">{{ $_.get(detection, 'darkweb.snowflakePort', '-') || '-' }}</div>
</div>
</template>
</template>
<template v-else>
<div class="overview__row">
<div class="row__label">{{ $t('detection.libraryId') }}</div>
<div class="row__content">{{ $_.get(detection, 'eventInfoObj.knowledge_id', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detection.libraryName') }}</div>
<div class="row__content">{{ $_.get(detection, 'eventInfoObj.name', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detection.iocType') }}</div>
<div class="row__content">{{ $_.get(detection, 'eventInfoObj.ioc_type', '-') || '-' }}</div>
</div>
<div class="overview__row">
<div class="row__label">{{ $t('detection.iocValue') }}</div>
<div class="row__content">{{ $_.get(detection, 'eventInfoObj.ioc_value', '-') || '-' }}</div>
</div>
</template>
</div>
<!-- <template v-if="detection.securityType === 'command and control' || detection.securityType === 'payload_delivery'">
</template>-->
</div>
<div class="overview__right">
<div class="overview__title">{{ $t('detections.goToVictim') }}</div>
@@ -248,7 +167,7 @@
<span class="row__content--span">{{ $t('detections.viewDetailOf') }}</span> &nbsp;
<span
class="row__content--link"
@click="goDetail('ip', detection.victimIp)">{{ detection.victimIp }}</span>
@click="goDetail('ip', basicInfo.victimIp)">{{ basicInfo.victimIp }}</span>
</div>
</div>
<div class="overview__title">{{ $t('detections.goToOffender') }}</div>
@@ -257,22 +176,22 @@
<span class="row__content--span">{{ $t('detections.viewDetailOf') }}</span> &nbsp;
<span
class="row__content--link"
@click="goDetail('ip', detection.offenderIp)"
>{{ detection.offenderIp }}</span
@click="goDetail('ip', basicInfo.offenderIp)"
>{{ basicInfo.offenderIp }}</span
>&nbsp;&nbsp;
<span
class="row__content--link"
@click="goDetail('domain', detection.domain)"
>{{ detection.domain }}</span
@click="goDetail('domain', basicInfo.domain)"
>{{ basicInfo.domain }}</span
>
</div>
</div>
<!-- <div class="overview__title">{{ $t('detections.goToHunt') }}</div>-->
<!-- <div class="overview__row">-->
<!-- <div class="row__content row__content&#45;&#45;link">-->
<!-- {{ $t('detections.viewAllRelated') }}-->
<!-- </div>-->
<!-- </div>-->
<div class="overview__title">{{ $t('detections.goToHunt') }}</div>
<div class="overview__row">
<div class="row__content row__content--link">
{{ $t('detections.viewAllRelated') }}
</div>
</div>
<div class="overview__title">
{{ $t('detections.relatedDetections') }}
</div>
@@ -302,12 +221,18 @@
<div class="timeline__severity timeline__severity--high">
<i
class="cn-icon cn-icon-alert-level"
:style="`color:${eventSeverityColor[event.severity]}`"
:style="`color:${eventSeverityColor[event.eventSeverity]}`"
></i>
<span>{{ event.severity }}</span>
<span>{{ event.eventSeverity }}</span>
</div>
<div class="timeline__security-type">
{{ event.securityType }}
</div>
<div class="timeline__start-time">
{{
dateFormatByAppearance(event.startTime)
}}
</div>
<div class="timeline__security-type">{{ event.eventType }}</div>
<div class="timeline__start-time">{{ dateFormatByAppearance(parseInt(event.startTime)) }}</div>
</div>
</div>
<div class="row-timeline__foot">
@@ -315,7 +240,7 @@
class="detection-ip"
:class="{
'detection-ip__current':
[detection.offenderIp, detection.victimIp].indexOf(
[basicInfo.offenderIp, basicInfo.victimIp].indexOf(
event.offenderIp,
) > -1,
}"
@@ -327,7 +252,7 @@
class="detection-ip"
:class="{
'detection-ip__current':
[detection.offenderIp, detection.victimIp].indexOf(
[basicInfo.offenderIp, basicInfo.victimIp].indexOf(
event.victimIp,
) > -1,
}"
@@ -345,8 +270,8 @@
<script>
import axios from 'axios'
import { api } from '@/utils/api'
import { getMillisecond, dateFormatByAppearance } from '@/utils/date-util'
import { eventSeverityColor, unitTypes, countryNameIdMapping, riskLevelMapping, riskLevelColor, riskLevelColor1 } from '@/utils/constants'
import { getMillisecond } from '@/utils/date-util'
import { eventSeverityColor, unitTypes } from '@/utils/constants'
import unitConvert from '@/utils/unit-convert'
import _ from 'lodash'
export default {
@@ -357,27 +282,34 @@ export default {
data () {
return {
eventSeverityColor,
riskLevelColor,
riskLevelColor1,
basicInfo: {},
events: [],
reference: 'https://attack.mitre.org',
countryNameIdMapping
reference: 'https://attack.mitre.org'
}
},
computed: {
formatT0 () {
const vm = this
return function (event) {
const diffSeconds = parseInt(event.diffSeconds)
const diffSeconds = event.diffSeconds
if (diffSeconds === 0) {
return 'T0'
}
const eventStartTime = parseInt(event.startTime)
const eventStartTime = event.startTime
const entityStartTime = vm.detection.startTime
if (_.isNumber(diffSeconds) && _.isNumber(eventStartTime) && _.isNumber(entityStartTime)) {
const suffix = unitConvert(diffSeconds, unitTypes.time, 's', null, 0).join('')
if (
_.isNumber(diffSeconds) &&
_.isNumber(eventStartTime) &&
_.isNumber(entityStartTime)
) {
const suffix = unitConvert(
diffSeconds,
unitTypes.time,
's',
null,
0
).join('')
if (eventStartTime > entityStartTime) {
return `T0+${suffix}`
} else if (eventStartTime < entityStartTime) {
@@ -386,105 +318,62 @@ export default {
}
return ''
}
},
appRisk () {
return function (level) {
const m = riskLevelMapping.find(mapping => {
return mapping.value == level
})
return (m && this.$t(m.label)) || level
}
},
reputationLevel () {
return function (level) {
const m = riskLevelMapping.find(mapping => {
return mapping.name == level
})
return (m && this.$t(m.label)) || level
}
},
locationRegion (info) {
return function (info) {
if (!info || !info.location) {
return '-'
}
let result = ''
if (info.location.country) {
result += `${info.location.country},`
}
if (info.location.province) {
result += `${info.location.province},`
}
if (info.location.city) {
result += `${info.location.city},`
}
result = result.substr(0, result.length - 1)
if (!result) {
result = '-'
}
return result
}
}
},
methods: {
getMillisecond,
dateFormatByAppearance,
/** 初始化实体详情 */
initEntityDetail () {
// 为完整填充IP信息攻击者ip、受害者ip都进行调用
// 根据detection的eventInfo对象的ioc_type进行判断若为domainmalware信息从domain详情中获取并填充domain信息
// 若ioc_type为ip则调用ip接口填充malware信息
// 最后调用app填充app信息。经上获取完整实体详情则最少需要调用4次接口
if (this.detection.offenderIp) {
axios.get(`${api.detection.securityEvent.ipDetail}?resource=${this.detection.offenderIp}`).then(res => {
if (res.status === 200) {
this.basicInfo.offenderInfo = res.data.data
}
})
}
if (this.detection.victimIp) {
axios.get(`${api.detection.securityEvent.ipDetail}?resource=${this.detection.victimIp}`).then(res => {
if (res.status === 200) {
this.basicInfo.victimInfo = res.data.data
}
})
}
if (this.detection.domain) {
axios.get(`${api.detection.securityEvent.domainDetail}?resource=${this.detection.domain}`).then(res => {
if (res.status === 200) {
this.basicInfo.domainInfo = res.data.data
}
})
}
if (this.detection.app) {
axios.get(`${api.detection.securityEvent.appDetail}?resource=${this.detection.app}`).then(res => {
if (res.status === 200) {
this.basicInfo.appInfo = res.data.data
}
})
}
if (this.detection.ruleId) {
axios.get(`${api.detection.detail}/${this.detection.ruleId}`).then(res => {
if (res.status === 200) {
this.basicInfo.ruleDescription = res.data.data.description
}
})
}
query () {
Promise.all([this.queryBasic(), this.queryEvent()]).then((responses) => {
responses[0].malwareTechniques = responses[0].malwareTechniques.length > 2 ? responses[0].malwareTechniques.replace('[', '').replace(']', '').split(',', 5).join(', ') : ''
responses[0].malwareGroups = responses[0].malwareGroups.length > 2 ? responses[0].malwareGroups.replace('[', '').replace(']', '').split(',', 5).join(', ') : ''
responses[0].malwarePlatforms = responses[0].malwarePlatforms.length > 1 ? responses[0].malwarePlatforms : ''
responses[0].malwareDescription = responses[0].malwareDescription.length > 1 ? responses[0].malwareDescription : ''
responses[0] && (this.basicInfo = responses[0])
responses[1] &&
(this.events = responses[1].sort(
(e1, e2) => e1.startTime - e2.startTime
))
})
},
queryBasic () {
return new Promise((resolve, reject) => {
try {
axios.get(api.detection.securityEvent.overviewBasic, {
params: {
eventId: this.detection.eventId,
startTime: this.detection.startTime,
endTime: this.detection.endTime
}
}).then((response) => {
if (response.status === 200) {
resolve(response.data.data.result[0])
} else {
reject(response.data)
}
})
} catch (e) {
reject(e)
}
})
},
queryEvent () {
axios.get(api.detection.securityEvent.relationEvent, {
params: {
// startTime: this.detection.startTime,
unbiasedTime: this.detection.startTime,
offenderIp: this.detection.offenderIp,
victimIp: this.detection.victimIp,
biasSecond: 3600
}
}).then((response) => {
if (response.status === 200) {
this.events = response.data.data.result.sort((e1, e2) => e1.startTime - e2.startTime)
} else {
this.events = []
return new Promise((resolve, reject) => {
try {
axios.get(api.detection.securityEvent.overviewEvent, {
params: {
startTime: this.detection.startTime,
offenderIp: this.detection.offenderIp,
victimIp: this.detection.victimIp
}
}).then((response) => {
if (response.status === 200) {
resolve(response.data.data.result)
} else {
reject(response.data)
}
})
} catch (e) {
reject(e)
}
})
},
@@ -502,18 +391,7 @@ export default {
}
},
mounted () {
this.initEntityDetail()
this.queryEvent()
this.query()
}
}
</script>
<style scoped>
.row__label {
width: 176px;
}
.row__content {
width: calc(100% - 176px);
padding-right: 50px;
}
</style>

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'
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.status === 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,143 @@
<template>
<div>
<div class="new-detection-filter-title">
{{ $t('detections.filters') }}
</div>
<div class="new-detection-filter-content">
<div>
<div class="new-filter-content-title">{{ $t('overall.status') }}</div>
<div class="new-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="new-filter-content-checkbox" :label="item.status">
<div>{{ item.label }}</div>
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div>
<div class="new-filter-content-title">{{ $t('overall.category') }}</div>
<div class="new-filter-content-content">
<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">
{{ item.label }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div>
<div class="new-filter-content-title">{{ $t('overall.type') }}</div>
<div class="new-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="new-filter-content-checkbox" :label="item.value">
{{ item.label }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
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 }
}
axios.get(this.url, { params: searchParams }).then(response => {
if (response.status === 200) {
if (response.data.data.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 (response.data.data.categoryList) {
response.data.data.categoryList.forEach(item => {
this.checkCategory.push(item.value)
})
}
this.typeList = response.data.data.typeList
if (response.data.data.typeList) {
response.data.data.typeList.forEach(item => {
this.checkType.push(item.value)
})
}
} else {
console.error(response.data)
}
}).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>

View File

@@ -0,0 +1,250 @@
<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 { api } from '@/utils/api'
import axios from 'axios'
import _ from 'lodash'
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 () {
axios.get(api.detection.statistics, { pageSize: -1 }).then(response => {
if (response.status === 200) {
this.intervalList = _.get(response, 'data.data.intervalList', [])
} else {
console.error(response.data)
}
}).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 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.status === 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

@@ -50,12 +50,9 @@
</template>
<template v-else-if="item.prop === 'status'">
<div :class="`detection-tag-status${scope.row[item.prop]}`">
{{ $t(switchStatus(scope.row[item.prop])) }}
{{ switchStatus(scope.row[item.prop]) }}
</div>
</template>
<template v-else-if="item.prop === 'category'">
{{ changeCategory(scope.row[item.prop]) }}
</template>
<template v-else-if="item.prop === 'description'">
<div style="padding-right: 20px">{{ scope.row[item.prop] }}</div>
</template>
@@ -80,8 +77,6 @@
import table from '@/mixins/table'
import { dateFormatByAppearance } from '@/utils/date-util'
import { switchStatus } from '@/utils/tools'
import _ from 'lodash'
import { detectionUnitList } from '@/utils/constants'
export default {
name: 'DetectionTable',
@@ -132,13 +127,15 @@ export default {
show: true
},
{
label: this.$t('detection.create.dimensions'),
// label: this.$t('config.user.createTime'),
label: 'Dimensions',
prop: 'dimensions',
minWidth: 204,
show: true
},
{
label: this.$t('detection.library'),
// label: this.$t('config.user.createTime'),
label: 'Library',
prop: 'library',
minWidth: 204,
show: true
@@ -154,9 +151,9 @@ export default {
if (n) {
n.forEach(t => {
if (t.ruleType === 'indicator_match') {
t.library = _.get(t, 'ruleConfigObj.knowledgeBase.name', '-')
t.library = t.ruleConfig.knowledge.name
} else if (t.ruleType === 'threshold') {
t.dimensions = _.get(t, 'ruleConfigObj.dimensions', '-')
t.dimensions = t.ruleConfig.dimensions
}
})
}
@@ -168,16 +165,6 @@ export default {
switchStatus,
rowDoubleClick (data) {
this.$emit('rowDoubleClick', data)
},
changeCategory (value) {
if (value) {
const obj = detectionUnitList.categoryList.find(d => d.value === value)
let label = value
if (obj) {
label = this.$t(obj.label)
}
return label
}
}
}
}

View File

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

View File

@@ -1,24 +1,22 @@
<template>
<div class="detection">
<div class="detection-title">
<span>{{ $t('overall.policies') }}</span>
<span>{{ $t('overall.detections') }}</span>
<span class="detection-title-label">
{{ $t('detection.policesCreated', { total: policyTotal }) }} | {{ $t('detection.policesEnabled', { enabled: policyEnabledNum }) }}
60 polices created(200 max) | 32 polices enabled(100 max)
</span>
</div>
<div class="detection-content">
<div class="detection-filter">
<detection-filter @filterParams="getFilterParams" @policyTotal="getPolicyTotal" />
<detection-filter></detection-filter>
</div>
<div class="detection-block">
<detection-tools
@delete="toDelete"
@create="onCreate"
@edit="onEdit"
@search="onSearch"
:disableEdit="disableEdit"
:disableDelete="disableDelete"/>
<div class="detection-table" style="position: relative">
@@ -34,6 +32,7 @@
:all-count="18"
@selectionChange="selectionChange"
@reload="reloadRowList"
@toggleLoading="toggleLoading"
@rowDoubleClick="onRowDoubleClick"
></detection-table>
</div>
@@ -62,8 +61,10 @@
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="150"></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">
@@ -82,13 +83,12 @@
</template>
<script>
import DetectionFilter from '@/views/detections/detectionPolicies/PolicyFilter'
import DetectionTools from '@/views/detections/detectionPolicies/PolicyTools'
import DetectionTable from '@/views/detections/detectionPolicies/PolicyTable'
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/detections/detectionPolicies/PolicyDrawer'
import axios from 'axios'
import DetectionDrawer from '@/views/detectionsNew/DetectionDrawer'
export default {
name: 'Index',
@@ -101,8 +101,8 @@ export default {
mixins: [dataListMixin],
data () {
return {
url: api.detection.list,
// url: api.knowledgeBase,
// url: api.detection.list,
url: api.knowledgeBase,
listUrl: api.detection.list,
tableId: 'detectionTable',
isNoData: false,
@@ -110,52 +110,42 @@ export default {
isSelectedStatus: false,
batchDeleteObjs: [], //
secondBatchDeleteObjs: [],
disableEdit: true,
disableDelete: true,
showConfirmDialog: false,
delItemList: [],
showDrawer: false,
drawerInfo: {},
filterParams: {},
policyTotal: 0,
policyEnabledNum: 0
drawerInfo: {}
}
},
methods: {
onSearch (keyWord) {
this.filterParams = {
...this.filterParams,
name: keyWord
}
this.search(this.filterParams, 'detection')
onSearch () {
// todo
// const params = {
// ...this.filterParams,
// name: this.keyWord
// }
// this.clearList()
// this.search(params)
// this.$refs.knowledgeFilter.reloadFilter(this.checkedCategoryIds, this.checkedStatusIds)
},
toDelete (data) {
if (data && data.ruleId) {
this.secondBatchDeleteObjs = []
this.batchDeleteObjs = []
this.secondBatchDeleteObjs.push(data)
this.batchDeleteObjs.push(data)
}
this.showDelDialog()
// todo
// if (data && data.ruleId) {
// this.secondBatchDeleteObjs = []
// this.batchDeleteObjs = []
// this.secondBatchDeleteObjs.push(data)
// this.batchDeleteObjs.push(data)
// }
// this.showDelDialog()
},
onCreate () {
this.$router.push({
path: '/detectionPolicy/create',
query: {
t: +new Date()
}
})
},
onEdit () {
const pageNo = this.$router.currentRoute.value.query.pageNo
this.$router.push({
path: '/detectionPolicy/edit',
query: {
t: +new Date(),
pageNoForTable: pageNo || 1,
id: this.batchDeleteObjs[0].ruleId
}
})
// todo
// this.$router.push({
// path: '/detection/policies/create',
// query: {
// t: +new Date()
// }
// })
},
selectionChange (objs) {
this.batchDeleteObjs = []
@@ -171,6 +161,8 @@ export default {
reloadRowList () {
this.getTableData()
},
toggleLoading () {
},
showDelDialog () {
this.showConfirmDialog = true
this.$nextTick(() => {
@@ -185,6 +177,16 @@ export default {
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
@@ -203,64 +205,40 @@ export default {
}).catch(() => {
})
} else {
this.toggleLoading(true)
axios.delete(api.detection.delete + '?ruleIds=' + ids).then(response => {
if (response.status === 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.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) {
this.isSelectedStatus = false
this.disableDelete = true
this.secondBatchDeleteObjs = []
this.batchDeleteObjs = []
this.showConfirmDialog = false
}
})
// todo
// this.toggleLoading(true)
// axios.delete(api.detection.delete + '?ruleIds=' + ids).then(response => {
// if (response.status === 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) {
this.showDrawer = true
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
// todo
// this.showDrawer = true
// this.drawerInfo = data
}
}
}

View File

@@ -17,7 +17,7 @@
@search="search"
></explorer-search>
<!-- 内容区 -->
<div v-if="showList" style="display: flex;flex-direction: row;padding-bottom: 20px;">
<div v-if="showList" style="display: flex;flex-direction: row;">
<entity-filter
:filter-data="newFilterData"
:loading-left="loadingLeft"
@@ -71,8 +71,7 @@
<div class="right-label">{{ $t('network.total') }}</div>
<div class="right-label-loading">
<loading :loading="loadingApp" size="small"></loading>
<!-- <div class="right-value">{{ numberWithCommas(entityAppTotal) }}</div>-->
<div class="right-value">837</div>
<div class="right-value">{{ numberWithCommas(entityAppTotal) }}</div>
</div>
</div>
@@ -102,8 +101,7 @@
<div class="right-label">{{ $t('network.total') }}</div>
<div class="right-label-loading">
<loading :loading="loadingDomain" size="small"></loading>
<!-- <div class="right-value">{{ numberWithCommas(entityDomainTotal) }}</div>-->
<div class="right-value">1,032,544</div>
<div class="right-value">{{ numberWithCommas(entityDomainTotal) }}</div>
</div>
</div>
@@ -133,8 +131,7 @@
<div class="right-label">{{ $t('network.total') }}</div>
<div class="right-label-loading">
<loading :loading="loadingIp" size="small"></loading>
<!-- <div class="right-value">{{ numberWithCommas(entityIpTotal) }}</div>-->
<div class="right-value">1,900,804</div>
<div class="right-value">{{ numberWithCommas(entityIpTotal) }}</div>
</div>
</div>
@@ -188,7 +185,6 @@ export default {
},
data () {
return {
showList: false,
listMode: 'list', // entity列表的模式list|block
entityAppTotal: '-',
@@ -216,28 +212,36 @@ export default {
title: this.$t('entity.topCountries'),
topColumn: 'ip.country',
totalCount: 0,
data: []
data: [],
loading: false,
firstLoad: true
},
{
icon: 'cn-icon cn-icon-city',
title: this.$t('entity.topCities'),
topColumn: 'ip.city',
totalCount: 0,
data: []
data: [],
loading: false,
firstLoad: true
},
{
icon: 'cn-icon cn-icon-as',
title: this.$t('entity.topASNs'),
topColumn: 'ip.asn',
totalCount: 0,
data: []
data: [],
loading: false,
firstLoad: true
},
{
icon: 'cn-icon cn-icon-operator',
title: this.$t('entity.topISPs'),
topColumn: 'ip.isp',
totalCount: 0,
data: []
data: [],
loading: false,
firstLoad: true
},
{
icon: 'cn-icon cn-icon-open-port',
@@ -245,28 +249,36 @@ export default {
topColumn: 'ip.port',
topColumn1: 'ip.protocol',
totalCount: 0,
data: []
data: [],
loading: false,
firstLoad: true
},
{
icon: 'cn-icon cn-icon-FQDN',
title: this.$t('entity.topFQDNCategories'),
topColumn: 'domain.category',
totalCount: 0,
data: []
data: [],
loading: false,
firstLoad: true
},
{
icon: 'cn-icon cn-icon-category2',
title: this.$t('entity.topAppCategories'),
topColumn: 'app.category',
totalCount: 0,
data: []
data: [],
loading: false,
firstLoad: true
},
{
icon: 'cn-icon cn-icon-tag1',
title: this.$t('entity.topTags'),
topColumn: 'tag',
totalCount: 0,
data: []
data: [],
loading: false,
firstLoad: true
}
],
listData: [],
@@ -399,16 +411,17 @@ export default {
mode: mode,
range: this.timeFilter.dateRangeValue,
pageNo: this.pageObj.pageNo,
pageSize: this.pageObj.pageSize
pageSize: this.pageObj.pageSize,
showList: true
}
})
this.showList = true
// this.showList = true
// 跳转页面,则不执行搜索功能
return true
}
this.queryFilterNew({ q: this.q, ...this.pageObj, ...this.timeFilter })
this.queryList({ q: this.q, ...this.pageObj, ...this.timeFilter })
this.queryFilterNew({ q: this.q, ...this.pageObj, ...this.timeFilter })
this.queryCount({ q: this.q, ...this.pageObj, ...this.timeFilter })
// 延时一秒避免初始化时pageSize为20pageNo为1也会调用“搜索”的情况
@@ -492,8 +505,8 @@ export default {
/** 新版查询filter数据 */
queryFilterNew (params) {
const queryParams = {
// startTime: getSecond(params.startTime),
// endTime: getSecond(params.endTime),
startTime: getSecond(params.startTime),
endTime: getSecond(params.endTime),
resource: params.q || ''
}
this.loadingLeft = true
@@ -505,8 +518,48 @@ export default {
const aggDomain = axios.get(api.entity.entityList.aggDomain, { params: queryParams })
const aggAppCategory = axios.get(api.entity.entityList.aggAppCategory, { params: queryParams })
const aggTag = axios.get(api.entity.entityList.aggTag, { params: queryParams })
Promise.all([aggCountry, aggCity, aggIPAsn, aggIPIsp, aggPort, aggDomain, aggAppCategory, aggTag]).then(response => {
const requests = [aggCountry, aggCity, aggIPAsn, aggIPIsp, aggPort, aggDomain, aggAppCategory, aggTag]
requests.forEach((req, index) => {
this.newFilterData[index].loading = true
req.then(response => {
if (response.status === 200 && response.data.data.list) {
if (response.data.data.list.length >= 5) {
this.newFilterData[index].showNum = 5
} else {
this.newFilterData[index].showNum = response.data.data.list.length
}
this.newFilterData[index].data = []
response.data.data.list.forEach((item, i) => {
let obj = {
label: item.value,
topColumn: this.newFilterData[index].topColumn,
value: item.uniqueEntities,
showNum: 5
}
if (index === 0) {
obj.flag = item.value // 接口字段名称为'China'svg名称为'CN'通过countryNameIdMapping进行转换
}
if (index === 4) {
obj = {
topColumn: this.newFilterData[index].topColumn,
topColumn1: this.newFilterData[index].topColumn1,
port: item.port,
l7Protocol: item.l7Protocol,
value: item.uniqueEntities,
showNum: 5
}
}
this.newFilterData[index].data.push(obj)
})
}
}).catch(e => {
this.$message.error(e.response.data.message)
}).finally(() => {
this.newFilterData[index].loading = false
this.newFilterData[index].firstLoad = false
})
})
/*Promise.all([aggCountry, aggCity, aggIPAsn, aggIPIsp, aggPort, aggDomain, aggAppCategory, aggTag]).then(response => {
response.forEach((item1, index) => {
if (item1.status === 200 && item1.data.data.list) {
if (item1.data.data.list.length >= 5) {
@@ -543,7 +596,7 @@ export default {
this.$message.error(e.response.data.message)
}).finally(() => {
this.loadingLeft = false
})
})*/
},
/** 实体列表查询 */
queryList (params) {
@@ -551,8 +604,8 @@ export default {
const queryParams = {
pageSize: params.pageSize,
pageNo: params.pageNo,
// startTime: getSecond(params.startTime),
// endTime: getSecond(params.endTime),
startTime: getSecond(params.startTime),
endTime: getSecond(params.endTime),
resource: params.q || ''
}
axios.get(api.entity.entityList.list, { params: queryParams }).then(response => {
@@ -573,8 +626,8 @@ export default {
queryCount (params) {
this.loadingCount = true
const queryParams = {
// startTime: getSecond(params.startTime),
// endTime: getSecond(params.endTime),
startTime: getSecond(params.startTime),
endTime: getSecond(params.endTime),
resource: params.q || ''
}
axios.get(api.entity.entityList.summaryCount, { params: queryParams }).then(response => {
@@ -692,67 +745,28 @@ export default {
} else {
this.search({ q: '', str: '', metaList: [] })
}
},
queryScoreBase () {
const { startTime, endTime } = getNowTime(60 * 24)
const params = {
startTime: getSecond(startTime),
endTime: getSecond(endTime)
}
const tcp = axios.get(api.npm.overview.tcpSessionDelay, { params: params })
const http = axios.get(api.npm.overview.httpResponseDelay, { params: params })
const ssl = axios.get(api.npm.overview.sslConDelay, { params: params })
const tcpPercent = axios.get(api.npm.overview.tcpLostlenPercent, { params: params })
const packetPercent = axios.get(api.npm.overview.packetRetransPercent, { params: params })
Promise.all([tcp, http, ssl, tcpPercent, packetPercent]).then(res => {
const scoreBase = {}
res.forEach((t, i) => {
if (t.status === 200) {
if (i === 0) {
scoreBase.establishLatencyMsP10 = _.get(t.data, 'data.result.establishLatencyMsP10', null)
scoreBase.establishLatencyMsP90 = _.get(t.data, 'data.result.establishLatencyMsP90', null)
} else if (i === 1) {
scoreBase.httpResponseLatencyP10 = _.get(t.data, 'data.result.httpResponseLatencyP10', null)
scoreBase.httpResponseLatencyP90 = _.get(t.data, 'data.result.httpResponseLatencyP90', null)
} else if (i === 2) {
scoreBase.sslConLatencyP10 = _.get(t.data, 'data.result.sslConLatencyP10', null)
scoreBase.sslConLatencyP90 = _.get(t.data, 'data.result.sslConLatencyP90', null)
} else if (i === 3) {
scoreBase.tcpLostlenPercentP10 = _.get(t.data, 'data.result.tcpLostlenPercentP10', null)
scoreBase.tcpLostlenPercentP90 = _.get(t.data, 'data.result.tcpLostlenPercentP90', null)
} else if (i === 4) {
scoreBase.pktRetransPercentP10 = _.get(t.data, 'data.result.pktRetransPercentP10', null)
scoreBase.pktRetransPercentP90 = _.get(t.data, 'data.result.pktRetransPercentP90', null)
}
}
})
this.$store.commit('setScoreBase', scoreBase)
}).catch((e) => {
}).finally(() => {
})
}
},
mounted () {
this.getEntityIndexData()
let { q, listMode } = this.$route.query
if (!this.showList) {
this.getEntityIndexData()
} else {
let { q, listMode } = this.$route.query
// 如果地址栏有listMode即列表页并非首页则开始搜索
if (listMode) {
this.showList = true
// %位置为0是输入中文时能解码%2025%分别是空格和%的情况
if (q && (q.indexOf('%') === 0 || q.indexOf('%20') > -1 || q.indexOf('%25') > -1)) {
q = decodeURI(q)
// 如果地址栏有listMode即列表页并非首页则开始搜索
if (listMode) {
// %位置为0是输入中文时能解码%2025%分别是空格和%的情况
if (q && (q.indexOf('%') === 0 || q.indexOf('%20') > -1 || q.indexOf('%25') > -1)) {
q = decodeURI(q)
}
// %位置不为0即内容包含非英文时
const str1 = q.substring(q.indexOf('%'), q.indexOf('%') + 3)
if (q && q.indexOf('%') > 0 && (str1 !== '%20' || str1 === '%25')) {
q = decodeURI(q)
}
this.initSearch(q)
this.listMode = listMode
}
// %位置不为0即内容包含非英文时
const str1 = q.substring(q.indexOf('%'), q.indexOf('%') + 3)
if (q && q.indexOf('%') > 0 && (str1 !== '%20' || str1 === '%25')) {
q = decodeURI(q)
}
this.initSearch(q)
this.listMode = listMode
// 查询评分基准
this.$store.commit('resetScoreBase')
this.queryScoreBase()
}
},
watch: {
@@ -766,11 +780,12 @@ export default {
const rangeParam = query.range
const startTimeParam = query.startTime
const endTimeParam = query.endTime
const showList = ref(Boolean(query.showList))
// 若url携带了使用携带的值否则使用默认值。
const dateRangeValue = rangeParam ? parseInt(query.range) : 60
const dateRangeValue = rangeParam ? parseInt(query.range) : 60 * 24
const timeFilter = ref({ dateRangeValue })
if (!startTimeParam || !endTimeParam) {
const { startTime, endTime } = getNowTime(60)
const { startTime, endTime } = getNowTime(60 * 24)
timeFilter.value.startTime = startTime
timeFilter.value.endTime = endTime
} else {
@@ -781,12 +796,13 @@ export default {
pageNo: query.pageNo ? parseInt(query.pageNo) : 1,
// 是否重置pageNo在执行新搜索时是true
resetPageNo: true,
pageSize: query.pageSize ? parseInt(query.pageSize) : defaultPageSize,
pageSize: query.pageSize ? parseInt(query.pageSize) : 10, // TODO 23-10-14 默认暂时改为10
total: 0
})
return {
timeFilter,
pageObj
pageObj,
showList
}
},
beforeUnmount () {

View File

@@ -2,51 +2,49 @@
<div class="entity-filter-case" style="position: relative">
<div class="filter-case__header">{{ $t('entities.filter1') }}</div>
<div v-if="filterDataLength>0">
<div v-if="filterDataLength > 0">
<div class="entity-filter" v-for="(item, index) in myFilterData" :key="index">
<div v-if="item.data.length>0">
<div class="filter__header">
<i :class="item.icon"></i>
{{ item.title }}
</div>
<div class="filter__body" style="position: relative">
<loading :loading="loadingLeft" style="top: -5px;"></loading>
<div class="filter__body-item"
v-for="(data, i) in item.data.slice(0, item.showNum)"
:key="i"
@click="filter(data.label, data)">
<div class="filter__body-item-left">
<div v-if="data.flag">
<img v-if="data.flag===countryNameIdMapping.Unknown || !countryNameIdMapping[data.flag]" src="../../../public/images/flag/Unknown.svg" class="filter-country-flag">
<img v-else :src="require(`../../../public/images/flag/${countryNameIdMapping[data.flag]}.png`)" class="filter-country-flag"/>
</div>
<div v-else class="filter__body-item-left-index">{{ i+1 }}</div>
<div class="filter__body-item-left-label">
<el-tooltip :content="data.label" placement="top" effect="light" :disabled="disabledLabel">
<span @mouseenter="handleMouse(`filter${index}${i}`)" :id="`filter${index}${i}`">
<span v-if="item.topColumn==='ip.port'">
{{ data.port }}/{{ data.l7Protocol }}
</span>
<span v-else>{{ data.label }}</span>
</span>
</el-tooltip>
</div>
</div>
<div class="filter__body-item-right">{{ data.value }}</div>
</div>
</div>
<div @click="showMoreFilter(item, index)"
:class="item.showNum === item.data.length ? 'filter-no-show-more' : 'filter-show-more'">
{{ $t('overall.showMore') }}
</div>
<div class="filter-hr"></div>
<div class="filter__header">
<i :class="item.icon"></i>
{{ item.title }}
</div>
<div class="filter__body" style="position: relative">
<loading :loading="item.loading" style="top: -5px;"></loading>
<div class="filter__body-item"
v-for="(data, i) in item.data.slice(0, item.showNum)"
:key="i"
@click="filter(data.label, data)">
<div class="filter__body-item-left">
<div v-if="data.flag">
<img v-if="data.flag===countryNameIdMapping.Unknown || !countryNameIdMapping[data.flag]" src="../../../public/images/flag/Unknown.svg" class="filter-country-flag">
<img v-else :src="require(`../../../public/images/flag/${countryNameIdMapping[data.flag]}.png`)" class="filter-country-flag"/>
</div>
<div v-else class="filter__body-item-left-index">{{ i+1 }}</div>
<div class="filter__body-item-left-label">
<el-tooltip :content="data.label" placement="top" effect="light" :disabled="disabledLabel">
<span @mouseenter="handleMouse(`filter${index}${i}`)" :id="`filter${index}${i}`">
<span v-if="item.topColumn==='ip.port'">
{{ data.port }}/{{ data.l7Protocol }}
</span>
<span v-else>{{ data.label }}</span>
</span>
</el-tooltip>
</div>
</div>
<div class="filter__body-item-right">{{ data.value }}</div>
</div>
</div>
<div @click="showMoreFilter(item, index)"
:class="item.showNum >= item.data.length || item.data.length <= 5 ? 'filter-no-show-more' : 'filter-show-more'">
{{ $t('entity.showMore') }}
</div>
<div class="filter-hr"></div>
</div>
</div>
<loading v-else-if="isFirstLoad" :loading="isFirstLoad"></loading>
<chart-no-data v-else style="padding-top: 40px"></chart-no-data>
</div>
</template>
@@ -54,6 +52,7 @@
import Loading from '@/components/common/Loading'
import ChartNoData from '@/views/charts/charts/ChartNoData'
import { countryNameIdMapping } from '@/utils/constants'
import _ from 'lodash'
export default {
name: 'EntityFilter',
components: { ChartNoData, Loading },
@@ -73,6 +72,9 @@ export default {
})
return length
},
isFirstLoad () {
return this.myFilterData.some(d => d.firstLoad)
}
},
data () {
@@ -85,6 +87,14 @@ export default {
mounted () {
this.myFilterData = this.filterData
},
watch: {
filterData: {
deep: true,
handler (n) {
this.myFilterData = _.cloneDeep(n)
}
}
},
methods: {
/**
* 判断文字是否溢出超出则鼠标移入tooltip显示否则鼠标移入不显示
@@ -103,17 +113,14 @@ export default {
this.$emit('filter', name, data)
},
showMoreFilter (item, index) {
if ((item.data.length - item.showNum) >= 5) {
this.myFilterData[index].showNum = item.data.length
/*if ((item.data.length - item.showNum) >= 5) {
item.shouNum += 5
this.myFilterData[index].showNum += 5
} else {
this.myFilterData[index].showNum += (item.data.length - item.showNum)
}
}*/
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -678,6 +678,7 @@ export default {
// 手动高亮listNode
const _listNode = _this.graph.findById(listNode.id)
_this.graph.emit('node:click', { item: _listNode, target: _listNode.getKeyShape() })
console.info(_this.stackData)
if (_this.stackData.justUndo) {
_this.stackData.justUndo = false
_this.stackData.redo = []

View File

@@ -86,7 +86,7 @@
<div class="entity-detail graph-basic-info__block-content">
<div class="graph-tag-list">
<div v-for="ic in $_.get(node, 'myData.tags', [])" :key="ic.value">
<div class="entity-tag graph-tag-item" :class="`entity-tag--level-two-${ic.type}`" :style="getTagColor(ic.color)">
<div class="entity-tag graph-tag-item" :class="`entity-tag--level-two-${ic.type}`">
<span>{{ic.value}}</span>
</div>
</div>
@@ -96,7 +96,7 @@
</template>
<script>
import { copySelectionText, selectElementText, getTagColor } from '@/utils/tools'
import { copySelectionText, selectElementText } from '@/utils/tools'
import { entityType, riskLevelMapping } from '@/utils/constants'
import chartMixin from '@/views/charts2/chart-mixin'
import { dateFormatByAppearance } from '@/utils/date-util'
@@ -174,7 +174,6 @@ export default {
}
},
methods: {
getTagColor,
/** 复制实体名称 */
copyEntityName () {
selectElementText(document.getElementById('entityName'))

View File

@@ -2,7 +2,7 @@ import _ from 'lodash'
import i18n from '@/i18n'
import axios from 'axios'
import { api } from '@/utils/api'
import { entityDefaultColor, entityDetailTags, tagValueLabelMapping } from '@/utils/constants'
import { entityDetailTags, psiphon3IpType } from '@/utils/constants'
export default class Node {
/*
@@ -91,7 +91,7 @@ export default class Node {
}
})
if (_.isArray(tags.userDefinedTags)) {
_tags = _.concat(_tags, tags.userDefinedTags.map(tag => ({ value: tag.tagValue, color: tag.knowledgeBase ? tag.knowledgeBase.color : entityDefaultColor })))
_tags = _.concat(_tags, tags.userDefinedTags.map(tag => ({ value: tag.tagValue, type: 'normal' })))
}
this.myData.tags = _tags
@@ -145,7 +145,7 @@ export default class Node {
tagValueHandler (k, k2, value) {
if (k === 'psiphon3Ip') {
if (k2 === 'type') {
const find = tagValueLabelMapping.find(t => t.value === value)
const find = psiphon3IpType.find(t => t.value === value)
if (find) {
return find.name
}

View File

@@ -38,11 +38,13 @@
<Pagination
ref="pagination"
:page-obj="pageObj"
:post-page-sizes="[10, 20, 50]"
@pageNo='pageNo'
@pageSize='pageSize'
@size-change="pageSize"
@prev-click="prev"
@next-click="next"
@scrollbarToTop="scrollbarToTop"
>
</Pagination>
</div>
@@ -124,6 +126,10 @@ export default {
const container = document.getElementById('cnContainer')
container.scrollTop += e.deltaY / 2
}
},
scrollbarToTop () {
const container = document.getElementById('cnContainer')
container.scrollTop = 0
}
},
mounted () {

View File

@@ -13,11 +13,7 @@
<div class="cn-entity__header" style="display: flex;">
<span class="cn-entity__header-title">{{ entityData.entityValue || 'Unknown' }}</span>
<span class="entity-detail" style="display: flex;margin-left: 6px;margin-top: 1px;flex-wrap: wrap;margin-bottom: -10px;">
<span v-for="(item, index) in levelTwoTags"
:key="index"
class="entity-tag entity-tag--small margin-r-10 margin-b-10"
:class="`entity-tag--level-two-${item.type}`"
:style="getTagColor(item.color)">
<span v-for="(item, index) in levelTwoTags" :key="index" class="entity-tag entity-tag--small margin-r-10 margin-b-10" :class="`entity-tag--level-two-${item.type}`">
{{ item.value }}
</span>
</span>
@@ -143,10 +139,7 @@
<div class="row-item-label">
<span class="row-item-label">{{ $t('network.score') }}&nbsp;:&nbsp;&nbsp;</span>
<span class="row-item-value" style="position: relative;">
<template v-if="!loadingNetworkQuality && score !=='-'">
<span v-for="(dot, i) in scoreDot" :key="i" :class="dot.class"></span>
</template>
<span>{{score}}</span>
<span v-if="!loadingNetworkQuality">{{ score }}</span>
<loading :loading="loadingNetworkQuality" size="small"></loading>
</span>
</div>
@@ -190,9 +183,8 @@ import relatedServer from '@/mixins/relatedServer'
import Loading from '@/components/common/Loading'
import axios from 'axios'
import { api } from '@/utils/api'
import { entityDefaultColor, entityDetailTags, tagValueLabelMapping } from '@/utils/constants'
import { entityDetailTags, psiphon3IpType } from '@/utils/constants'
import _ from 'lodash'
import { getTagColor } from '@/utils/tools'
export default {
name: 'Row',
@@ -248,7 +240,6 @@ export default {
})
},
methods: {
getTagColor,
initData () {
let url = ''
switch (this.entity.entityType) {
@@ -295,21 +286,28 @@ export default {
Object.keys(res.data[k]).forEach(k2 => {
const find = entityDetailTags[this.entity.entityType].find(t => t.name === k2)
if (find) {
this.levelTwoTags.push({ key: k2, value: this.tagValueHandler(res.data[k][k2]), type: find.type })
this.levelTwoTags.push({ key: k2, value: this.tagValueHandler(k, k2, res.data[k][k2]), type: find.type })
}
})
}
})
if (_.isArray(res.data.userDefinedTags)) {
this.levelTwoTags = _.concat(this.levelTwoTags, res.data.userDefinedTags.map(tag => ({ value: tag.tagValue, color: tag.knowledgeBase ? tag.knowledgeBase.color : entityDefaultColor })))
this.levelTwoTags = _.concat(this.levelTwoTags, res.data.userDefinedTags.map(tag => ({ value: tag.tagValue, type: 'normal' })))
}
this.hideTagArea = _.isEmpty(this.levelTwoTags)
}
})
},
tagValueHandler (value) {
const find = tagValueLabelMapping.find(t => t.value === value)
return find ? find.name : value
tagValueHandler (k, k2, value) {
if (k === 'psiphon3Ip') {
if (k2 === 'type') {
const find = psiphon3IpType.find(t => t.value === value)
if (find) {
return find.name
}
}
}
return value
},
/* 切换折叠状态 */
switchCollapse () {

View File

@@ -67,11 +67,14 @@
<div class="overview__row">
<div class="row__label row__label--width130">{{$t('entities.networkQualityRating')}}</div>
<div style="position: relative;">
<div class="entity-score" v-if="!loadingNetworkQuality && score !=='-'">
<span v-for="(dot, i) in scoreDot" :key="i" :class="dot.class"></span>
<span style="padding-left: 4px;">{{score}}</span>
<div class="entity-score" v-if="!loadingNetworkQuality">
<div v-if="score !== '-'">
<div class="circle-icon" v-if="score <= 2 || score === '-'" :class="{'data-score-red': score <= 2 || score === '-'}" ></div>
<div class="circle-icon" v-else-if="score <= 4" :class="{'data-score-yellow': score <= 4}" ></div>
<div class="circle-icon" v-else-if="score <= 6" :class="{'data-score-green': score <= 6}" ></div>
</div>
{{score}}
</div>
<div class="entity-score" v-else>{{score}}</div>
<loading :loading="loadingNetworkQuality" size="small" style="left: 1rem;width: 50%;"></loading>
</div>
</div>

View File

@@ -75,11 +75,14 @@
<div class="overview__row">
<div class="row__label row__label--width130">{{$t('entities.networkQualityRating')}}</div>
<div style="position: relative;">
<div class="entity-score" v-if="!loadingNetworkQuality && score !=='-'">
<span v-for="(dot, i) in scoreDot" :key="i" :class="dot.class"></span>
<span style="padding-left: 4px;">{{score}}</span>
<div class="entity-score" v-if="!loadingNetworkQuality">
<div v-if="score !== '-'">
<div class="circle-icon" v-if="score <= 2 || score === '-'" :class="{'data-score-red': score <= 2 || score === '-'}" ></div>
<div class="circle-icon" v-else-if="score <= 4" :class="{'data-score-yellow': score <= 4}" ></div>
<div class="circle-icon" v-else-if="score <= 6" :class="{'data-score-green': score <= 6}" ></div>
</div>
{{score}}
</div>
<div class="entity-score" v-else>{{score}}</div>
<loading :loading="loadingNetworkQuality" size="small" style="left: 1rem;width: 50%;"></loading>
</div>
</div>

View File

@@ -106,11 +106,14 @@
<div class="overview__row">
<div class="row__label row__label--width130">{{$t('entities.networkQualityRating')}}</div>
<div style="position: relative;">
<div class="entity-score" v-if="!loadingNetworkQuality && score !=='-'">
<span v-for="(dot, i) in scoreDot" :key="i" :class="dot.class"></span>
<span style="padding-left: 4px;">{{score}}</span>
<div class="entity-score" v-if="!loadingNetworkQuality">
<div v-if="score !== '-'">
<div class="circle-icon" v-if="score <= 2 || score === '-'" :class="{'data-score-red': score <= 2 || score === '-'}" ></div>
<div class="circle-icon" v-else-if="score <= 4" :class="{'data-score-yellow': score <= 4}" ></div>
<div class="circle-icon" v-else-if="score <= 6" :class="{'data-score-green': score <= 6}" ></div>
</div>
{{score}}
</div>
<div class="entity-score" v-else>{{score}}</div>
<loading :loading="loadingNetworkQuality" size="small" style="left: 1rem;width: 50%;"></loading>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More