Compare commits

...

121 Commits

Author SHA1 Message Date
刘洪洪
374c66fcff fix: 还原之前版本 2024-01-02 11:05:01 +08:00
刘洪洪
705572b245 fix: 修复实体、检测界面因路由路径问题不能打开,以及实体详情界面部分问题 2024-01-02 10:42:54 +08:00
chenjinsong
0152c46d05 fix: 修复知识库更新记录的时间丢失时区的问题 2023-10-31 19:39:54 +08:00
chenjinsong
64f376e22a CN-1445 fix: 修复切换不同知识库的更新页面后,psiphon3的柱状图不能显示的问题 2023-10-31 18:46:02 +08:00
chenjinsong
5b89fca77c CN-1404 fix: 去掉其他知识库的update按钮 2023-10-31 18:05:36 +08:00
陈劲松
d1f5997b88 Merge branch 'cherry-pick-f151415d' into 'dev-23.10'
fix: detection--policy的trigger去除秒时间选项

See merge request cyber-narrator/cn-ui!54
2023-10-31 09:25:41 +00:00
刘洪洪
88002a8fc4 fix: detection--policy的trigger去除秒时间选项
(cherry picked from commit f151415de6)
2023-10-31 09:25:33 +00:00
陈劲松
556a9b03b4 Merge branch 'cherry-pick-bd1f7556' into 'dev-23.10'
CN-1425 fix: 修复dashboard下钻后切换顶部最后一级面包屑时会回到下钻前页面的问题

See merge request cyber-narrator/cn-ui!53
2023-10-30 08:38:35 +00:00
chenjinsong
ca5c81d8be CN-1425 fix: 修复dashboard下钻后切换顶部最后一级面包屑时会回到下钻前页面的问题
(cherry picked from commit bd1f755612)
2023-10-30 08:38:28 +00:00
陈劲松
02779c26d1 Merge branch 'cherry-pick-dd4f5e1f' into 'dev-23.10'
fix: eventType取消国际化转换

See merge request cyber-narrator/cn-ui!52
2023-10-30 08:37:51 +00:00
刘洪洪
20692705e9 fix: eventType取消国际化转换
(cherry picked from commit dd4f5e1fba)
2023-10-30 08:37:42 +00:00
陈劲松
bc3bc7eaf3 Merge branch 'cherry-pick-ed1d994d' into 'dev-23.10'
fix: eventType取消国际化转换

See merge request cyber-narrator/cn-ui!51
2023-10-30 08:37:21 +00:00
刘洪洪
d05ae06af6 fix: eventType取消国际化转换
(cherry picked from commit ed1d994d5e)
2023-10-30 08:37:15 +00:00
陈劲松
ca3d8766ba Merge branch 'cherry-pick-815af776' into 'dev-23.10'
fix: 修复policy新建时点击save按钮不生效的问题

See merge request cyber-narrator/cn-ui!50
2023-10-30 08:35:12 +00:00
刘洪洪
00e73adadb fix: 修复policy新建时点击save按钮不生效的问题
(cherry picked from commit 815af776aa)
2023-10-30 08:35:01 +00:00
陈劲松
4ede5768e4 Merge branch 'cherry-pick-a4da1dbf' into 'dev-23.10'
fix: 去掉部分控制台打印

See merge request cyber-narrator/cn-ui!49
2023-10-30 08:34:06 +00:00
chenjinsong
541692f50f fix: 去掉部分控制台打印
(cherry picked from commit a4da1dbfac)
2023-10-30 08:33:59 +00:00
陈劲松
89294c98e1 Merge branch 'cherry-pick-7b8ca904' into 'dev-23.10'
fix: 修复policy新建时form的时间提示被盖住,以及限制输入框内容长度的问题

See merge request cyber-narrator/cn-ui!48
2023-10-30 08:33:22 +00:00
刘洪洪
8fd283c1e7 fix: 修复policy新建时form的时间提示被盖住,以及限制输入框内容长度的问题
(cherry picked from commit 7b8ca90436)
2023-10-30 08:33:16 +00:00
陈劲松
90b90fdd3c Merge branch 'cherry-pick-90827fd7' into 'dev-23.10'
fix: policy新建时添加小时不得超过24等时间限制

See merge request cyber-narrator/cn-ui!47
2023-10-30 07:19:38 +00:00
刘洪洪
72ee214877 fix: policy新建时添加小时不得超过24等时间限制
(cherry picked from commit 90827fd706)
2023-10-30 07:19:27 +00:00
刘洪洪
99f97b76a7 fix: 修复因映射关系找不到导致的报错问题 2023-10-30 13:25:28 +08:00
chenjinsong
b0260ea41a fix: 修复知识库柱状图横坐标不对的问题 2023-10-30 11:43:55 +08:00
chenjinsong
a7d6ffb4b4 fix: 修复知识库更新结束或者在上传页面返回后柱状图消失的问题 2023-10-30 11:26:24 +08:00
刘洪洪
dbc68077ca CN-1416 fix: 增加一些字符的国际化映射 2023-10-30 11:14:58 +08:00
chenjinsong
186e115adf fix: 工具方法打印排查问题 2023-10-30 10:33:53 +08:00
chenjinsong
6c5233a760 CN-1422 fix: 修复Behavior pattern扇形图比例问题 2023-10-27 19:09:42 +08:00
chenjinsong
2df67c1972 Merge remote-tracking branch 'origin/dev' into dev 2023-10-27 18:49:47 +08:00
chenjinsong
cb3b66bdf7 CN-1396 fix: 修复psiphon3知识库update页面的问题 2023-10-27 18:49:37 +08:00
刘洪洪
6ba0ca0a82 CN-1420 fix: Detections-Security events模块中,通过Event type查询异常 2023-10-27 15:49:20 +08:00
chenjinsong
00df0414c2 fix: 修复自定义知识库不能正常访问的问题 2023-10-27 11:45:58 +08:00
hyx
20a4bb22ef CN-1396 intelligence learnings更新记录功能开发 2023-10-27 07:46:34 +08:00
刘洪洪
00bb6f8c5f CN-1406 fix: 新增策略的trigger模块添加国际化 2023-10-26 18:15:22 +08:00
刘洪洪
e61c664802 CN-1417 fix: 修复Detections-Policies模块的Filters下的Status勾选Disabled和Enabled,列表只会显示第一次勾选内容的问题 2023-10-26 18:06:34 +08:00
chenjinsong
a4dff135d1 fix: 修复取值错误导致事件下拉可能报错的问题 2023-10-26 16:13:25 +08:00
刘洪洪
0bd87db92f CN-1403 fix: 一些ui细节调整 2023-10-26 15:09:14 +08:00
刘洪洪
407d9ec667 CN-1412 fix: 修复新增/编辑policy页library的入参错误问题 2023-10-26 11:36:13 +08:00
chenjinsong
e7637dd866 fix: 调整实体下拉无评分时的展示形式 2023-10-26 11:18:03 +08:00
刘洪洪
59c2b26d6c fix: 修复detection下拉详情的近期相关事件时间戳变成字符串格式,导致显示异常的问题 2023-10-26 10:58:59 +08:00
刘洪洪
8b5b36a621 CN-1412 fix: 新增/编辑policy页,library的下拉框添加可查询功能 2023-10-26 10:51:26 +08:00
chenjinsong
7762658864 Merge remote-tracking branch 'origin/dev' into dev 2023-10-26 10:18:16 +08:00
chenjinsong
eb25dc6aa7 CN-1372 fix: 撤回步长计算函数,步长改为在api计算 2023-10-26 10:18:05 +08:00
刘洪洪
1fff8f49c9 feat: 更新iconfont图标 2023-10-25 18:45:46 +08:00
刘洪洪
47d8212abe feat: 修复策略中trigger的时间选择列表,添加策略setting的国际化,添加setting的icon图标 2023-10-25 18:41:16 +08:00
chenjinsong
94940d745c CN-1372 fix: 提交步长计算函数 2023-10-25 18:08:59 +08:00
chenjinsong
030af380fd fix: 内置知识库上传限制放宽到1G 2023-10-25 17:34:48 +08:00
chenjinsong
e9f21038b5 CN-1404 fix: 去掉非智能学习知识库多余的启停开关 2023-10-25 16:44:27 +08:00
刘洪洪
e355e427fb feat: policy相关文件修改命名并放置到正确目录下 2023-10-25 15:30:04 +08:00
刘洪洪
6dea1ee890 fix: 1、修复detection下拉面板点击空白处,地址栏的eventId未被清除的问题;2、完善policy交互流程 2023-10-25 15:13:57 +08:00
刘洪洪
1fae281b8b fix: 修复detection下拉详情的地址信息取值报错问题 2023-10-25 11:26:33 +08:00
chenjinsong
60a527c765 fix: 修复detection有些取值不对的问题 2023-10-25 11:15:09 +08:00
chenjinsong
3daa875a25 fix: 修复detection的range参数传递错误的问题 2023-10-24 20:58:03 +08:00
chenjinsong
b4fcbd260b fix: 调整detection列表和下拉中的描述字段逻辑 2023-10-24 20:20:56 +08:00
刘洪洪
871781ab70 fix: 完善detection创建策略时信息填写完毕,不点击continue也可以创建 2023-10-24 18:39:49 +08:00
刘洪洪
9bb9967353 fix: 完善detection下拉详情的地址信息,修复相关事件的时间显示异常问题,修复搜索初始化一直为text模式问题 2023-10-24 18:11:17 +08:00
chenjinsong
327ae1956d CN-1391 fix: 调整detection列表和下拉中的展示字段 2023-10-24 18:01:59 +08:00
刘洪洪
4d4d197a80 fix: 修复detection列表和filter问题:1、搜索栏清除搜索,勾选未去除;2、filter选项包含大写,点击搜索时报错;3、搜索包含空格导致搜索失败;4、添加刷新保留状态 2023-10-24 16:02:25 +08:00
chenjinsong
46f1a880cd fix: 调整policy的一些字典项 2023-10-24 15:34:08 +08:00
chenjinsong
d1c9eba029 fix: 修复detection列表总数等问题 2023-10-24 13:52:55 +08:00
chenjinsong
643ada9172 CN-1400 fix: 调整知识库智能学习启停功能 2023-10-23 18:42:32 +08:00
chenjinsong
d0aa64fb9c fix: 调整detection菜单和一些文字描述 2023-10-23 18:04:29 +08:00
hyx
5b7dd6da90 CN-1384 1.behavior pattern:左边的列表垂直居中,右边的图像向左边移动 2023-10-23 17:56:36 +08:00
chenjinsong
97d793471c fix: 修复报错无法加载policies列表的问题 2023-10-23 16:34:29 +08:00
hyx
6a2d5a0efe CN-1384 1.把0的数据去掉,列表和图里都不展示;2.进入实体详情页时,查一次接口,确认非0数据的个数,个数显示在tab上;3.进入tab时,查更新tab顶部数值;4.数据全是0显示No Data; 2023-10-23 14:54:14 +08:00
chenjinsong
b90810470e fix: 更新遗漏的tag处理 2023-10-23 13:42:42 +08:00
chenjinsong
d8865b74d7 CN-1317 fix: 调整地图上俄罗斯的圆点位置 2023-10-23 10:39:06 +08:00
chenjinsong
012d873b71 CN-1400 fix: 界面支持智能学习启停 2023-10-22 23:10:00 +08:00
chenjinsong
fefca1588f CN-1382 fix: 暂时关闭评分相关的单元测试 2023-10-22 21:28:13 +08:00
chenjinsong
f6af25e5e6 CN-1223 fix: 暂时关闭评分相关的单元测试 2023-10-22 20:58:52 +08:00
chenjinsong
c5c7b58720 CN-1223 fix: score改版 2023-10-22 20:21:32 +08:00
chenjinsong
40d43acb6c CN-1391 fix: 修复一系列bug 2023-10-22 18:29:34 +08:00
hyx
7d34b8388c CN-1384 IP实体详情页behavior pattern tab功能开发 2023-10-20 17:41:01 +08:00
刘洪洪
d2feedb319 CN-1391 fix: 添加eventInfoObj字段 2023-10-20 16:02:24 +08:00
刘洪洪
0f52bd5362 CN-1391 fix: Detection列表按新版设计更新接口和样式 2023-10-20 15:45:11 +08:00
刘洪洪
48b3e2aebd fix: 修复检测规则新增时参数不符要求导致新增失败,以及编辑时修改状态不生效的问题 2023-10-20 14:56:56 +08:00
chenjinsong
9208a4fff2 fix: 实体展示新增dns server role类的tag 2023-10-18 15:52:27 +08:00
chenjinsong
19747cf326 CN-1273 fix: 调整内置tag字典 2023-10-18 10:52:25 +08:00
刘洪洪
c2de6c853c Merge remote-tracking branch 'origin/dev' into dev 2023-10-17 10:01:42 +08:00
刘洪洪
b0a7d44c42 fix: 修复detection数据缺少时显示异常的问题 2023-10-17 10:01:32 +08:00
hyx
3b641e1878 CN-1377 国际化内容补充:1.检查下拉框,去掉placeholder; 2023-10-17 10:01:08 +08:00
刘洪洪
0a9d5591d5 fix: 修复导入axios依赖携带版本的问题 2023-10-16 18:13:30 +08:00
刘洪洪
0e82bebaac CN-1173 fix: 检测功能UI开发与接口对接 2023-10-16 17:53:46 +08:00
chenjinsong
85db8cd745 CN-1273 fix: 修复知识库编辑页新增color后,左侧step高度异常的问题 2023-10-16 15:21:34 +08:00
chenjinsong
c960c60790 CN-1317 fix: 调整npm location地图逻辑
1.地图右上角筛选框只提供有数据的选项;
2.更新“国家名称-code”的字典;
3.少数国家/地区无下钻地理数据的,只缩放而不下钻,并提示暂无地图数据;
2023-10-13 16:29:43 +08:00
chenjinsong
18b62f1e3d fix: 修复链路下钻后参数会丢失大写格式的问题 2023-10-12 10:34:09 +08:00
刘洪洪
11540d3c34 fix: 修复曲线图在数据所有tab的max都为0的情况下会重复请求的问题 2023-10-11 18:40:29 +08:00
刘洪洪
5fbab86a80 CN-1351 fix: 实体列表、详情和graph的tag颜色从接口获取 2023-10-11 15:17:15 +08:00
chenjinsong
ed9a2c4e1f fix: 尝试修复依赖问题 2023-10-09 20:15:12 +08:00
chenjinsong
b94bc23a86 CN-1317 fix: 增加几个地理数据文件 2023-10-09 20:04:10 +08:00
chenjinsong
328f226a86 fix: 尝试修改依赖版本 2023-10-09 19:51:15 +08:00
chenjinsong
660950a73c fix: 尝试修改依赖版本 2023-10-09 19:24:44 +08:00
chenjinsong
8f9308a04e fix: 尝试修改依赖版本 2023-10-09 19:03:40 +08:00
chenjinsong
133c9a23c0 fix: 尝试修改依赖版本 2023-10-09 18:55:26 +08:00
chenjinsong
bc4a6c2a9e fix: 尝试修改依赖版本 2023-10-09 18:52:06 +08:00
chenjinsong
5798c0c5a7 fix: 尝试修改依赖版本 2023-10-09 18:38:21 +08:00
chenjinsong
15ac00d548 fix: 尝试修改依赖版本 2023-10-09 17:55:48 +08:00
chenjinsong
95217b84a5 fix: 尝试修改依赖版本 2023-10-09 17:40:37 +08:00
chenjinsong
24e549a444 fix: 尝试修改依赖版本 2023-10-09 17:28:23 +08:00
chenjinsong
e16d351efd Merge remote-tracking branch 'origin/dev' into dev 2023-10-09 17:01:40 +08:00
chenjinsong
ccec756baa feat: 更改后端端口配置 2023-10-09 17:01:28 +08:00
刘洪洪
a47e04fae2 fix: 尝试修复不识别$t的问题 2023-10-09 16:30:11 +08:00
刘洪洪
8cc6edbdd4 fix: 实体搜索允许通过语言添加俄语 2023-10-09 15:16:48 +08:00
hyx
afa86464d9 CN-1273 自定义library增加tag颜色选择功能 2023-10-09 13:46:28 +08:00
hyx
16e63ab905 CN-1273 自定义library增加tag颜色选择功能 2023-10-09 11:55:19 +08:00
刘洪洪
6f72b2258f fix: 修复实体搜索内容包含非英文时会弹窗提示非法字符串的问题 2023-10-08 17:26:32 +08:00
刘洪洪
5a4301e3aa fix: 实体详情曲线图去掉尾部最多4个0值点 2023-10-08 14:53:44 +08:00
chenjinsong
96c380001c fix: 解决编译问题 2023-10-08 14:25:58 +08:00
chenjinsong
40365decf6 fix: 删除galaxy proxy和chart内容 2023-10-08 14:17:51 +08:00
chenjinsong
ac9c16d464 Merge remote-tracking branch 'origin/dev' into dev 2023-10-08 11:58:22 +08:00
chenjinsong
7abe436127 fix: 更新package-lock.json 2023-10-08 11:58:11 +08:00
唐浩
d85e2d347f Update .gitlab-ci.yml file 2023-10-07 09:18:47 +00:00
chenjinsong
cc61cbc05c fix: 修复networkoverview line去掉尾部4个0值的代码导致测试用例不通过的问题 2023-10-07 10:33:13 +08:00
刘洪洪
7cc2439dd2 fix: 去除无效打印 2023-09-28 14:07:34 +08:00
刘洪洪
8ba062054c CN-1363 fix: 修复单引号内不能包含逗号的问题,以及语句中引号内包含逗号后添加连接has函数的逻辑 2023-09-28 14:05:41 +08:00
陈劲松
cdb1d4baec Merge branch 'cherry-pick-fc56a1fc' into 'dev'
fix: networkoverview的曲线图去掉尾部最多4个0值点

See merge request cyber-narrator/cn-ui!41
2023-09-28 05:25:06 +00:00
chenjinsong
1886f8214f fix: networkoverview的曲线图去掉尾部最多4个0值点
(cherry picked from commit fc56a1fc0c)
2023-09-28 13:25:00 +08:00
陈劲松
aaaecfd9a1 Merge branch 'cherry-pick-ccd2b3d0' into 'dev'
fix: 修复实体列表流量速率显示为横杠的问题

See merge request cyber-narrator/cn-ui!40
2023-09-28 02:02:24 +00:00
chenjinsong
9ab00df342 fix: 修复实体列表流量速率显示为横杠的问题
(cherry picked from commit ccd2b3d08b)
2023-09-28 10:02:18 +08:00
陈劲松
33293aaaa3 Merge branch 'cherry-pick-60bbb8b1' into 'dev'
fix: 修复实体列表实体属性为空串时,不显示'-'的问题

See merge request cyber-narrator/cn-ui!39
2023-09-27 11:54:10 +00:00
chenjinsong
5cb105784a fix: 修复实体列表实体属性为空串时,不显示'-'的问题
(cherry picked from commit 60bbb8b165)
2023-09-27 19:54:02 +08:00
刘洪洪
4e0e49585e CN-1362 fix: 更改折线图隐藏Tab的逻辑 2023-09-27 18:40:16 +08:00
113 changed files with 12773 additions and 26907 deletions

View File

@@ -39,9 +39,9 @@ build_project:
stage: build_project stage: build_project
script: script:
- echo "npm install ..." - echo "npm install ..."
- cnpm install --save-dev --unsafe-perm - npm install --save-dev --unsafe-perm
- echo "npm run build" - echo "npm run build"
- cnpm run build - npm run build
artifacts: artifacts:
name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME" name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
when: on_success when: on_success

27006
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,63 +10,64 @@
"analyz": "cross-env NODE_ENV=production npm_config_report=true npm run build" "analyz": "cross-env NODE_ENV=production npm_config_report=true npm run build"
}, },
"dependencies": { "dependencies": {
"@amcharts/amcharts4": "^4.10.24", "@amcharts/amcharts4": "~4.10.0",
"@amcharts/amcharts4-geodata": "^4.1.20", "@amcharts/amcharts4-geodata": "^4.1.20",
"@antv/g6": "^4.8.17", "@antv/g6": "^4.8.17",
"axios": "^0.21.1", "axios": "^0.21.1",
"babel-plugin-lodash": "^3.3.4", "babel-plugin-lodash": "~3.3.0",
"codemirror": "^5.65.1", "codemirror": "^5.65.1",
"core-js": "^3.6.5", "core-js": "~3.31.0",
"dayjs": "^1.10.5", "dayjs": "^1.10.5",
"dexie": "^3.2.2", "dexie": "~3.2.0",
"echarts": "^5.1.1", "echarts": "^5.1.1",
"element-plus": "^1.0.2-beta.44", "element-plus": "~1.0.2-beta.71",
"lib-flexible": "^0.3.2", "lib-flexible": "^0.3.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mockjs": "^1.1.0", "mockjs": "^1.1.0",
"moment-timezone": "^0.5.33", "moment-timezone": "^0.5.33",
"node-sass": "^4.14.1", "node-sass": "~4.14.0",
"postcss-plugin-px2rem": "^0.8.1", "postcss-plugin-px2rem": "~0.8.1",
"postcss-px2rem-exclude": "0.0.6", "postcss-px2rem-exclude": "0.0.6",
"sass-loader": "^8.0.2", "sass-loader": "~8.0.2",
"sass-resources-loader": "^2.2.1", "sass-resources-loader": "~2.2.5",
"tiny-emitter": "^2.1.0", "tiny-emitter": "^2.1.0",
"vue": "^3.0.0", "vue": "~3.3.0",
"vue-grid-layout": "^3.0.0-beta1", "vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.1.6", "vue-i18n": "~9.2.0",
"vue-router": "^4.0.8", "vue-router": "~4.2.0",
"vuex": "^4.0.1" "vuex": "~4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.12.1", "@babel/cli": "~7.22.0",
"@babel/core": "^7.11.4", "@babel/core": "~7.22.5",
"@babel/plugin-proposal-class-properties": "^7.12.1", "@babel/plugin-proposal-class-properties": "~7.18.0",
"@babel/plugin-proposal-private-methods": "^7.12.1", "@babel/plugin-proposal-private-methods": "~7.18.0",
"@babel/plugin-transform-runtime": "^7.12.1", "@babel/plugin-transform-runtime": "~7.22.0",
"@babel/plugin-proposal-private-property-in-object": "^7.12.1", "@babel/plugin-proposal-private-property-in-object": "~7.21.0",
"@babel/preset-env": "^7.11.5", "@babel/preset-env": "~7.22.0",
"@babel/preset-typescript": "^7.10.4", "@babel/preset-typescript": "~7.22.0",
"@commitlint/cli": "^9.1.2", "@commitlint/cli": "^9.1.2",
"@commitlint/config-conventional": "^9.1.2", "@commitlint/config-conventional": "^9.1.2",
"@highlightjs/vue-plugin": "^2.0.1", "@highlightjs/vue-plugin": "^2.0.1",
"@rollup/plugin-commonjs": "^15.1.0", "@rollup/plugin-commonjs": "^15.1.0",
"@rollup/plugin-node-resolve": "^9.0.0", "@rollup/plugin-node-resolve": "^9.0.0",
"@rollup/plugin-typescript": "^6.0.0", "@rollup/plugin-typescript": "^6.0.0",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "~5.16.0",
"@testing-library/user-event": "^14.4.3", "@testing-library/user-event": "~14.4.0",
"@testing-library/vue": "^6.4.2", "@testing-library/vue": "~6.6.0",
"@types/jest": "^26.0.24", "@types/jest": "~26.0.0",
"@types/lodash": "^4.14.161", "@types/lodash": "^4.14.161",
"@typescript-eslint/eslint-plugin": "^3.10.1", "@typescript-eslint/eslint-plugin": "^3.10.1",
"@typescript-eslint/parser": "^3.10.1", "@typescript-eslint/parser": "^3.10.1",
"@vue/babel-plugin-jsx": "^1.0.0", "@vue/babel-plugin-jsx": "~1.1.0",
"@vue/babel-preset-app": "^5.0.8", "@vue/babel-preset-app": "~5.0.0",
"@vue/cli-plugin-babel": "~4.5.0", "@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0", "@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-service": "~4.5.0", "@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0", "@vue/compiler-sfc": "~3.3.0",
"@vue/component-compiler-utils": "^3.2.0", "@vue/component-compiler-utils": "~3.3.0",
"@vue/test-utils": "^2.2.7", "@vue/test-utils": "~2.4.0",
"@vue/server-renderer": "~3.3.0",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"babel-jest": "^26.0.0", "babel-jest": "^26.0.0",
"compression-webpack-plugin": "^8.0.1", "compression-webpack-plugin": "^8.0.1",
@@ -77,11 +78,10 @@
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.3.1", "eslint-plugin-promise": "^4.3.1",
"eslint-plugin-vue": "^7.7.0", "eslint-plugin-vue": "^7.7.0",
"jest": "^26.0.0", "jest": "~26.6.0",
"ts-jest": "^26.4.4", "ts-jest": "~26.5.0",
"uglifyjs-webpack-plugin": "^2.2.0", "uglifyjs-webpack-plugin": "^2.2.0",
"vue-jest": "^5.0.0-alpha.10", "vue-jest": "^5.0.0-alpha.10"
"vue3-ace-editor": "^2.0.2"
}, },
"browserslist": [ "browserslist": [
"> 1%", "> 1%",

View File

@@ -1,5 +1,5 @@
const BASE_CONFIG = { const BASE_CONFIG = {
baseUrl: 'http://192.168.44.54:8091/', baseUrl: 'http://192.168.44.54:8090/',
version: '23.06', version: '23.10',
apiVersion: 'v1' 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

View File

@@ -83,6 +83,71 @@
.entity-detail-event-block { .entity-detail-event-block {
width: calc(100% - 2px); 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 { .entity-detail-event-error {

View File

@@ -128,24 +128,25 @@ $blue: #046ECA;
} }
} }
} }
.link-statistical-dimension {
.row-dot {
margin-top: 5px;
margin-right: 5px;
}
.row-dot { .green-dot {
margin-top: 5px; width: 7px;
margin-right: 5px; height: 7px;
} border-radius: 50%;
background: #749F4D;
}
.green-dot { .red-dot {
width: 7px; width: 7px;
height: 7px; height: 7px;
border-radius: 50%; border-radius: 50%;
background: #749F4D; background: #E26154;
} }
.red-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: #E26154;
} }
.item-popover-up, .item-popover-down { .item-popover-up, .item-popover-down {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -349,6 +349,24 @@
.data-score-green { .data-score-green {
background: #749F4D; 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; height:24px;
font-size: 14px; font-size: 14px;
color: #046ECA; color: #046ECA;

View File

@@ -174,6 +174,25 @@
font-size: 14px; font-size: 14px;
color: #353636; color: #353636;
font-weight: 400; 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-face {
font-family: "cn-icon"; /* Project id 2614877 */ font-family: "cn-icon"; /* Project id 2614877 */
src: url('iconfont.woff2?t=1693386443164') format('woff2'), src: url('iconfont.woff2?t=1698229141457') format('woff2'),
url('iconfont.woff?t=1693386443164') format('woff'), url('iconfont.woff?t=1698229141457') format('woff'),
url('iconfont.ttf?t=1693386443164') format('truetype'); url('iconfont.ttf?t=1698229141457') format('truetype');
} }
.cn-icon { .cn-icon {
@@ -13,6 +13,18 @@
-moz-osx-font-smoothing: grayscale; -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 { .cn-icon-category-group:before {
content: "\e6c7"; 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

@@ -624,6 +624,11 @@ export default {
const parser = new Parser(this.columnList) const parser = new Parser(this.columnList)
if (q.indexOf('%') === 0 || q.indexOf('%20') > -1 || q.indexOf('%25') > -1) { if (q.indexOf('%') === 0 || q.indexOf('%20') > -1 || q.indexOf('%25') > -1) {
q = decodeURI(q) q = decodeURI(q)
} else {
const str1 = q.substring(q.indexOf('%'), q.indexOf('%') + 3)
if (q.indexOf('%') > 0 && (str1 !== '%20' || str1 === '%25')) {
q = decodeURI(q)
}
} }
this.metaList = parser.parseStr(q).metaList this.metaList = parser.parseStr(q).metaList
} }

View File

@@ -225,6 +225,11 @@ export default {
if (q) { if (q) {
if (q.indexOf('%') === 0 || q.indexOf('%20') > -1 || q.indexOf('%25') > -1) { if (q.indexOf('%') === 0 || q.indexOf('%20') > -1 || q.indexOf('%25') > -1) {
q = decodeURI(q) q = decodeURI(q)
} else {
const str1 = q.substring(q.indexOf('%'), q.indexOf('%') + 3)
if (q.indexOf('%') > 0 && (str1 !== '%20' || str1 === '%25')) {
q = decodeURI(q)
}
} }
// 为避免地址栏任意输入导致全查询的q带QUERY解析时不识别导致的语法错误 // 为避免地址栏任意输入导致全查询的q带QUERY解析时不识别导致的语法错误
// 如地址栏输入116.178.222.171此时的q很长刷新界面时需要把q里的116.178.222.171拿出来进行搜索 // 如地址栏输入116.178.222.171此时的q很长刷新界面时需要把q里的116.178.222.171拿出来进行搜索

View File

@@ -6,9 +6,10 @@ import { ElMessage } from 'element-plus'
import i18n from '@/i18n' import i18n from '@/i18n'
const strReg = { const strReg = {
all: /^[\da-zA-Z\s.'><!=-_(),%]$/, // 需要不限制语言,正则过滤中英日俄语出错实现语言都通过。留个记录观察,后续校验
all: /^[\da-zA-Z\u4E00-\u9FA5\u3040-\u309F\u0800-\u4e00\u0400-\u04FF\u2000-\u206F\s.'><!=-_(),%]$/,
key: /^(?![\d])[\da-zA-Z\s.'-_]$/, key: /^(?![\d])[\da-zA-Z\s.'-_]$/,
value: /^[\da-zA-Z\s.'-_%]$/ value: /^[\da-zA-Z\u4E00-\u9FA5\u3040-\u309F\u0800-\u4e00\u0400-\u04FF\u2000-\u206F\s.'-_%]$/
} }
const operatorList = ['=', ' in ', ' IN ', ' like ', ' LIKE ', 'HAS(', 'has('] const operatorList = ['=', ' in ', ' IN ', ' like ', ' LIKE ', 'HAS(', 'has(']
@@ -527,7 +528,9 @@ export default class Parser {
} }
// 在单引号里,又在括号里,则遇到逗号、右括号就报错 // 在单引号里,又在括号里,则遇到逗号、右括号就报错
if (isInBracket && isInApostrophe) { if (isInBracket && isInApostrophe) {
if ([',', ')'].indexOf(strArr[j]) > -1) { // if ([',', ')'].indexOf(strArr[j]) > -1) {
// 目前有ip in ('1.1.1.1,2.2.2.2')该情况,后续待验证
if ([')'].indexOf(strArr[j]) > -1) {
errorList.push(new ParserError(j, errorTypes.syntaxError, errorDesc.syntaxError.unclosedApostrophe)) errorList.push(new ParserError(j, errorTypes.syntaxError, errorDesc.syntaxError.unclosedApostrophe))
break break
} }
@@ -709,6 +712,13 @@ export default class Parser {
meta.column.label = token.value meta.column.label = token.value
meta.column.type = columnType.fullText meta.column.type = columnType.fullText
} }
} else if (nextToken.type === types.rightBracket) {
// 此处操作为ip='1,2' and has(tag,'222')中 ,'222')的情况
if (prevToken) {
if (prevToken.type === types.comma && token.type === types.commonStr && nextToken.type === types.rightBracket) {
meta.value.value = token.value
}
}
} else { } else {
errorList.push(new ParserError(token.end, errorTypes.syntaxError, errorDesc.syntaxError.unexpectedString)) errorList.push(new ParserError(token.end, errorTypes.syntaxError, errorDesc.syntaxError.unexpectedString))
break break
@@ -1163,7 +1173,9 @@ export default class Parser {
const arr = [] const arr = []
// 如果出现this.columnList中的字段如IP\Domain\App\Country等则不进行模糊搜索将str返回出去 // 如果出现this.columnList中的字段如IP\Domain\App\Country等则不进行模糊搜索将str返回出去
this.columnList.forEach(item => { this.columnList.forEach(item => {
arr.push(item.label.toLowerCase()) // todo 取消了大小写校验,后续观察是否出现问题
// arr.push(item.label.toLowerCase())
arr.push(item.label)
}) })
// 因为手动输入时可能会输入and所以将操作符的AND转换为and统一处理 // 因为手动输入时可能会输入and所以将操作符的AND转换为and统一处理
@@ -1173,7 +1185,9 @@ export default class Parser {
newStr = newStr.replace(new RegExp(arr[i], 'g'), arr[i]) newStr = newStr.replace(new RegExp(arr[i], 'g'), arr[i])
} }
// 检查str字段在arr中是否出现,true为出现过 // 检查str字段在arr中是否出现,true为出现过
const result = arr.some(item => newStr.toLowerCase().includes(item)) // todo 取消了大小写校验,后续观察是否出现问题
// const result = arr.some(item => newStr.toLowerCase().includes(item))
const result = arr.some(item => newStr.includes(item))
if (newStr.indexOf(' and ') > -1) { if (newStr.indexOf(' and ') > -1) {
// 将单引号包裹的and拿出来放到数组tempList里原来的单引号包裹内容用temp即'it is test keyword{键值}'代替 // 将单引号包裹的and拿出来放到数组tempList里原来的单引号包裹内容用temp即'it is test keyword{键值}'代替
// 再将字符串用and转换为数组遍历数组发现值为temp的获取键值根据键值获取tempList的值组合起来 // 再将字符串用and转换为数组遍历数组发现值为temp的获取键值根据键值获取tempList的值组合起来
@@ -1184,7 +1198,7 @@ export default class Parser {
// 将单引号包裹的and内容集合起来 // 将单引号包裹的and内容集合起来
while ((match = regex.exec(newStr)) !== null) { while ((match = regex.exec(newStr)) !== null) {
if (match[1].includes('and')) { if (match[1].includes(' and ')) {
tempList.push(match[1]) tempList.push(match[1])
} }
} }
@@ -1224,7 +1238,8 @@ export default class Parser {
// 不区分大小写用this.columnList里的label // 不区分大小写用this.columnList里的label
arr.forEach(item => { arr.forEach(item => {
if (str.toLowerCase().indexOf(item.toLowerCase()) > -1) { if (str.toLowerCase().indexOf(item.toLowerCase()) > -1) {
str = str.replace(new RegExp(item, 'gi'), item) // todo 记录一下此处取消了转小写转换,后续搜索验证
// str = str.replace(new RegExp(item, 'gi'), item)
if (!operatorList.some(ite => str.includes(ite)) && str.toLowerCase() !== item.toLowerCase()) { if (!operatorList.some(ite => str.includes(ite)) && str.toLowerCase() !== item.toLowerCase()) {
str = this.checkFormatByStr(str) str = this.checkFormatByStr(str)
} }

View File

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

View File

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

View File

@@ -1,408 +0,0 @@
<template>
<div class="right-box right-box-user">
<div class="right-box__header">
<div class="header__title">{{editObject.id ? $t('config.chart.edit') : $t('config.chart.add')}}</div>
<div class="header__operation">
<span v-cancel="{object: editObject, func: esc}"><i class="cn-icon cn-icon-close"></i></span>
</div>
</div>
<div class="right-box__container">
<div class="container__form">
<el-form ref="chartForm" :model="editObject" :rules="editObject.id ? rules2 : rules" label-position="top" label-width="120px">
<!--name-->
<el-form-item :label="$t('overall.name')" prop="name">
<el-input id="chart-input-name" v-model="editObject.name" :disabled="editObject.name==='admin' && editObject.id === 1"
maxlength="64" placeholder="" show-word-limit size="small" type="text"></el-input>
</el-form-item>
<!--i18n-->
<el-form-item :label="$t('config.chart.i18n')" prop="i18n">
<el-input id="chart-input-i18n" v-model="editObject.i18n"
maxlength="64" placeholder="" show-word-limit size="small" type="text"></el-input>
</el-form-item>
<!--type-->
<el-form-item :label="$t('config.chart.type')" prop="type">
<el-select id="chart-type"
v-model="editObject.type"
class="right-box__select"
clearable
collapse-tags
placeholder=""
popper-class="right-box-select-dropdown prevent-clickoutside"
size="small"
@change="()=>{ this.$forceUpdate() }">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
<span style="float: left">{{ item.label }}</span><span style="float: right;color: #909399;font-size: 13px;">{{ item.remark }}</span>
</el-option>
</el-select>
</el-form-item>
<!--panel-->
<el-form-item :label="$t('config.chart.panel')" prop="panelId">
<el-select id="chart-input-panelId"
v-model="editObject.panelId"
class="right-box__select"
clearable
collapse-tags
placeholder=""
:disabled="editObject.id?true:false"
popper-class="right-box-select-dropdown prevent-clickoutside"
size="small"
@change="getChartData">
<el-option :key="panelTypeAndRouteMapping.trafficSummary" :label="$t('trafficSummary.trafficSummary')" :value="panelTypeAndRouteMapping.trafficSummary"></el-option>
<el-option :key="panelTypeAndRouteMapping.networkAppPerformance" :label="$t('networkAppPerformance.networkAppPerformance')" :value="panelTypeAndRouteMapping.networkAppPerformance"></el-option>
<el-option :key="panelTypeAndRouteMapping.cryptocurrency" :label="$t('overall.cryptocurrency')" :value="panelTypeAndRouteMapping.cryptocurrency"></el-option>
<el-option :key="panelTypeAndRouteMapping.dnsServiceInsights" :label="$t('dnsServiceInsights.dnsServiceInsights')" :value="panelTypeAndRouteMapping.dnsServiceInsights"></el-option>
<el-option :key="panelTypeAndRouteMapping.ipEntityDetail" :label="$t('entities.ipEntityDetail')" :value="panelTypeAndRouteMapping.ipEntityDetail"></el-option>
<el-option :key="panelTypeAndRouteMapping.domainEntityDetail" :label="$t('entities.domainEntityDetail')" :value="panelTypeAndRouteMapping.domainEntityDetail"></el-option>
<el-option :key="panelTypeAndRouteMapping.appEntityDetail" :label="$t('entities.appEntityDetail')" :value="panelTypeAndRouteMapping.appEntityDetail"></el-option>
</el-select>
</el-form-item>
<!--pid-->
<el-form-item :label="$t('config.chart.pid')" prop="pid">
<el-select id="chart-pid"
v-model="editObject.pid"
class="right-box__select"
clearable
collapse-tags
placeholder=""
:disabled="editObject.id?true:false"
popper-class="right-box-select-dropdown prevent-clickoutside"
size="small"
@change="()=>{ this.$forceUpdate() }">
<template v-for="chart in chartData" :key="chart.id">
<el-option :label="chart.name" :value="chart.id"></el-option>
</template>
</el-select>
</el-form-item>
<!--x-->
<el-form-item :label="$t('config.chart.x')" prop="x">
<el-input id="chart-input-x" v-model="editObject.x"
maxlength="64" placeholder="" show-word-limit size="small" type="text"></el-input>
</el-form-item>
<!--y-->
<el-form-item :label="$t('config.chart.y')" prop="y">
<el-input id="chart-input-y" v-model="editObject.y"
maxlength="64" placeholder="" show-word-limit size="small" type="text"></el-input>
</el-form-item>
<!--w-->
<el-form-item :label="$t('config.chart.w')" prop="w">
<el-input id="chart-input-x" v-model="editObject.w"
maxlength="64" placeholder="" show-word-limit size="small" type="text"></el-input>
</el-form-item>
<!--h-->
<el-form-item :label="$t('config.chart.h')" prop="h">
<el-input id="chart-input-y" v-model="editObject.h"
maxlength="64" placeholder="" show-word-limit size="small" type="text"></el-input>
</el-form-item>
<!--params-->
<el-form-item :label="$t('config.chart.params')" prop="params">
<v-ace-editor
v-model:value="editObject.params"
lang="json"
theme="chrome"
style="height: 300px" />
</el-form-item>
<!--remark-->
<el-form-item :label="$t('config.chart.remark')">
<el-input maxlength="1024" show-word-limit :rows="2" size='mini' type="textarea" v-model="editObject.remark" id="chart-box-remark"/>
</el-form-item>
</el-form>
</div>
</div>
<div class="right-box__footer">
<button id="chart-edit-cancel" v-cancel="{object: editObject, func: esc}" class="footer__btn footer__btn--light">
<span>{{$t('overall.cancel')}}</span>
</button>
<button id="chart-edit-save" :class="{'footer__btn--disabled': blockOperation.save}" :disabled="blockOperation.save" class="footer__btn" @click="save">
<span>{{$t('overall.save')}}</span>
</button>
</div>
</div>
</template>
<script>
import rightBoxMixin from '@/mixins/right-box'
import axios from 'axios'
import { panelTypeAndRouteMapping, storageKey } from '@/utils/constants'
import { api } from '@/utils/api'
import { VAceEditor } from 'vue3-ace-editor'
import 'ace-builds/src-noconflict/mode-javascript'
import 'ace-builds/src-noconflict/mode-json'
import 'ace-builds/src-noconflict/theme-chrome'
export default {
name: 'ChartBox',
mixins: [rightBoxMixin],
components: {
VAceEditor
},
data () {
return {
url: api.chart,
loginName: localStorage.getItem(storageKey.username),
panelTypeAndRouteMapping: panelTypeAndRouteMapping,
rules: { // 表单校验规则
name: [
{ required: true, message: this.$t('validate.required'), trigger: 'blur' }
],
type: [
{ required: true, message: this.$t('validate.required'), trigger: 'blur' }
],
panel_id: [
{ required: true, message: this.$t('validate.required'), trigger: 'blur' }
],
x: [
{ required: true, message: this.$t('validate.required'), trigger: 'blur' }
],
y: [
{ required: true, message: this.$t('validate.required'), trigger: 'blur' }
],
w: [
{ required: true, message: this.$t('validate.required'), trigger: 'blur' }
],
h: [
{ required: true, message: this.$t('validate.required'), trigger: 'blur' }
]
},
rules2: { // 表单校验规则
name: [
{ required: true, message: this.$t('validate.required'), trigger: 'blur' }
],
type: [
{ required: true, message: this.$t('validate.required'), trigger: 'blur' }
],
panel_id: [
{ required: true, message: this.$t('validate.required'), trigger: 'blur' }
],
x: [
{ required: true, message: this.$t('validate.required'), trigger: 'blur' }
],
y: [
{ required: true, message: this.$t('validate.required'), trigger: 'blur' }
],
w: [
{ required: true, message: this.$t('validate.required'), trigger: 'blur' }
],
h: [
{ required: true, message: this.$t('validate.required'), trigger: 'blur' }
]
},
chartData: [],
options: [
{
value: 1,
label: 'map-1',
remark: '流量流向地图-连线'
},
{
value: 2,
label: 'map-2',
remark: '地图-色块'
}, {
value: 3,
label: 'map-3',
remark: '地图-点'
},
{
value: 4,
label: 'map-4',
remark: '地图-点'
}, {
value: 11,
label: 'line-1',
remark: '折线图-常规'
},
{
value: 61,
label: 'table-1',
remark: '表格'
}, {
value: 62,
label: 'table-2',
remark: 'DNS记录'
}, {
value: 63,
label: 'table-3',
remark: '挖矿活跃ip'
}, {
value: 31,
label: 'pie-1',
remark: '饼图-带联动表格'
}, {
value: 51,
label: 'singleValue-1',
remark: '单值图'
},
{
value: 91,
label: 'tab-container',
remark: 'tab标签(容器)'
}, {
value: 92,
label: 'tab-item',
remark: 'tab标签(标签页)'
},
{
value: 93,
label: 'title',
remark: '标题'
}, {
value: 94,
label: 'group',
remark: '组'
},
{
value: 12,
label: 'line-2',
remark: '折线图-带统计'
}, {
value: 52,
label: 'singleValue-2',
remark: '详情单值图'
},
{
value: 53,
label: 'singleValue-3',
remark: '单值图'
}, {
value: 13,
label: 'line-3',
remark: '折线图-堆叠面积'
},
{
value: 21,
label: 'bar-1',
remark: '柱状图'
}, {
value: 22,
label: 'bar-2',
remark: '开放端口'
}, {
value: 23,
label: 'bar-3',
remark: '挖矿事件统计(time类型柱状图)'
}, {
value: 24,
label: 'bar-4',
remark: '矿机所属单位(category类型柱状图)'
},
{
value: 32,
label: 'pie-2',
remark: '饼图-常规'
}, {
value: 33,
label: 'pie-3',
remark: '托管域名'
},
{
value: 34,
label: 'pie-4',
remark: '相关域名'
}, {
value: 42,
label: 'relation-2',
remark: '关系图谱'
},
{
value: 43,
label: 'relation-3',
remark: '访问链路图'
},
{
value: 82,
label: 'base-2',
remark: 'APP基本信息'
}, {
value: 83,
label: 'list-1',
remark: 'Whois'
},
{
value: 84,
label: 'list-2',
remark: 'DNS记录'
},
{
value: 85,
label: 'list-3',
remark: '近期挖矿事件'
}
]
}
},
setup () {
},
mounted () {
if (this.editObject.id) {
this.getChartData(this.editObject.panelId)
}
},
watch: {
'editObject.panelId': function (newValue, oldValue) {
this.editObject.pid = ''
}
},
methods: {
isCurrentUser (username) {
return localStorage.getItem(storageKey.username) === username
},
/* 密码失去焦点 检验确认密码 */
pinBlur () {
if (this.editObject.pin && this.editObject.pinChange) {
this.$refs.chartForm.validateField('pinChange')
}
},
save () {
if (this.blockOperation.save) { return }
this.blockOperation.save = true
this.$refs.chartForm.validate((valid) => {
if (valid) {
if (this.editObject.id) {
axios.put(this.url, this.editObject).then(res => {
this.blockOperation.save = false
if (res.status === 200) {
this.$message({ duration: 2000, type: 'success', message: this.$t('tip.saveSuccess') })
this.esc(true)
} else {
this.$message.error(res.data.message)
}
})
} else {
axios.post(this.url, this.editObject).then(res => {
this.blockOperation.save = false
if (res.status === 200) {
this.$message({ duration: 2000, type: 'success', message: this.$t('tip.saveSuccess') })
this.esc(true)
} else {
this.$message.error(res.data.message)
}
})
}
} else {
this.blockOperation.save = false
return false
}
})
},
async getChartData (value) {
await axios.get(api.chart, { params: { panelId: value } }).then(response => {
if (response.status === 200) {
this.chartData = response.data.data.list
}
})
}
}
}
</script>

View File

@@ -1,117 +0,0 @@
<template>
<div class="right-box">
<div class="right-box__header">
<div class="header__title">{{editObject.id ? $t('overall.edit') : $t('overall.new')}}</div>
<div class="header__operation">
<span v-cancel="{object: editObject, func: esc}"><i class="cn-icon cn-icon-close"></i></span>
</div>
</div>
<div class="right-box__container">
<div class="container__form">
<el-form ref="form" :model="editObject" :rules="rules" label-position="top" label-width="120px">
<!--name-->
<el-form-item :label="$t('overall.name')" prop="name">
<el-input id="proxy-name" v-model="editObject.name"
maxlength="64" placeholder="" show-word-limit size="small" type="text"></el-input>
</el-form-item>
<!--path-->
<el-form-item :label="$t('overall.path')" prop="path">
<el-input id="proxy-path" v-model="editObject.path"
placeholder="" show-word-limit size="small" type="text"></el-input>
</el-form-item>
<!--method-->
<el-form-item :label="$t('overall.method')" prop="method">
<el-select id="proxy-method"
v-model="editObject.method"
placeholder=" "
size="small"
class="right-box__select"
popper-class="right-box-select-dropdown prevent-clickoutside"
>
<el-option value="get"></el-option>
<el-option value="post"></el-option>
<el-option value="put"></el-option>
<el-option value="fetch"></el-option>
</el-select>
</el-form-item>
<!--version-->
<el-form-item :label="$t('overall.version')" prop="version">
<el-input id="proxy-version" v-model="editObject.version" placeholder="" size="small" type="text"></el-input>
</el-form-item>
<!--target-->
<el-form-item :label="$t('galaxyProxy.targetUrl')" prop="targetUrl">
<el-input id="proxy-targetUrl" v-model="editObject.targetUrl" placeholder="" size="small" type="text"></el-input>
</el-form-item>
<!--target param-->
<el-form-item :label="$t('galaxyProxy.targetParam')" prop="targetParam">
<!-- <prism-editor class="my-editor" v-model="editObject.targetParam" :highlight="jsonHl" line-numbers></prism-editor>-->
<v-ace-editor
v-model:value="editObject.targetParam"
lang="json"
theme="chrome"
style="height: 300px" />
</el-form-item>
<!--target header-->
<el-form-item :label="$t('galaxyProxy.targetHeader')" prop="targetHeader">
<v-ace-editor
v-model:value="editObject.targetHeader"
lang="json"
theme="chrome"
style="height: 300px" />
</el-form-item>
<!--pre handle-->
<el-form-item label="Pre handle" prop="preHandle">
<v-ace-editor
v-model:value="editObject.preHandle"
lang="javascript"
theme="chrome"
style="height: 300px" />
</el-form-item>
<!--post handle-->
<el-form-item label="Post handle" prop="postHandle">
<v-ace-editor
v-model:value="editObject.postHandle"
lang="javascript"
theme="chrome"
style="height: 300px" />
</el-form-item>
</el-form>
</div>
</div>
<div class="right-box__footer">
<button id="asset-edit-cancel" v-cancel="{object: editObject, func: esc}" class="footer__btn footer__btn--light">
<span>{{$t('overall.cancel')}}</span>
</button>
<button id="asset-edit-save" :class="{'footer__btn--disabled': blockOperation.save}" :disabled="blockOperation.save" class="footer__btn" @click="save">
<span>{{$t('overall.save')}}</span>
</button>
</div>
</div>
</template>
<script>
import rightBoxMixin from '@/mixins/right-box'
import { api } from '@/utils/api'
import { VAceEditor } from 'vue3-ace-editor'
import 'ace-builds/src-noconflict/mode-javascript'
import 'ace-builds/src-noconflict/mode-json'
import 'ace-builds/src-noconflict/theme-chrome'
export default {
name: 'GalaxyProxyBox',
mixins: [rightBoxMixin],
components: {
VAceEditor
},
data () {
return {
url: api.galaxyProxy,
rules: { // 表单校验规则
name: [
{ required: true, message: this.$t('validate.required'), trigger: 'blur' }
]
}
}
}
}
</script>

View File

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

View File

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

View File

@@ -1,329 +0,0 @@
<template>
<el-dialog
v-model="show"
:top="top"
:modal="modal"
:custom-class="customClass"
:show-close="showClose"
:width="width"
@close="closeDebug"
@open="openDebug"
destroy-on-close
>
<div class="debug-wrapper">
<el-select v-model="name" filterable placeholder="Select" class="item item-1" allow-create
@change="changPath">
<template v-for="(proxy,index) in galaxyProxyData" :key="proxy.id">
<el-option :label="proxy.name" :value="index"></el-option>
</template>
</el-select>
<div class="item item-2 sub-grid-send">
<el-select v-model="method" filterable placeholder="Select" style="width:100px;" >
<el-option
v-for="item in methodOptions"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
<el-input v-model="fullPathWithParams" placeholder="Please input" readonly/>
<el-button @click="send">{{$t('galaxyProxy.debug.send')}}</el-button>
</div>
<div class="item item-3 sub-grid-params">
<div class="sub-grid-params-add">
<span style="padding-left: 15px;">{{$t('galaxyProxy.debug.requestParams')}}</span>
<el-button size="mini" @click="handleAddDetails"><i class="cn-icon-add cn-icon"></i></el-button>
</div>
<el-table :data="paramsTableData"
width="100%"
@selection-change="handleDetailSelectionChange"
:row-class-name="rowClassName"
empty-text=""
ref="tb">
<el-table-column type="selection" align="center" label="" width="30px" style="border-left:0px;" />
<el-table-column prop="key" align="center" width="80px" >
<template #header>
<div class="table-operation-title">{{$t('galaxyProxy.debug.key')}}</div>
</template>
<template #default="scope">
<el-input @change="paramsChange(scope.row)" v-model="scope.row.key"></el-input>
</template>
</el-table-column>
<el-table-column prop="value" align="center" >
<template #header>
<div class="table-operation-title">{{$t('galaxyProxy.debug.value')}}</div>
</template>
<template #default="scope">
<el-input @change="paramsChange(scope.row)" v-model="scope.row.value"></el-input>
</template>
</el-table-column>
<el-table-column align="center" style="border-right:0px;" width="70px">
<template #header>
<div class="table-operation-title">{{$t('overall.option')}}</div>
</template>
<template #default="scope">
<div @click="deleteRow(scope.$index)" class="debug__params-delete">{{$t('overall.delete')}}</div>
</template>
</el-table-column>
<template v-slot:empty>
<div style="height:0px;"></div>
</template>
</el-table>
</div>
<div class="item item-4">
<div class="request-header" >{{$t('galaxyProxy.debug.request.header')}}</div>
<div class="request-header-content" ><pre v-html="proxyRequestHeader" style="height: 98%;width: 98%;margin-left: -15px;margin-top: -10px;"></pre></div>
<div class="response-header">{{$t('galaxyProxy.debug.response.header')}}</div>
<div class="response-body">{{$t('galaxyProxy.debug.response.body')}}</div>
<div class="response-header-content"><pre v-html="proxyResponseHeader" style="height: 98%;width: 98%;margin-left: -15px;margin-top: -10px;"></pre></div>
<div class="response-body-content color-pre__style">
<v-ace-editor
v-model:value="proxyResponseBody"
lang="json"
readonly="true"
theme="chrome"
style="height: 100%" />
</div>
</div>
</div>
</el-dialog>
</template>
<script>
import { api } from '@/utils/api'
import { getForDebug, postForDebug } from '@/utils/http'
import axios from 'axios'
import { VAceEditor } from 'vue3-ace-editor'
import 'ace-builds/src-noconflict/mode-javascript'
import 'ace-builds/src-noconflict/mode-json'
import 'ace-builds/src-noconflict/theme-chrome'
export default {
name: 'GalaxyProxyDebug',
components: {
VAceEditor
},
props: {
showDebug: Boolean,
top: {
type: String,
default: '5vh'
},
modal: {
type: Boolean,
default: true
},
customClass: {
type: String,
default: 'proxy-debug__dialog'
},
showClose: {
type: Boolean,
default: true
},
width: { // 高度需要通过customClass自行定义
type: [String, Number],
default: '90vw'
},
curGalaxyProxy: Object // 实体,{ name: '', path: '', icon: icon-class }
},
data () {
return {
show: false,
galaxyProxyData: [],
name: '',
paramsTableData: [
],
// 选中的数据
checkedDetail: {},
checkedRowData: [],
methodOptions: [
{
value: 'GET',
label: 'GET'
},
{
value: 'POST',
label: 'POST'
}
],
method: 'GET',
path: '', // 不带参数的路径用于请求URL
fullPathWithParams: '', // 带参数的完整路径,用于界面显示
proxyResponseBody: '',
proxyRequestHeader: '',
proxyResponseHeader: ''
}
},
setup () {
},
methods: {
handleAddDetails () {
if (this.paramsTableData == undefined) {
this.paramsTableData = new Array()
}
const obj = {}
obj.key = ''
obj.value = ''
this.paramsTableData.push(obj)
},
// 删除当前行
deleteRow (index) {
this.paramsTableData.splice(index, 1)
this.updateUrlParams()
},
rowClassName ({ row, rowIndex }) {
// 把每一行的索引放进row
row.index = rowIndex
},
// 参数发送改变时
paramsChange (row) {
this.fullPathWithParams = this.path
this.checkedDetail = {}
this.checkedRowData.forEach(t => {
if (t.key != '') {
this.checkedDetail[t.key] = t.value
}
})
if (this.fullPathWithParams != '') {
this.updateUrlParams()
}
},
updateUrlParams () {
this.fullPathWithParams = this.path
let params = ''
for (const key in this.checkedDetail) {
if (key != '') {
params = params + key + '=' + this.checkedDetail[key] + '&'
}
}
if (params.endsWith('&')) {
params = params.substring(0, params.length - 1)
}
if (params != '') {
this.fullPathWithParams = this.fullPathWithParams + '?' + params
}
},
// 单选框选中数据
handleDetailSelectionChange (selection) {
this.checkedDetail = {}
this.checkedRowData = selection
selection.forEach((item, index) => {
this.checkedDetail[item.key] = item.value
})
this.updateUrlParams()
},
async getGalaxyProxyData (value) {
await axios.get(api.galaxyProxy + '?pageSize=-1').then(response => {
if (response.status === 200) {
this.galaxyProxyData = response.data.data.list
}
})
},
syntaxUrl (json) {
if (typeof json != 'string') {
json = JSON.stringify(json, undefined, 2)
}
return json = json.replace(/,/g, '&').replace(/{/g, '').replace(/}/g, '').replace(/"/g, '').replace(/:/g, '=').replace(/ /g, '')
},
changPath (index) {
// 修改input的值
this.path = axios.defaults.baseURL + 'interface' + this.galaxyProxyData[index].path
this.updateUrlParams()
this.method = this.galaxyProxyData[index].method.toUpperCase()
},
send () {
// this.path = "galaxy/setting?pageNo=1&pageSize=1"
// 注意有GET与POST两种方式
if (this.method.toUpperCase() == 'GET') {
getForDebug(this.path, this.checkedDetail).then(response => {
this.proxyResponseBody = this.syntaxJson(JSON.parse(JSON.stringify(response.data)))
this.proxyResponseHeader = this.syntaxJson(JSON.parse(JSON.stringify(response.headers)))
this.proxyRequestHeader = this.syntaxJson(JSON.parse(JSON.stringify(response.config.headers)))
this.proxyResponseHeader = this.proxyResponseHeader.replace(/{/g, '').replace(/}/g, '').replace(/"/g, '')
this.proxyRequestHeader = this.proxyRequestHeader.replace(/{/g, '').replace(/}/g, '').replace(/"/g, '')
})
} else if (this.method.toUpperCase() == 'POST') {
postForDebug(this.path, this.checkedDetail).then(response => {
if (response) {
this.proxyResponseBody = this.syntaxJson(JSON.parse(JSON.stringify(response.data)))
this.proxyResponseHeader = this.syntaxJson(JSON.parse(JSON.stringify(response.headers)))
this.proxyRequestHeader = this.syntaxJson(JSON.parse(JSON.stringify(response.config.headers)))
this.proxyResponseHeader = this.proxyResponseHeader.replace(/{/g, '').replace(/}/g, '').replace(/"/g, '')
this.proxyRequestHeader = this.proxyRequestHeader.replace(/{/g, '').replace(/}/g, '').replace(/"/g, '')
}
})
}
},
// 格式化json
syntaxJson (json) {
if (typeof json != 'string') {
json = JSON.stringify(json, undefined, 2)
}
return json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
},
closeDebug () {
this.paramsTableData = []// JSON.parse(JSON.stringify(paramsTableData))
this.proxyResponseBody = ''
this.proxyResponseHeader = ''
this.proxyRequestHeader = ''
this.name = ''
this.path = ''
this.fullPathWithParams = ''
for (const key in this.curGalaxyProxy) {
delete this.curGalaxyProxy[key]
}
},
openDebug () {
if (this.curGalaxyProxy.path) {
this.name = this.curGalaxyProxy.name
this.path = axios.defaults.baseURL + 'interface' + this.curGalaxyProxy.path
this.fullPathWithParams = this.path
this.method = this.curGalaxyProxy.method.toUpperCase()
} else {
this.name = ''
this.path = ''
this.method = 'GET'
}
}
},
mounted () {
// 获取代理名称及路径
this.getGalaxyProxyData()
},
watch: {
showDebug (n, o) {
this.show = n
},
show (n, o) {
this.$emit('update:show-debug', n)
},
path (n, o) {
this.updateUrlParams()
}
}
}
</script>

View File

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

View File

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

View File

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

View File

@@ -104,6 +104,11 @@
> >
</el-switch> </el-switch>
</template> </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> <span v-else>{{scope.row[item.prop] || '-'}}</span>
</template> </template>
</el-table-column> </el-table-column>
@@ -153,6 +158,11 @@ export default {
prop: 'reference', prop: 'reference',
width: 180, width: 180,
show: true show: true
}, {
label: this.$t('overall.color'),
prop: 'color',
width: 180,
show: true
}, { }, {
label: this.$t('overall.remark'), label: this.$t('overall.remark'),
prop: 'description', prop: 'description',
@@ -189,6 +199,23 @@ export default {
show: true, show: true,
width: 80 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'
}
] ]
} }
}, },
@@ -221,6 +248,20 @@ export default {
const t = knowledgeBaseSource.find(t => t.value === type) const t = knowledgeBaseSource.find(t => t.value === type)
return t ? t.name : '' 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,27 +3,18 @@
<div class="card-type-title" v-if="aiTaggingList.length > 0">{{$t('knowledgeBase.intelligenceLearning')}}</div> <div class="card-type-title" v-if="aiTaggingList.length > 0">{{$t('knowledgeBase.intelligenceLearning')}}</div>
<el-checkbox-group v-model="checkList" > <el-checkbox-group v-model="checkList" >
<div class="card-box" v-for="data in aiTaggingList" :key="data.knowledgeId"> <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-content">
<div class="card-operate"> <div class="card-operate">
<el-tooltip <el-switch v-model="data.status"
effect="light" class="card-enable"
trigger="hover" active-color="#38ACD2"
:content="$t('tip.notAvailable')" inactive-color="#C0CEDB"
placement="right" :active-value="1"
popper-class="panel-tooltip" :inactive-value="0"
:before-change="(knowledgeId) => confirmSwitchLearning(data.knowledgeId)"
> >
<el-switch v-model="data.status" </el-switch>
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>
<div class="card-icon"> <div class="card-icon">
<img :src="data.iconUrl"/> <img :src="data.iconUrl"/>
@@ -75,7 +66,9 @@
<div class="center-dialog"> <div class="center-dialog">
<el-dialog v-model="showUpdateDialog" <el-dialog v-model="showUpdateDialog"
:destroy-on-close="true"
:custom-class="showAddUpdateDialog ? 'update-knowledge update-knowledge--upload' : 'update-knowledge'" :custom-class="showAddUpdateDialog ? 'update-knowledge update-knowledge--upload' : 'update-knowledge'"
:before-close="beforeClose"
:after-close="handleClose"> :after-close="handleClose">
<div class="knowledge-update__top" > <div class="knowledge-update__top" >
<div class="update-left__icon"> <div class="update-left__icon">
@@ -86,32 +79,47 @@
<div class="update-title"> <div class="update-title">
<div class="card-title-name" :title="updateKnowledge.label">{{updateKnowledge.label}}</div> <div class="card-title-name" :title="updateKnowledge.label">{{updateKnowledge.label}}</div>
</div> </div>
<el-tooltip <el-switch v-model="updateKnowledge.status"
effect="light" active-color="#38ACD2"
trigger="hover" inactive-color="#C0CEDB"
v-if="showEnable" :active-value="1"
:content="$t('tip.notAvailable')" :inactive-value="0"
placement="right" :before-change="(knowledgeId) => confirmSwitchLearning(updateKnowledge.knowledgeId)"
popper-class="panel-tooltip" v-if="updateKnowledge.source === 'cn_psiphon3_ip'"
> >
<el-switch v-model="updateKnowledge.status" </el-switch>
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>
<div class="knowledge-desc" :title="updateKnowledge.desc">{{updateKnowledge.desc ? updateKnowledge.desc : '—'}}</div> <div class="knowledge-desc" :title="updateKnowledge.desc">{{updateKnowledge.desc ? updateKnowledge.desc : '—'}}</div>
</div> </div>
</div> </div>
<template v-if="!showAddUpdateDialog"> <template v-if="!showAddUpdateDialog">
<div class="knowledge-update" > <div class="knowledge-update__tab" v-if="showEnable">
<div class="update-title"> <el-tabs v-model="activeTab"
<div class="card-title-name">update record</div> 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> </div>
<div class="update-operate"> <div class="update-operate">
<button :title="$t('overall.update')" class="top-tool-btn--update" <button :title="$t('overall.update')" class="top-tool-btn--update"
@@ -121,23 +129,92 @@
</button> </button>
</div> </div>
</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" <el-table ref="updateDataTable"
border border
:data="updateHistoryList" :data="updateHistoryList"
@selection-change="secondSelectionChange" @selection-change="secondSelectionChange"
width="100%" width="100%"
class="update-dialog__table" 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;" 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;"> header-cell-style="padding:8px 0px;font-size: 12px;color: #353636;font-weight: 500;border-right:none;">
<el-table-column prop="opTime" label="Update time" width="150" ></el-table-column> <el-table-column prop="opTime" :label="$t('entities.tab.informationAggregation.updateTime')" width="150" >
<el-table-column prop="user" label="Operating user" 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'">
<template #default="scope" :column="item"> <template #default="scope" :column="item">
<span>{{$_.get(scope.row, 'user.name', '-')}}</span> <span>{{$_.get(scope.row, 'user.name', '-')}}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="commitVersion" label="Version information" width="150" ></el-table-column> <el-table-column prop="commitVersion" :label="$t('overall.version')" width="150" ></el-table-column>
<el-table-column prop="description" label="Description"></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> </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>
<template v-if="showAddUpdateDialog"> <template v-if="showAddUpdateDialog">
<div class="update-knowledge-form"> <div class="update-knowledge-form">
@@ -187,6 +264,7 @@
<el-dialog v-model="showConfirmDialog" <el-dialog v-model="showConfirmDialog"
:title="$t('overall.tips')" :title="$t('overall.tips')"
custom-class="update-knowledge-tip" custom-class="update-knowledge-tip"
:width="480"
:before-close="handleConfirmClose"> :before-close="handleConfirmClose">
<div class="dialog-message">{{$t('knowledge.updateTips')}}</div> <div class="dialog-message">{{$t('knowledge.updateTips')}}</div>
<template #footer> <template #footer>
@@ -196,18 +274,37 @@
</span> </span>
</template> </template>
</el-dialog> </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> </div>
</template> </template>
<script> <script>
import table from '@/mixins/table' import table from '@/mixins/table'
import Loading from '@/components/common/Loading' import Loading from '@/components/common/Loading'
import { getSecond, getMillisecond, xAxisTimeFormatter, xAxisTimeRich } from '@/utils/date-util'
import { knowledgeCategoryValue, unitTypes, storageKey, builtInKnowledgeBaseBasicInfo } from '@/utils/constants' import { knowledgeCategoryValue, unitTypes, storageKey, builtInKnowledgeBaseBasicInfo } from '@/utils/constants'
import { ref } from 'vue' import { ref, shallowRef } from 'vue'
import { api } from '@/utils/api' import { api } from '@/utils/api'
import { detectionTooltipFormatter } from '@/views/charts/charts/tools'
import ChartNoData from '@/views/charts/charts/ChartNoData'
import axios from 'axios' import axios from 'axios'
import _ from 'lodash' import _ from 'lodash'
import * as echarts from 'echarts'
import unitConvert from '@/utils/unit-convert' import unitConvert from '@/utils/unit-convert'
export default { export default {
name: 'knowledgeBaseTableForCard', name: 'knowledgeBaseTableForCard',
mixins: [table], mixins: [table],
@@ -221,7 +318,8 @@ export default {
} }
}, },
components: { components: {
Loading Loading,
ChartNoData
}, },
data () { data () {
return { return {
@@ -237,12 +335,57 @@ export default {
updateHistoryList: [], updateHistoryList: [],
updateObject: {}, updateObject: {},
currentVersion: 0, currentVersion: 0,
uploadLoading: false 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') }
]
} }
}, },
setup () { setup () {
// 没上传过文件的提示 // 没上传过文件的提示
const uploadErrorTip = ref('') const uploadErrorTip = ref('')
const nowMill = window.$dayJs.tz().valueOf()
const timeFilter = ref({
startTime: nowMill - 1000 * 60 * 60 * 24,
endTime: nowMill,
dateRangeValue: 1440
})
return { return {
baseUrl: BASE_CONFIG.baseUrl, baseUrl: BASE_CONFIG.baseUrl,
apiVersion: BASE_CONFIG.apiVersion, apiVersion: BASE_CONFIG.apiVersion,
@@ -252,13 +395,174 @@ export default {
uploadErrorTip, uploadErrorTip,
fileTypeLimit: '.csv', fileTypeLimit: '.csv',
fileList: ref([]), fileList: ref([]),
uploadFileSizeLimit: 100 * 1024 * 1024 uploadFileSizeLimit: 1024 * 1024 * 1024,
myChart: shallowRef(null),
chartOption: shallowRef(null),
timeFilter
} }
}, },
methods: { 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) { fileChange (file, fileList) {
console.info(!_.endsWith(file.name, '.csv'))
console.info(file.size > this.uploadFileSizeLimit)
// 判断后缀,仅支持.csv // 判断后缀,仅支持.csv
if (!_.endsWith(file.name, '.csv')) { if (!_.endsWith(file.name, '.csv')) {
this.fileList = [] this.fileList = []
@@ -287,10 +591,10 @@ export default {
if (response.code === 200) { */ if (response.code === 200) { */
this.$message.success(this.$t('tip.success')) this.$message.success(this.$t('tip.success'))
this.showAddUpdateDialog = false this.showAddUpdateDialog = false
axios.get(api.knowledgeBaseLog + '/' + this.updateKnowledge.knowledgeId, { params: { pageSize: 999 } }).then(res => { this.getCurTabData()
this.updateHistoryList = res.data.data.list if (this.updateKnowledge.source === 'cn_psiphon3_ip') {
this.currentVersion = this.updateHistoryList[0].commitVersion + 1 this.init()
}) }
/* } else { /* } else {
this.$message.error(this.$t('tip.uploadFailed', { msg: response.message })) this.$message.error(this.$t('tip.uploadFailed', { msg: response.message }))
} */ } */
@@ -325,6 +629,13 @@ export default {
data.isSelected = val data.isSelected = val
this.$emit('checkboxStatusChange', val, data) this.$emit('checkboxStatusChange', val, data)
}, },
beforeClose (done) {
if (this.myChart) {
this.myChart.dispose()
this.myChart = null
}
done()
},
handleClose () { handleClose () {
this.showUpdateDialog = false this.showUpdateDialog = false
this.showAddUpdateDialog = false this.showAddUpdateDialog = false
@@ -340,13 +651,16 @@ export default {
this.showUpdateDialog = true this.showUpdateDialog = true
this.showAddUpdateDialog = false this.showAddUpdateDialog = false
}, },
jumpToUpdatePage (data, showEnable) { async jumpToUpdatePage (data, showEnable) {
axios.get(api.knowledgeBaseLog + '/' + data.knowledgeId, { params: { pageSize: 999 } }).then(res => { this.updateKnowledge = data
this.updateKnowledge = data this.showEnable = showEnable
this.updateHistoryList = res.data.data.list await this.getCurTabData()
this.currentVersion = this.updateHistoryList[0].commitVersion + 1 if (data.source === 'cn_psiphon3_ip') {
this.showEnable = showEnable await this.init()
this.showUpdate() }
this.showUpdate()
this.$nextTick(() => {
this.handleActiveBar()
}) })
}, },
uploadRecord () { uploadRecord () {
@@ -355,6 +669,43 @@ export default {
this.updateObject.label = this.updateKnowledge.label this.updateObject.label = this.updateKnowledge.label
this.updateObject.description = '' 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 () { clearSelect () {
this.$nextTick(() => { this.$nextTick(() => {
this.checkList = [] this.checkList = []
@@ -366,18 +717,10 @@ export default {
}) })
}, },
mouseenter (card) { mouseenter (card) {
this.tableData.forEach(t => { card.showUpdate = true
if (t.knowledgeId === card.knowledgeId) {
card.showUpdate = true
}
})
}, },
mouseleave (card) { mouseleave (card) {
this.tableData.forEach(t => { card.showUpdate = false
if (t.knowledgeId === card.knowledgeId) {
card.showUpdate = false
}
})
}, },
del (data) { del (data) {
this.$emit('delete', data) this.$emit('delete', data)
@@ -394,9 +737,48 @@ export default {
dataType: dataType 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
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: { watch: {
tabType (n) {
this.$nextTick(() => {
this.handleActiveBar()
})
},
timeFilter: {
handler () {
if (this.updateKnowledge.source === 'cn_psiphon3_ip') {
this.init()
}
this.$nextTick(() => {
this.handleActiveBar()
})
}
},
tableData: { tableData: {
handler (n) { handler (n) {
if (this.tableData && this.tableData.length > 0) { if (this.tableData && this.tableData.length > 0) {
@@ -420,15 +802,35 @@ export default {
} }
} }
}, },
activeTab (n) {
if (n === 'updateRecord') {
if (this.myChart) {
this.myChart.dispose()
this.myChart = null
}
}
},
showAddUpdateDialog: { showAddUpdateDialog: {
handler (n) { handler (n) {
if (!n) { if (!n) {
this.fileList = [] this.fileList = []
if (this.updateKnowledge.source === 'cn_psiphon3_ip') {
this.init()
}
} else {
if (this.myChart) {
this.myChart.dispose()
this.myChart = null
}
} }
} }
} }
}, },
mounted () { mounted () {
this.myChart = null
this.chartOption = null
window.addEventListener('resize', this.resize)
this.aiTaggingList = [] this.aiTaggingList = []
this.websketchList = [] this.websketchList = []
this.tableData.forEach(item => { this.tableData.forEach(item => {
@@ -440,6 +842,22 @@ 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: { computed: {
uploadParams () { uploadParams () {
return { return {
@@ -447,6 +865,20 @@ export default {
action: 'overwrite', action: 'overwrite',
description: this.updateObject.description 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.confirmEnablePsiphon3') + '?'
} else if (find.status === 1) {
tip = this.$t('tip.confirmDisablePsiphon3') + '?'
}
}
}
return tip
} }
} }
} }

View File

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

View File

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

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

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

View File

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

View File

@@ -20,7 +20,7 @@ const routes = [
component: () => import('@/views/report/Report') component: () => import('@/views/report/Report')
}, },
{ {
path: '/entityExplorer', path: '/entity',
component: () => import('@/views/entityExplorer/EntityExplorer') component: () => import('@/views/entityExplorer/EntityExplorer')
}, },
{ {
@@ -93,21 +93,16 @@ const routes = [
component: () => import('@/views/administration/I18n') component: () => import('@/views/administration/I18n')
}, },
{ {
name: 'Chart', path: '/detectionPolicy',
path: '/chart', component: () => import('@/views/detections/detectionPolicies/Index')
component: () => import('@/views/administration/Chart')
}, },
{ {
path: '/detectionsNew', path: '/detectionPolicy/create',
component: () => import('@/views/detectionsNew/Index') component: () => import('@/views/detections/detectionPolicies/PolicyForm')
}, },
{ {
path: '/detection/policies', path: '/detectionPolicy/edit',
component: () => import('@/views/detectionsNew/Index') component: () => import('@/views/detections/detectionPolicies/PolicyForm')
},
{
path: '/detection/policies/create',
component: () => import('@/views/detectionsNew/DetectionForm')
} }
] ]
} }

View File

@@ -60,6 +60,29 @@ const panel = {
routerHistoryList: [], // 路由跳转记录列表 routerHistoryList: [], // 路由跳转记录列表
dnsQtypeMapData: [], dnsQtypeMapData: [],
dnsRcodeMapData: [], 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方便原有逻辑计算 chartTabList: null // chartTabs组件的tab状态点击列表初始化为null方便原有逻辑计算
}, },
mutations: { mutations: {
@@ -153,6 +176,56 @@ const panel = {
setRouterHistoryList (state, list) { setRouterHistoryList (state, list) {
state.routerHistoryList = 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) { setChartTabList (state, list) {
state.chartTabList = list state.chartTabList = list
} }
@@ -232,6 +305,12 @@ const panel = {
}, },
getChartTabList (state) { getChartTabList (state) {
return state.chartTabList return state.chartTabList
},
scoreBaseReady (state) {
return state.scoreBase.isReady
},
getScoreBase (state) {
return state.scoreBase
} }
}, },
actions: { actions: {

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -43,7 +43,7 @@ export function rTime (date) {
} }
// 日期转换为时间戳 // 日期转换为时间戳
export function toTime (date) { export function toTime (date) {
return new Date(date).getTime() return new Date(parseFloat(date)).getTime()
} }
// 时间格式转换 // 时间格式转换
export function dateFormat (date, format = 'YYYY-MM-DD HH:mm:ss') { export function dateFormat (date, format = 'YYYY-MM-DD HH:mm:ss') {
@@ -121,9 +121,9 @@ export function xAxisTimeFormatter (value) {
':' + ':' +
(date.getMinutes() < 10 ? `0${date.getMinutes()}` : date.getMinutes()) (date.getMinutes() < 10 ? `0${date.getMinutes()}` : date.getMinutes())
// 如果是一天的开始 // 如果是一天的开始
if (date.getTime() === dayStart.getTime()) { if (getSecond(date.getTime()) === getSecond(dayStart.getTime())) {
return '{day|' + dateFormat(date, 'YYYY-MM-DD') + '}' return '{day|' + dateFormat(date, 'YYYY-MM-DD') + '}'
} else if (date.getTime() === hourStart.getTime()) { } else if (getSecond(date.getTime()) === getSecond(hourStart.getTime())) {
return '{hour|' + HHmm + '}' return '{hour|' + HHmm + '}'
} else { } else {
return HHmm return HHmm
@@ -137,3 +137,69 @@ export const xAxisTimeRich = {
fontWeight: 'bold' fontWeight: 'bold'
} }
} }
function switchDateTypeByStr (str) {
switch (str) {
// case 'Y':
// return 'years'
// case 'M':
// return 'months'
// case 'W':
// return 'weeks'
case 'D':
return 'days'
case 'H':
return 'hours'
case 'M':
return 'minutes'
case 'S':
return 'seconds'
}
}
function switchValueByDateType (str) {
switch (str) {
// case 'years':
// return 'Y'
// case 'months':
// return 'M'
// case 'weeks':
// return 'W'
case 'days':
return 'D'
case 'hours':
return 'H'
case 'minutes':
return 'M'
case 'seconds':
return 'S'
}
}
export function getTimeByDurations (str) {
if (str && str.indexOf('P') === 0) {
const obj = {
type: '',
value: ''
}
for (let i = 1; i < str.length; i++) {
const item = str[i]
// 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 = { export const dataForNpmTrafficLine = {
tabs: [ tabs: [
{ {
name: _this.$t('network.total'), name: 'network.total',
show: true, show: true,
positioning: 0, positioning: 0,
data: [], data: [],
unitType: 'number' unitType: 'number'
}, },
{ {
name: _this.$t('network.inbound'), name: 'network.inbound',
show: true, show: true,
positioning: 1, positioning: 1,
data: [], data: [],
unitType: 'number' unitType: 'number'
}, },
{ {
name: _this.$t('network.outbound'), name: 'network.outbound',
show: true, show: true,
positioning: 2, positioning: 2,
data: [], data: [],
unitType: 'number' unitType: 'number'
}, },
{ {
name: _this.$t('network.internal'), name: 'network.internal',
show: true, show: true,
positioning: 3, positioning: 3,
data: [], data: [],
unitType: 'number' unitType: 'number'
}, },
{ {
name: _this.$t('network.through'), name: 'network.through',
show: true, show: true,
positioning: 4, positioning: 4,
data: [], data: [],
unitType: 'number' unitType: 'number'
}, },
{ {
name: _this.$t('network.other'), name: 'network.other',
show: true, show: true,
positioning: 5, positioning: 5,
data: [], data: [],
@@ -58,21 +58,21 @@ export const dataForNpmTrafficLine = {
} }
], ],
npmQuantity: [ npmQuantity: [
{ name: _this.$t('networkAppPerformance.tcpConnectionEstablishLatency'), show: true, positioning: 0, data: [], unitType: unitTypes.time, index: 0 }, { name: '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: '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: '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: '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 } { name: 'overall.packetRetrans', show: true, positioning: 0, data: [], unitType: unitTypes.percent, index: 4 }
], ],
metricOptions: [ metricOptions: [
/* { value: 'Bits/s', label: 'Bits/s' }, /* { value: 'Bits/s', label: 'Bits/s' },
{ value: 'Packets/s', label: 'Packets/s' }, { value: 'Packets/s', label: 'Packets/s' },
{ value: 'Sessions/s', label: 'Sessions/s' }, */ { value: 'Sessions/s', label: 'Sessions/s' }, */
{ value: 'establishLatencyMs', label: _this.$t('networkAppPerformance.tcpConnectionEstablishLatency') }, { value: 'establishLatencyMs', label: 'networkAppPerformance.tcpConnectionEstablishLatency' },
{ value: 'httpResponseLatency', label: _this.$t('networkAppPerformance.httpResponse') }, { value: 'httpResponseLatency', label: 'networkAppPerformance.httpResponse' },
{ value: 'sslConLatency', label: _this.$t('networkAppPerformance.sslResponseLatency') }, { value: 'sslConLatency', label: 'networkAppPerformance.sslResponseLatency' },
{ value: 'tcpLostlenPercent', label: _this.$t('networkAppPerformance.packetLoss') }, { value: 'tcpLostlenPercent', label: 'networkAppPerformance.packetLoss' },
{ value: 'pktRetransPercent', label: _this.$t('overall.packetRetrans') } { value: 'pktRetransPercent', label: 'overall.packetRetrans' }
] ]
} }
@@ -175,9 +175,9 @@ export const dataForLinkTrafficLine = {
export const dataForNpmLine = { export const dataForNpmLine = {
chartOptionLineData: [ chartOptionLineData: [
{ legend: _this.$t('network.total'), index: 0, invertTab: true, show: false, color: '#749F4D' }, { legend: 'network.total', index: 0, invertTab: true, show: false, color: '#749F4D' },
{ legend: _this.$t('network.inbound'), index: 1, invertTab: true, show: false, color: '#98709B' }, { legend: 'network.inbound', index: 1, invertTab: true, show: false, color: '#98709B' },
{ legend: _this.$t('network.outbound'), index: 2, invertTab: true, show: false, color: '#E5A219' } { legend: 'network.outbound', index: 2, invertTab: true, show: false, color: '#E5A219' }
], ],
npmLineColor: [ npmLineColor: [
{ legend: '', color: '#749F4D' }, { legend: '', color: '#749F4D' },
@@ -221,27 +221,27 @@ export const dataForDnsTrafficLine = {
export const dataForNpmEventsHeader = { export const dataForNpmEventsHeader = {
chartData: [ chartData: [
{ {
eventSeverity: 'critical', eventSeverity: 'overall.critical',
count: '-', count: '-',
index: 0 index: 0
}, },
{ {
eventSeverity: 'high', eventSeverity: 'overall.high',
count: '-', count: '-',
index: 1 index: 1
}, },
{ {
eventSeverity: 'medium', eventSeverity: 'overall.medium',
count: '-', count: '-',
index: 2 index: 2
}, },
{ {
eventSeverity: 'low', eventSeverity: 'overall.low',
count: '-', count: '-',
index: 3 index: 3
}, },
{ {
eventSeverity: 'info', eventSeverity: 'overall.info',
count: '-', count: '-',
index: 4 index: 4
} }
@@ -334,11 +334,91 @@ export const columnList1 = [
} }
} }
] ]
const securityEvent = [
{
name: 'event_type',
type: 'string',
label: 'event_type',
doc: {
constraints: {
operator_functions: '=,in,like'
}
}
},
{
name: 'event_name',
type: 'string',
label: 'event_name',
doc: {
constraints: {
operator_functions: '=,in,like'
}
}
},
{
name: 'severity',
type: 'string',
label: 'severity',
doc: {
constraints: {
operator_functions: '=,in,like'
}
}
},
{
name: 'offender_ip',
type: 'string',
label: 'offender Ip',
doc: {
constraints: {
operator_functions: '=,in,like'
}
}
},
{
name: 'victim_ip',
type: 'string',
label: 'victim Ip',
doc: {
constraints: {
operator_functions: '=,in,like'
}
}
},
{
name: 'domain',
type: 'string',
label: 'domain',
doc: {
constraints: {
operator_functions: '=,in,like'
}
}
},
{
name: 'app',
type: 'string',
label: 'app',
doc: {
constraints: {
operator_functions: '=,in,like'
}
}
}
]
let schemaEntityExplore = localStorage.getItem(storageKey.schemaEntityExplore) const schema = localStorage.getItem(storageKey.schemaEntityExplore)
schemaEntityExplore = schemaEntityExplore ? JSON.parse(schemaEntityExplore).entityMetadata.searchColumns : columnList1 const schemaEntityExplore = schema ? JSON.parse(schema).entityMetadata.searchColumns : columnList1
export const columnList = schemaEntityExplore export const columnList = schemaEntityExplore
let securityEventMetadata = securityEvent
if (schema) {
if (JSON.parse(schema).securityEventMetadata) {
securityEventMetadata = JSON.parse(schema).securityEventMetadata.searchColumns
}
}
export const schemaDetectionSecurity = securityEventMetadata
export const operatorList = ['=', '!=', /* '>', '<', '>=', '<=', */'IN', 'NOT IN', 'LIKE', 'NOT LIKE'] export const operatorList = ['=', '!=', /* '>', '<', '>=', '<=', */'IN', 'NOT IN', 'LIKE', 'NOT LIKE']
export const connectionList = [ export const connectionList = [
{ {

View File

@@ -784,7 +784,7 @@ export function getChainRatio (current, prev) {
} }
} }
export function computeScore (data) { export function computeScore (data, scoreBase) {
let score = 0 let score = 0
let k = 0 let k = 0
let totalScore = 0 let totalScore = 0
@@ -799,26 +799,14 @@ export function computeScore (data) {
} else if (t === 'httpResponseLatency' || t === 'sslConLatency') { } else if (t === 'httpResponseLatency' || t === 'sslConLatency') {
k = 0.05 k = 0.05
} }
if (t === 'establishLatencyMs' || t === 'httpResponseLatency' || t === 'sslConLatency') { if (!data[t] && data[t] !== 0) {
if (!data[t] && data[t] !== 0) { score = 1
score = 1 } else if (data[t] <= scoreBase[t].p10) {
} else if (data[t] <= 50) { score = 1
score = 1 } else if (data[t] >= scoreBase[t].p90) {
} else if (data[t] > 200) { score = 0
score = 0 } else {
} else { score = (data[t] - scoreBase[t].p90) / (scoreBase[t].p10 - scoreBase[t].p90)
score = (data[t] - 200) / (50 - 200)
}
} else if (t === 'tcpLostlenPercent' || t === 'pktRetransPercent') {
if (!data[t] && data[t] !== 0) {
score = 1
} else if (data[t] <= 0.01) {
score = 1
} else if (data[t] > 0.05) {
score = 0
} else {
score = (data[t] - 0.05) / (0.01 - 0.05)
}
} }
scoreArr.push(score * k) scoreArr.push(score * k)
}) })
@@ -1318,12 +1306,13 @@ export function numberWithCommas (num) {
* @returns {string} * @returns {string}
*/ */
export function switchStatus (status) { export function switchStatus (status) {
switch (status) { switch (status + '') {
case 0: case '0':
return 'Disabled' return 'detection.create.disabled'
case 1: case '1':
return 'Enabled' return 'detection.create.enabled'
} }
return null
} }
/** /**
@@ -1356,3 +1345,18 @@ export function beforeRouterPush () {
} }
store.commit('setRouterHistoryList', historyList) 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

@@ -1,93 +0,0 @@
<template>
<div style="height: 100%;">
<cn-data-list
ref="dataList"
:tableId="tableId"
v-model:custom-table-title="tools.customTableTitle"
:api="url"
:from="fromRoute.chart"
:layout="['columnCustomize','elementSet','search']"
@search="search"
>
<template #top-tool-left>
<button id="chart-add"
class="top-tool-btn margin-r-10 top-tool-btn--create"
@click="add">
<i class="cn-icon-xinjian cn-icon"></i>
<span>{{$t('overall.create')}}</span>
</button>
<button id="chart-edit" class="top-tool-btn margin-r-10" :disabled="disableEdit"
@click="editSelectRecord">
<i class="cn-icon-edit cn-icon"></i>
<span>{{$t('overall.edit')}}</span>
</button>
<button id="chart-delete" class="top-tool-btn margin-r-10" :disabled="disableDelete"
@click="delBatch">
<i class="cn-icon-delete cn-icon"></i>
<span>{{$t('overall.delete')}}</span>
</button>
</template>
<template #default>
<loading :loading="loading"></loading>
<chart-table
ref="dataTable"
:api="url"
:isNoData="isNoData"
:custom-table-title="tools.customTableTitle"
:height="mainTableHeight"
:table-data="tableData"
@delete="del"
@edit="edit"
@orderBy="tableDataSort"
@reload="getTableData"
@selectionChange="selectionChange"
/>
</template>
<template #pagination>
<pagination ref="pagination" :page-obj="pageObj" :table-id="tableId" @pageNo='pageNo' @pageSize='pageSize'></pagination>
</template>
</cn-data-list>
<el-drawer
v-model="rightBox.show"
direction="rtl"
custom-class="common-right-box"
:size="700"
:with-header="false"
destroy-on-close>
<chart-box
:object="object"
@close="closeRightBox"
/>
</el-drawer>
</div>
</template>
<script>
import cnDataList from '@/components/table/CnDataList'
import dataListMixin from '@/mixins/data-list'
import chartTable from '@/components/table/administration/ChartTable'
import chartBox from '@/components/rightBox/settings/ChartBox'
import { api } from '@/utils/api'
export default {
name: 'Chart',
mixins: [dataListMixin],
components: {
cnDataList,
chartTable,
chartBox
},
data () {
return {
url: api.chart,
listUrl: api.chartList,
blankObject: { // 空白对象
id: '',
name: '',
params: '',
i18n: ''
},
tableId: 'chartTable'
}
}
}
</script>

View File

@@ -1,159 +0,0 @@
<template>
<div style="height: 100%;">
<cn-data-list
ref="dataList"
:tableId="tableId"
v-model:custom-table-title="tools.customTableTitle"
:api="url"
:from="fromRoute.galaxyProxy"
:layout="['columnCustomize','elementSet','search']"
@search="search"
>
<template v-slot:top-tool-left>
<button id="galaxy-proxy-clear-cache" class="top-tool-btn margin-r-10" :title="$t('overall.clearCache')"
type="button" @click="clearCache">
<i class="cn-icon cn-icon-clear-cache"></i>
</button>
<button id="galaxy-proxy-add"
class="top-tool-btn margin-r-10 top-tool-btn--create"
@click="add">
<i class="cn-icon-xinjian cn-icon"></i>
<span>{{$t('overall.create')}}</span>
</button>
<button id="galaxy-edit" class="top-tool-btn margin-r-10" :disabled="disableEdit"
@click="editSelectRecord">
<i class="cn-icon-edit cn-icon"></i>
<span>{{$t('overall.edit')}}</span>
</button>
<button id="galaxy-delete" class="top-tool-btn margin-r-10"
@click="delBatch">
<i class="cn-icon-delete cn-icon"></i>
<span>{{$t('overall.delete')}}</span>
</button>
<button id="galaxy-proxy-debug" class="top-tool-btn margin-r-10" :title="$t('overall.debug')"
type="button" @click="debug(true,{})">
<i class="cn-icon-category cn-icon"></i>
</button>
<top-tool-more-options
ref="export"
id="model"
:params="searchLabel"
class="top-tool-export margin-l-10 margin-r-10"
export-file-name="galaxyProxy"
export-url="/galaxy/setting/export"
import-url="/galaxy/setting/import"
@afterImport="getTableData"
>
<template v-slot:before>
</template>
</top-tool-more-options>
</template>
<template v-slot:default>
<loading :loading="loading"></loading>
<galaxy-proxy-table
ref="dataTable"
:api="url"
:isNoData="isNoData"
:custom-table-title="tools.customTableTitle"
:height="mainTableHeight"
:table-data="tableData"
@delete="del"
@edit="edit"
@orderBy="tableDataSort"
@reload="getTableData"
@selectionChange="selectionChange"
@copy="copy"
@debug="debugRow"
></galaxy-proxy-table>
</template>
<!-- 分页组件 -->
<template #pagination>
<pagination ref="pagination" :page-obj="pageObj" :table-id="tableId" @pageNo='pageNo' @pageSize='pageSize'></pagination>
</template>
</cn-data-list>
<el-drawer
v-model="rightBox.show"
direction="rtl"
custom-class="common-right-box"
:size="700"
:with-header="false"
destroy-on-close>
<galaxy-proxy-box :object="object" @close="closeRightBox"></galaxy-proxy-box>
</el-drawer>
</div>
<galaxy-proxy-debug
v-model:show-debug="showDebug"
top="5vh"
:show-close="false"
:curGalaxyProxy="curGalaxyProxy"></galaxy-proxy-debug>
</template>
<script>
import cnDataList from '@/components/table/CnDataList'
import galaxyProxyBox from '@/components/rightBox/settings/GalaxyProxyBox'
import galaxyProxyTable from '@/components/table/administration/GalaxyProxyTable'
import dataListMixin from '@/mixins/data-list'
import { api } from '@/utils/api'
import axios from 'axios'
import TopToolMoreOptions from '@/components/common/popBox/TopToolMoreOptions'
import galaxyProxyDebug from '@/components/setting/GalaxyProxyDebug'
export default {
name: 'GalaxyProxy',
components: {
cnDataList,
galaxyProxyBox,
galaxyProxyTable,
TopToolMoreOptions,
galaxyProxyDebug
},
mixins: [dataListMixin],
data () {
return {
url: api.galaxyProxy,
tableId: 'galaxySettingTable', // 需要分页的table的id用于记录每页数量
blankObject: { // 空白对象
name: ''
},
showDebug: false,
curGalaxyProxy: {}
}
},
methods: {
edit (u) {
axios.get(`${this.url}/${u.id}`).then(response => {
if (response.status === 200) {
const editObject = response.data.data
editObject.targetHeader || (editObject.targetHeader = '')
editObject.preHandle || (editObject.preHandle = '')
editObject.postHandle || (editObject.postHandle = '')
editObject.targetParam || (editObject.targetParam = '')
this.object = editObject
this.rightBox.show = true
}
})
},
debug (isTopDebug, u) {
if (!isTopDebug && u) {
this.curGalaxyProxy = JSON.parse(JSON.stringify(u))
}
this.showDebug = true
},
debugRow (u) {
this.debug(false, u)
},
clearCache () {
axios.put(`${this.url}/clearCache`).then(response => {
if (response.status === 200) {
this.$message({ duration: 2000, type: 'success', message: this.$t('tip.success') })
} else {
this.$message.error(response.data.message)
}
}).catch(() => {
this.$message.error(this.$t('tip.unknownError'))
})
}
}
}
</script>

View File

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

View File

@@ -24,7 +24,7 @@
<el-select <el-select
size="mini" size="mini"
v-model="metric" v-model="metric"
placeholder="" placeholder=" "
popper-class="common-select" popper-class="common-select"
v-if="showMetric" v-if="showMetric"
:popper-append-to-body="false" :popper-append-to-body="false"
@@ -104,7 +104,9 @@ export default {
dnsRcodeMapData: [], dnsRcodeMapData: [],
dnsQtypeMapData: [], dnsQtypeMapData: [],
score: null, score: null,
curTabState: curTabState curTabState: curTabState,
performanceData: {},
scoreDataState: false // 评分数据是否加载完成
} }
}, },
computed: { computed: {
@@ -114,22 +116,32 @@ export default {
// 显示顶部的Metric单位选项标识 // 显示顶部的Metric单位选项标识
showMetric () { showMetric () {
return this.panelType === panelTypeAndRouteMapping.networkOverview || this.panelType === panelTypeAndRouteMapping.networkOverviewDrillDown return this.panelType === panelTypeAndRouteMapping.networkOverview || this.panelType === panelTypeAndRouteMapping.networkOverviewDrillDown
},
scoreBaseState () {
return this.$store.getters.scoreBaseReady
} }
}, },
watch: { watch: {
// npmThirdLevelMenuScore: {
// deep: true,
// immediate: true,
// handler (n) {
// this.score = n
// }
// }
timeFilter: { timeFilter: {
handler () { handler () {
if (this.$route.path === '/panel/networkAppPerformance' && (this.lineQueryCondition || this.networkOverviewBeforeTab)) { if (this.$route.path === '/panel/networkAppPerformance') {
this.scoreCalculation() this.$store.commit('resetScoreBase')
this.queryScoreBase()
if (this.lineQueryCondition || this.networkOverviewBeforeTab) {
this.scoreCalculation()
}
} }
} }
},
scoreBaseState (n) {
if (n && this.scoreDataState) {
this.handleScoreData()
}
},
scoreDataState (n) {
if (n && this.scoreBaseState) {
this.handleScoreData()
}
} }
}, },
async mounted () { async mounted () {
@@ -212,8 +224,14 @@ export default {
return chart return chart
}) })
}) })
if (this.$route.path === '/panel/networkAppPerformance' && (this.lineQueryCondition || this.networkOverviewBeforeTab)) { if (this.$route.path === '/panel/networkAppPerformance') {
this.scoreCalculation() if (this.lineQueryCondition || this.networkOverviewBeforeTab) {
this.scoreCalculation()
}
}
if (this.$route.path === '/panel/networkAppPerformance' || this.$route.path === '/panel/linkMonitor') {
this.$store.commit('resetScoreBase')
this.queryScoreBase()
} }
}, },
setup (props) { setup (props) {
@@ -396,6 +414,44 @@ export default {
}) })
overwriteUrl(newUrl) 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 () { scoreCalculation () {
let condition = '' let condition = ''
let url = '' let url = ''
@@ -443,18 +499,21 @@ export default {
url = api.npm.overview.networkAnalysis url = api.npm.overview.networkAnalysis
} }
if ((type && condition) || type) { if ((type && condition) || type) {
this.scoreDataState = false
this.performanceData = {}
params.type = params.type || type params.type = params.type || type
axios.get(url, { params }).then(res => { axios.get(url, { params }).then(res => {
if (res.status === 200) { if (res.status === 200) {
const data = { this.performanceData = {
establishLatencyMs: _.get(res, 'data.data.result.establishLatencyMsAvg', null), establishLatencyMs: _.get(res, 'data.data.result.establishLatencyMsAvg', null),
httpResponseLatency: _.get(res, 'data.data.result.httpResponseLatencyAvg', null), httpResponseLatency: _.get(res, 'data.data.result.httpResponseLatencyAvg', null),
sslConLatency: _.get(res, 'data.data.result.sslConLatencyAvg', null), sslConLatency: _.get(res, 'data.data.result.sslConLatencyAvg', null),
tcpLostlenPercent: _.get(res, 'data.data.result.tcpLostlenPercentAvg', null), tcpLostlenPercent: _.get(res, 'data.data.result.tcpLostlenPercentAvg', null),
pktRetransPercent: _.get(res, 'data.data.result.pktRetransPercentAvg', null) pktRetransPercent: _.get(res, 'data.data.result.pktRetransPercentAvg', null)
} }
this.score = computeScore(data)
} }
}).finally(() => {
this.scoreDataState = true
}) })
} }
}, },
@@ -467,6 +526,9 @@ export default {
} }
}) })
window.open(href, '_blank') window.open(href, '_blank')
},
handleScoreData () {
this.score = computeScore(this.performanceData, this.$store.getters.getScoreBase)
} }
}, },
/** /**

View File

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

View File

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

View File

@@ -50,7 +50,7 @@
<el-select <el-select
size="mini" size="mini"
v-model="metric" v-model="metric"
placeholder="" placeholder=" "
popper-class="common-select" popper-class="common-select"
:popper-append-to-body="false" :popper-append-to-body="false"
@change="metricChange" @change="metricChange"
@@ -66,6 +66,7 @@
size="mini" size="mini"
v-model="lineRefer" v-model="lineRefer"
:disabled="!lineTab" :disabled="!lineTab"
placeholder=" "
popper-class="common-select" popper-class="common-select"
:popper-append-to-body="false" :popper-append-to-body="false"
@change="referenceSelectChange" @change="referenceSelectChange"
@@ -226,6 +227,9 @@ export default {
if (response.status === 200) { if (response.status === 200) {
this.isNoData = res.data.result.length === 0 this.isNoData = res.data.result.length === 0
this.showError = false this.showError = false
if (!active) {
this.tabs = _.cloneDeep(this.tabsTemplate)
}
if (this.isNoData) { if (this.isNoData) {
this.lineTab = '' this.lineTab = ''
this.tabs = _.cloneDeep(this.tabsTemplate) this.tabs = _.cloneDeep(this.tabsTemplate)
@@ -497,7 +501,7 @@ export default {
}) })
} }
if (data !== undefined && data.length > 0) { if (data && data.length > 0) {
newData.forEach((item) => { newData.forEach((item) => {
item.type = getLineType(item.type) item.type = getLineType(item.type)
if (item.type === val) { if (item.type === val) {
@@ -510,6 +514,24 @@ export default {
}) })
} }
lineData.splice(0, 1) 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') { if (val === 'Sessions/s') {
const tabs = _.cloneDeep(this.tabsTemplate) const tabs = _.cloneDeep(this.tabsTemplate)
lineData.forEach((d, i) => { lineData.forEach((d, i) => {
@@ -527,6 +549,7 @@ export default {
}) })
this.tabs = tabs this.tabs = tabs
this.$nextTick(() => { this.$nextTick(() => {
this.lineRefer = 'Average'
this.echartsInit(this.tabs, true) this.echartsInit(this.tabs, true)
}) })
} else { } else {
@@ -544,7 +567,7 @@ export default {
const self = this const self = this
tabs.forEach(e => { tabs.forEach(e => {
e.unitType = type e.unitType = type
if (e.name !== 'network.total' && parseFloat(e.analysis.avg) === 0) { if (e.name !== 'network.total' && parseFloat(e.analysis.max) === 0) {
e.show = false e.show = false
num += 1 num += 1
} else { } else {
@@ -554,13 +577,18 @@ export default {
} }
} }
if (self.lineTab === e.class) { if (self.lineTab === e.class) {
if (parseFloat(e.analysis.avg) <= 0) { if (parseFloat(e.analysis.max) <= 0) {
self.lineTab = '' self.lineTab = ''
self.lineRefer = '' 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 this.tabs = tabs
if (num === 5) { if (num === 5) {
tabs[0].invertTab = false tabs[0].invertTab = false

View File

@@ -20,6 +20,7 @@
<security-event v-else-if="tab.name === entityDetailTabsName.securityEvent && tab.name === activeTab" @toggleLoading="setLoading" :timeFilter="oneDayTimeFilter" @checkTag="setTag" /> <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" /> <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> <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-tab-pane>
</el-tabs> </el-tabs>
</div> </div>
@@ -28,24 +29,27 @@
<script> <script>
import chartMixin from '@/views/charts2/chart-mixin' import chartMixin from '@/views/charts2/chart-mixin'
import i18n from '@/i18n' import i18n from '@/i18n'
import { entityDetailTabsName, entityDetailTags, psiphon3IpType } from '@/utils/constants' import { entityDetailTabsName, entityDetailTags } from '@/utils/constants'
import { reactive, ref } from 'vue' import { reactive, ref } from 'vue'
import InformationAggregation from '@/views/charts2/charts/entityDetail/tabs/InformationAggregation' import InformationAggregation from '@/views/charts2/charts/entityDetail/tabs/InformationAggregation'
import DomainNameResolution from '@/views/charts2/charts/entityDetail/tabs/DomainNameResolution' import DomainNameResolution from '@/views/charts2/charts/entityDetail/tabs/DomainNameResolution'
import SecurityEvent from '@/views/charts2/charts/entityDetail/tabs/SecurityEvent' import SecurityEvent from '@/views/charts2/charts/entityDetail/tabs/SecurityEvent'
import PerformanceEvent from '@/views/charts2/charts/entityDetail/tabs/PerformanceEvent' 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 OpenPort from '@/views/charts2/charts/entityDetail/tabs/OpenPort'
import DigitalCertificate from '@/views/charts2/charts/entityDetail/tabs/DigitalCertificate' import DigitalCertificate from '@/views/charts2/charts/entityDetail/tabs/DigitalCertificate'
import { overwriteUrl, urlParamsHandler } from '@/utils/tools' import { overwriteUrl, urlParamsHandler } from '@/utils/tools'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import axios from 'axios' import axios from 'axios'
import { api } from '@/utils/api' import { api } from '@/utils/api'
import { tagValueLabelMapping } from '../../../../utils/constants'
export default { export default {
name: 'EntityDetailTabs', name: 'EntityDetailTabs',
mixins: [chartMixin], mixins: [chartMixin],
components: { components: {
PerformanceEvent, PerformanceEvent,
BehaviorPattern,
SecurityEvent, SecurityEvent,
InformationAggregation, InformationAggregation,
DomainNameResolution, DomainNameResolution,
@@ -90,6 +94,9 @@ export default {
if (entityType !== 'app') { if (entityType !== 'app') {
tabs.unshift({ name: entityDetailTabsName.informationAggregation, label: i18n.global.t('entities.informationAggregation'), icon: 'cn-icon cn-icon-information-aggregation', tag: 0 }) 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 activeTab = ref(tabs[0].name)
const { query } = useRoute() const { query } = useRoute()
@@ -136,7 +143,7 @@ export default {
Object.keys(r[k]).forEach(k2 => { Object.keys(r[k]).forEach(k2 => {
const find = entityDetailTags[this.entity.entityType].find(t => t.name === k2) const find = entityDetailTags[this.entity.entityType].find(t => t.name === k2)
if (find) { if (find) {
aggregation.intelligenceContent.push({ key: k2, value: this.tagValueHandler(k, k2, r[k][k2]), type: find.type }) aggregation.intelligenceContent.push({ key: k2, value: this.tagValueHandler(r[k][k2]), type: find.type })
} }
}) })
} }
@@ -171,7 +178,8 @@ export default {
if (this.entity.entityType === 'ip') { if (this.entity.entityType === 'ip') {
const appsOfIp = axios.get(api.entity.domainNameResolutionAboutAppsOfIp, { params: params }) const appsOfIp = axios.get(api.entity.domainNameResolutionAboutAppsOfIp, { params: params })
const domainsOfIp = axios.get(api.entity.domainNameResolutionAboutDomainsOfIp, { params: params }) const domainsOfIp = axios.get(api.entity.domainNameResolutionAboutDomainsOfIp, { params: params })
this.promiseData(appsOfIp, domainsOfIp) const behaviorPattern = axios.get(api.entity.behaviorPattern, { params: params })
this.promiseData(appsOfIp, domainsOfIp, behaviorPattern)
} }
if (this.entity.entityType === 'domain') { if (this.entity.entityType === 'domain') {
@@ -191,6 +199,18 @@ export default {
const len1 = res[0].status === 200 ? res0.data.result.length : 0 const len1 = res[0].status === 200 ? res0.data.result.length : 0
const len2 = res[1].status === 200 ? res1.data.result.length : 0 const len2 = res[1].status === 200 ? res1.data.result.length : 0
this.initSetTag(entityDetailTabsName.relatedEntity, len1 + len2) 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 break
} }
case 'domain': { case 'domain': {
@@ -251,16 +271,9 @@ export default {
case 'app': return api.entity.openPortOfApp case 'app': return api.entity.openPortOfApp
} }
}, },
tagValueHandler (k, k2, value) { tagValueHandler (value) {
if (k === 'psiphon3Ip') { const find = tagValueLabelMapping.find(t => t.value === value)
if (k2 === 'type') { return find ? find.name : value
const find = psiphon3IpType.find(t => t.value === value)
if (find) {
return find.name
}
}
}
return value
} }
}, },
beforeUnmount () { beforeUnmount () {

View File

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

View File

@@ -355,12 +355,12 @@ export default {
if (out < 0.0001 && out !== 0) { if (out < 0.0001 && out !== 0) {
outUsage = '< 0.01%' outUsage = '< 0.01%'
} else { } else {
outUsage = JSON.stringify(parseFloat((out * 100).toFixed(2))) outUsage = JSON.stringify(parseFloat(out * 100).toFixed(2))
} }
if (_in < 0.0001 && _in !== 0) { if (_in < 0.0001 && _in !== 0) {
inUsage = '< 0.01%' inUsage = '< 0.01%'
} else { } else {
inUsage = JSON.stringify(parseFloat((_in * 100).toFixed(2))) inUsage = JSON.stringify(parseFloat(_in * 100).toFixed(2))
} }
length = outUsage.length + inUsage.length length = outUsage.length + inUsage.length

View File

@@ -30,7 +30,8 @@ export default {
isLinkShowError: false, // 显示左侧链路报错标识 isLinkShowError: false, // 显示左侧链路报错标识
linkErrorMsg: '', // 左侧链路的报错信息 linkErrorMsg: '', // 左侧链路的报错信息
isNextShowError: false, // 显示右侧下一跳报错标识 isNextShowError: false, // 显示右侧下一跳报错标识
nextErrorMsg: '' // 右侧下一跳的报错信息 nextErrorMsg: '', // 右侧下一跳的报错信息
scoreDataState: false // 评分数据是否加载完成
} }
}, },
components: { components: {
@@ -41,6 +42,23 @@ export default {
handler () { handler () {
this.init() 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 () { mounted () {
@@ -48,6 +66,7 @@ export default {
}, },
methods: { methods: {
init () { init () {
this.scoreDataState = false
// 链路基本信息 // 链路基本信息
let linkInfo = localStorage.getItem(storageKey.linkInfo) let linkInfo = localStorage.getItem(storageKey.linkInfo)
linkInfo = JSON.parse(linkInfo) linkInfo = JSON.parse(linkInfo)
@@ -117,9 +136,9 @@ export default {
d.usageMore90 = outUsage >= 0.9 || inUsage >= 0.9 d.usageMore90 = outUsage >= 0.9 || inUsage >= 0.9
// 计算npm分数 // 计算npm分数
// 分数低于3分赋红点 // 分数低于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 xAxis = inLink.linkId.split('Hundredgige').pop() - 1
const yAxis = outLink.linkId.split('Hundredgige').pop() - 1 const yAxis = outLink.linkId.split('Hundredgige').pop() - 1
@@ -210,9 +229,9 @@ export default {
d.usageMore90 = outUsage >= 0.9 || inUsage >= 0.9 d.usageMore90 = outUsage >= 0.9 || inUsage >= 0.9
// 计算npm分数 // 计算npm分数
// 分数低于3分赋红点 // 分数低于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 xAxis = inLink.linkId
const yAxis = outLink.linkId const yAxis = outLink.linkId
@@ -254,6 +273,7 @@ export default {
} }
} }
}).finally(() => { }).finally(() => {
this.scoreDataState = true
this.toggleLoading(false) this.toggleLoading(false)
}) })
}, },
@@ -282,6 +302,23 @@ export default {
score = computeScore(dataScore) score = computeScore(dataScore)
return score 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弹窗和右侧数据模块的宽度 * 计算popover弹窗和右侧数据模块的宽度
* 弹窗最小宽度为360px右侧数据最小宽度为75px右侧数据每大一位popover弹窗宽度增加7px * 弹窗最小宽度为360px右侧数据最小宽度为75px右侧数据每大一位popover弹窗宽度增加7px

View File

@@ -42,6 +42,7 @@
<el-select <el-select
size="mini" size="mini"
v-model="lineMetric" v-model="lineMetric"
placeholder=" "
popper-class="common-select" popper-class="common-select"
:popper-append-to-body="false" :popper-append-to-body="false"
@change="metricSelectChange" @change="metricSelectChange"
@@ -156,7 +157,7 @@ export default {
endTime: getSecond(this.timeFilter.endTime) endTime: getSecond(this.timeFilter.endTime)
} }
if (this.queryCondition) { if (this.queryCondition) {
const condition = this.queryCondition.toLowerCase().split(' or ') const condition = this.queryCondition.split(' or ')
if (condition.length > 1) { if (condition.length > 1) {
params.outParam = condition.find(c => c.indexOf('common_out_link_id') > -1 || c.indexOf('out_link_direction') > -1) params.outParam = condition.find(c => c.indexOf('common_out_link_id') > -1 || c.indexOf('out_link_direction') > -1)
params.inParam = condition.find(c => c.indexOf('common_in_link_id') > -1 || c.indexOf('in_link_direction') > -1) params.inParam = condition.find(c => c.indexOf('common_in_link_id') > -1 || c.indexOf('in_link_direction') > -1)
@@ -168,6 +169,9 @@ export default {
if (response.status === 200) { if (response.status === 200) {
this.showError = false this.showError = false
this.isNoData = res.data.result.length === 0 this.isNoData = res.data.result.length === 0
if (!active) {
this.tabs = dataForLinkTrafficLine.tabs
}
if (this.isNoData) { if (this.isNoData) {
this.lineTab = '' this.lineTab = ''
this.tabs = dataForLinkTrafficLine.tabs this.tabs = dataForLinkTrafficLine.tabs
@@ -387,7 +391,7 @@ export default {
let num = 0 let num = 0
linkData.forEach(e => { linkData.forEach(e => {
e.unitType = type e.unitType = type
if (parseFloat(e.analysis.avg) === 0 || isNaN(parseFloat(e.analysis.avg))) { if (parseFloat(e.analysis.max) === 0 || isNaN(parseFloat(e.analysis.max))) {
e.show = false e.show = false
num += 1 num += 1
} else { } else {
@@ -397,13 +401,18 @@ export default {
} }
} }
if (this.lineTab === e.class) { if (this.lineTab === e.class) {
if (parseFloat(e.analysis.avg) <= 0) { if (parseFloat(e.analysis.max) <= 0) {
this.lineTab = '' this.lineTab = ''
this.lineRefer = '' 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 this.tabs = linkData
// 如果三者avg都为0时至少保证total显示 // 如果三者avg都为0时至少保证total显示
const ingressObj = linkData.find(d => d.name === 'linkMonitor.ingress') const ingressObj = linkData.find(d => d.name === 'linkMonitor.ingress')
@@ -411,10 +420,10 @@ export default {
let ingressAvg = 0 let ingressAvg = 0
let egressAvg = 0 let egressAvg = 0
if (ingressObj) { if (ingressObj) {
ingressAvg = parseFloat(ingressObj.analysis.avg) || 0 ingressAvg = parseFloat(ingressObj.analysis.max) || 0
} }
if (egressObj) { if (egressObj) {
egressAvg = parseFloat(egressObj.analysis.avg) || 0 egressAvg = parseFloat(egressObj.analysis.max) || 0
} }
if ((ingressAvg + egressAvg) === 0) { if ((ingressAvg + egressAvg) === 0) {
const totalObj = linkData.find(d => d.name === 'network.total') const totalObj = linkData.find(d => d.name === 'network.total')

View File

@@ -100,7 +100,7 @@ export default {
} }
let url = '' let url = ''
if (this.queryCondition) { if (this.queryCondition) {
const condition = this.queryCondition.toLowerCase().split(' or ') const condition = this.queryCondition.split(' or ')
if (condition.length > 1) { if (condition.length > 1) {
if (n === 0) { if (n === 0) {
params.q = condition.find(c => c.indexOf('common_in_link_id') > -1 || c.indexOf('in_link_direction') > -1) params.q = condition.find(c => c.indexOf('common_in_link_id') > -1 || c.indexOf('in_link_direction') > -1)

View File

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

View File

@@ -1,994 +0,0 @@
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,6 +38,7 @@
size="mini" size="mini"
v-model="lineRefer" v-model="lineRefer"
:disabled="!lineTab" :disabled="!lineTab"
placeholder=" "
popper-class="common-select" popper-class="common-select"
:popper-append-to-body="false" :popper-append-to-body="false"
@change="referenceSelectChange" @change="referenceSelectChange"
@@ -192,6 +193,9 @@ export default {
if (response.status === 200) { if (response.status === 200) {
this.isNoData = res.data.result.length === 0 this.isNoData = res.data.result.length === 0
this.showError = false this.showError = false
if (!active) {
this.tabs = _.cloneDeep(this.tabsTemplate)
}
if (this.isNoData) { if (this.isNoData) {
this.lineTab = '' this.lineTab = ''
this.tabs = _.cloneDeep(this.tabsTemplate) this.tabs = _.cloneDeep(this.tabsTemplate)
@@ -464,8 +468,7 @@ export default {
newData.push(obj) newData.push(obj)
}) })
} }
if (data && data.length > 0) {
if (data !== undefined && data.length > 0) {
newData.forEach((item) => { newData.forEach((item) => {
item.type = getLineType(item.type) item.type = getLineType(item.type)
if (item.type === val) { if (item.type === val) {
@@ -478,6 +481,24 @@ export default {
}) })
} }
lineData.splice(0, 1) 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') { if (val === 'Sessions/s') {
const tabs = _.cloneDeep(this.tabsTemplate) const tabs = _.cloneDeep(this.tabsTemplate)
lineData.forEach((d, i) => { lineData.forEach((d, i) => {
@@ -495,6 +516,7 @@ export default {
}) })
this.tabs = tabs this.tabs = tabs
this.$nextTick(() => { this.$nextTick(() => {
this.lineRefer = 'Average'
this.echartsInit(this.tabs, true) this.echartsInit(this.tabs, true)
}) })
} else { } else {
@@ -512,7 +534,7 @@ export default {
const self = this const self = this
tabs.forEach(e => { tabs.forEach(e => {
e.unitType = type e.unitType = type
if (e.name !== 'network.total' && parseFloat(e.analysis.avg) === 0) { if (e.name !== 'network.total' && parseFloat(e.analysis.max) === 0) {
e.show = false e.show = false
num += 1 num += 1
} else { } else {
@@ -522,13 +544,18 @@ export default {
} }
} }
if (self.lineTab === e.class) { if (self.lineTab === e.class) {
if (parseFloat(e.analysis.avg) <= 0) { if (parseFloat(e.analysis.max) <= 0) {
self.lineTab = '' self.lineTab = ''
self.lineRefer = '' 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 this.tabs = tabs
if (num === 5) { if (num === 5) {
tabs[0].invertTab = false tabs[0].invertTab = false

View File

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

View File

@@ -172,7 +172,8 @@ export default {
curTabState: curTabState, curTabState: curTabState,
urlChangeParams: {}, urlChangeParams: {},
showError: false, showError: false,
errorMsg: '' errorMsg: '',
scoreDataState: false // 评分数据是否加载完成
} }
}, },
components: { components: {
@@ -185,6 +186,21 @@ export default {
handler () { handler () {
this.init() 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: { methods: {
@@ -197,6 +213,7 @@ export default {
const currentTrafficRequest = axios.get(api.npm.overview.appTrafficAnalysis, { params: { ...params, cycle: 0 } }) const currentTrafficRequest = axios.get(api.npm.overview.appTrafficAnalysis, { params: { ...params, cycle: 0 } })
const lastCycleTrafficRequest = axios.get(api.npm.overview.appTrafficAnalysis, { params: { ...params, cycle: 1 } }) const lastCycleTrafficRequest = axios.get(api.npm.overview.appTrafficAnalysis, { params: { ...params, cycle: 1 } })
this.toggleLoading(true) this.toggleLoading(true)
this.scoreDataState = false
Promise.all([currentTrafficRequest, lastCycleTrafficRequest]).then(res => { Promise.all([currentTrafficRequest, lastCycleTrafficRequest]).then(res => {
if (res[0].status === 200 && res[1].status === 200) { if (res[0].status === 200 && res[1].status === 200) {
this.showError = false this.showError = false
@@ -239,6 +256,9 @@ export default {
t[keyPre[i] + 'Score'] = r.data.data.result.find(d => d.appSubcategory === t.appSubcategory) t[keyPre[i] + 'Score'] = r.data.data.result.find(d => d.appSubcategory === t.appSubcategory)
}) })
} else { } else {
tableData.forEach(t => {
t[keyPre[i] + 'Score'] = null
})
this.showError = true this.showError = true
msg = msg + ',' + r.data.data.message msg = msg + ',' + r.data.data.message
if (msg.indexOf(',') === 0) { if (msg.indexOf(',') === 0) {
@@ -250,7 +270,7 @@ export default {
this.errorMsg = msg this.errorMsg = msg
} }
}) })
tableData.forEach(t => { /* tableData.forEach(t => {
const data = { const data = {
establishLatencyMs: t.tcpScore ? t.tcpScore.establishLatencyMs : null, establishLatencyMs: t.tcpScore ? t.tcpScore.establishLatencyMs : null,
httpResponseLatency: t.httpScore ? t.httpScore.httpResponseLatency : null, httpResponseLatency: t.httpScore ? t.httpScore.httpResponseLatency : null,
@@ -259,10 +279,11 @@ export default {
pktRetransPercent: t.packetRetransScore ? t.packetRetransScore.pktRetransPercent : null pktRetransPercent: t.packetRetransScore ? t.packetRetransScore.pktRetransPercent : null
} }
t.score = computeScore(data) t.score = computeScore(data)
}) }) */
this.tableData = tableData this.tableData = tableData
}).finally(() => { }).finally(() => {
this.toggleLoading(false) this.toggleLoading(false)
this.scoreDataState = true
}) })
} else { } else {
this.tableData = [] this.tableData = []
@@ -382,6 +403,18 @@ export default {
overwriteUrl(newUrl) overwriteUrl(newUrl)
} }
this.urlChangeParams = {} 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 () { 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" v-for="(item, index) in chartData" :key=index>
<div class="npm-header-body-severity"> <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-icon" :class="item.eventSeverity" :test-id="`icon${index}`"></div>
<div class="npm-header-body-severity-value" :test-id="`severity${index}`">{{item.eventSeverity}}</div> <div class="npm-header-body-severity-value" :test-id="`severity${index}`">{{ $t(item.eventSeverity) }}</div>
</div> </div>
<chart-error v-if="showError" tooltip :content="errorMsg" /> <chart-error v-if="showError" tooltip :content="errorMsg" />
<div v-else class="npm-header-body-total" :test-id="`total${index}`">{{item.count}}</div> <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) : '', endTime: this.timeFilter && this.timeFilter.endTime ? getSecond(this.timeFilter.endTime) : '',
type: this.type type: this.type
} }
/*this.toggleLoading(true) /* this.toggleLoading(true)
axios.get(api.npm.events.list, { params: params }).then(response => { axios.get(api.npm.events.list, { params: params }).then(response => {
const res = response.data const res = response.data
if (response.status === 200) { if (response.status === 200) {
@@ -69,7 +69,7 @@ export default {
this.errorMsg = this.errorMsgHandler(e) this.errorMsg = this.errorMsgHandler(e)
}).finally(() => { }).finally(() => {
this.toggleLoading(false) this.toggleLoading(false)
})*/ }) */
this.toggleLoading(false) this.toggleLoading(false)
} }
}, },

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,9 +4,10 @@ import {
chartColor3, chartColor3,
chartColor5, chartColor5,
chartColor6, chartColor6,
chartColorForBehaviorPattern,
unitTypes unitTypes
} from '@/utils/constants' } from '@/utils/constants'
import unitConvert, { valueToRangeValue } from '@/utils/unit-convert' import unitConvert from '@/utils/unit-convert'
import { axisFormatter } from '@/views/charts/charts/tools' import { axisFormatter } from '@/views/charts/charts/tools'
import { xAxisTimeFormatter, xAxisTimeRich } from '@/utils/date-util' import { xAxisTimeFormatter, xAxisTimeRich } from '@/utils/date-util'
@@ -156,6 +157,111 @@ 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 = { export const stackedLineChartOption = {
color: chartColor3, color: chartColor3,
tooltip: { tooltip: {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,252 @@
<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

@@ -0,0 +1,128 @@
<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

@@ -0,0 +1,497 @@
<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

@@ -50,9 +50,12 @@
</template> </template>
<template v-else-if="item.prop === 'status'"> <template v-else-if="item.prop === 'status'">
<div :class="`detection-tag-status${scope.row[item.prop]}`"> <div :class="`detection-tag-status${scope.row[item.prop]}`">
{{ switchStatus(scope.row[item.prop]) }} {{ $t(switchStatus(scope.row[item.prop])) }}
</div> </div>
</template> </template>
<template v-else-if="item.prop === 'category'">
{{ changeCategory(scope.row[item.prop]) }}
</template>
<template v-else-if="item.prop === 'description'"> <template v-else-if="item.prop === 'description'">
<div style="padding-right: 20px">{{ scope.row[item.prop] }}</div> <div style="padding-right: 20px">{{ scope.row[item.prop] }}</div>
</template> </template>
@@ -77,6 +80,8 @@
import table from '@/mixins/table' import table from '@/mixins/table'
import { dateFormatByAppearance } from '@/utils/date-util' import { dateFormatByAppearance } from '@/utils/date-util'
import { switchStatus } from '@/utils/tools' import { switchStatus } from '@/utils/tools'
import _ from 'lodash'
import { detectionUnitList } from '@/utils/constants'
export default { export default {
name: 'DetectionTable', name: 'DetectionTable',
@@ -127,15 +132,13 @@ export default {
show: true show: true
}, },
{ {
// label: this.$t('config.user.createTime'), label: this.$t('detection.create.dimensions'),
label: 'Dimensions',
prop: 'dimensions', prop: 'dimensions',
minWidth: 204, minWidth: 204,
show: true show: true
}, },
{ {
// label: this.$t('config.user.createTime'), label: this.$t('detection.library'),
label: 'Library',
prop: 'library', prop: 'library',
minWidth: 204, minWidth: 204,
show: true show: true
@@ -151,9 +154,9 @@ export default {
if (n) { if (n) {
n.forEach(t => { n.forEach(t => {
if (t.ruleType === 'indicator_match') { if (t.ruleType === 'indicator_match') {
t.library = t.ruleConfig.knowledge.name t.library = _.get(t, 'ruleConfigObj.knowledgeBase.name', '-')
} else if (t.ruleType === 'threshold') { } else if (t.ruleType === 'threshold') {
t.dimensions = t.ruleConfig.dimensions t.dimensions = _.get(t, 'ruleConfigObj.dimensions', '-')
} }
}) })
} }
@@ -165,6 +168,16 @@ export default {
switchStatus, switchStatus,
rowDoubleClick (data) { rowDoubleClick (data) {
this.$emit('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,14 +11,16 @@
<span>{{ $t('overall.create') }}</span> <span>{{ $t('overall.create') }}</span>
</button> </button>
<!-- <button--> <button
<!-- id="knowledge-base-edit"--> :disabled="disableEdit"
<!-- :title="$t('knowledgeBase.editKnowledgeBase')"--> id="knowledge-base-edit"
<!-- class="top-tool-btn margin-r-10"--> :title="$t('knowledgeBase.editKnowledgeBase')"
<!-- style="width:72px;">--> class="top-tool-btn margin-r-10"
<!-- <i class="cn-icon-edit cn-icon" ></i>--> @click="onEdit"
<!-- <span>{{$t('overall.edit')}}</span>--> style="width:72px;">
<!-- </button>--> <i class="cn-icon-edit cn-icon" ></i>
<span>{{$t('overall.edit')}}</span>
</button>
<button <button
:disabled="disableDelete" :disabled="disableDelete"
@@ -51,6 +53,10 @@ export default {
disableDelete: { disableDelete: {
type: Boolean, type: Boolean,
default: true default: true
},
disableEdit: {
type: Boolean,
default: true
} }
}, },
data () { data () {
@@ -65,6 +71,9 @@ export default {
onCreate () { onCreate () {
this.$emit('create') this.$emit('create')
}, },
onEdit () {
this.$emit('edit')
},
onDelete (data) { onDelete (data) {
this.$emit('delete', data) this.$emit('delete', data)
} }

View File

@@ -4,7 +4,7 @@ import {
} from '@/views/charts/charts/tools' } from '@/views/charts/charts/tools'
import unitConvert from '@/utils/unit-convert' import unitConvert from '@/utils/unit-convert'
import _ from 'lodash' import _ from 'lodash'
import { dateFormatByAppearance } from '@/utils/date-util' import { dateFormatByAppearance, xAxisTimeFormatter, xAxisTimeRich } from '@/utils/date-util'
import { unitTypes } from '@/utils/constants' import { unitTypes } from '@/utils/constants'
const severitySeriesIndexMappings = [ const severitySeriesIndexMappings = [
@@ -67,8 +67,8 @@ export const multipleBarOption = {
source: [ source: [
] ]
}, },
xAxis: { xAxis: [{
type: 'category', type: 'time',
axisTick: { show: false }, axisTick: { show: false },
axisLine: { axisLine: {
show: true, show: true,
@@ -77,13 +77,16 @@ export const multipleBarOption = {
} }
}, },
axisLabel: { axisLabel: {
formatter: xAxisTimeFormatter,
rich: xAxisTimeRich,
color: '#737373', color: '#737373',
interval: 'auto' interval: 'auto'
}, },
splitNumber: 8,
splitLine: { splitLine: {
show: false show: false
} }
}, }],
yAxis: { yAxis: {
axisTick: { show: false }, axisTick: { show: false },
axisLine: { axisLine: {

View File

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

View File

@@ -1,173 +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${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

@@ -1,143 +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.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

@@ -1,250 +0,0 @@
<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

@@ -17,7 +17,7 @@
@search="search" @search="search"
></explorer-search> ></explorer-search>
<!-- 内容区 --> <!-- 内容区 -->
<div v-if="showList" style="display: flex;flex-direction: row;"> <div v-if="showList" style="display: flex;flex-direction: row;padding-bottom: 20px;">
<entity-filter <entity-filter
:filter-data="newFilterData" :filter-data="newFilterData"
:loading-left="loadingLeft" :loading-left="loadingLeft"
@@ -689,6 +689,44 @@ export default {
} else { } else {
this.search({ q: '', str: '', metaList: [] }) 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 () { mounted () {
@@ -702,8 +740,16 @@ export default {
if (q && (q.indexOf('%') === 0 || q.indexOf('%20') > -1 || q.indexOf('%25') > -1)) { if (q && (q.indexOf('%') === 0 || q.indexOf('%20') > -1 || q.indexOf('%25') > -1)) {
q = decodeURI(q) 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.initSearch(q)
this.listMode = listMode this.listMode = listMode
// 查询评分基准
this.$store.commit('resetScoreBase')
this.queryScoreBase()
} }
}, },
watch: { watch: {

View File

@@ -39,7 +39,7 @@
</div> </div>
<div @click="showMoreFilter(item, index)" <div @click="showMoreFilter(item, index)"
:class="item.showNum === item.data.length ? 'filter-no-show-more' : 'filter-show-more'"> :class="item.showNum === item.data.length ? 'filter-no-show-more' : 'filter-show-more'">
{{ $t('entity.showMore') }} {{ $t('overall.showMore') }}
</div> </div>
<div class="filter-hr"></div> <div class="filter-hr"></div>
</div> </div>

View File

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

View File

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

View File

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

View File

@@ -13,7 +13,11 @@
<div class="cn-entity__header" style="display: flex;"> <div class="cn-entity__header" style="display: flex;">
<span class="cn-entity__header-title">{{ entityData.entityValue || 'Unknown' }}</span> <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 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}`"> <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)">
{{ item.value }} {{ item.value }}
</span> </span>
</span> </span>
@@ -26,7 +30,7 @@
<div class="basic-info__item"> <div class="basic-info__item">
<i class="cn-icon cn-icon-country"></i> <i class="cn-icon cn-icon-country"></i>
<span class="row-item-label">{{ $t('overall.country') }}&nbsp;:&nbsp;&nbsp;</span> <span class="row-item-label">{{ $t('overall.country') }}&nbsp;:&nbsp;&nbsp;</span>
<span class="row-item-value">{{ entityData.location ? entityData.location.country : '-' }}</span> <span class="row-item-value">{{ $_.get(entityData, 'location.country', '-') || '-' }}</span>
</div> </div>
<div class="basic-info__item"> <div class="basic-info__item">
<i class="cn-icon cn-icon-position"></i> <i class="cn-icon cn-icon-position"></i>
@@ -36,36 +40,36 @@
<div class="basic-info__item"> <div class="basic-info__item">
<i class="cn-icon cn-icon-cloud"></i> <i class="cn-icon cn-icon-cloud"></i>
<span class="row-item-label">{{ $t('entities.asn') }}&nbsp;:&nbsp;&nbsp;</span> <span class="row-item-label">{{ $t('entities.asn') }}&nbsp;:&nbsp;&nbsp;</span>
<span class="row-item-value">{{ entityData.asn ? entityData.asn.asn : '-' }}</span> <span class="row-item-value">{{ $_.get(entityData, 'asn.asn', '-') || '-' }}</span>
</div> </div>
</template> </template>
<template v-else-if="entityData.entityType === 'domain'"> <template v-else-if="entityData.entityType === 'domain'">
<div class="basic-info__item"> <div class="basic-info__item">
<i class="cn-icon cn-icon-category-group"></i> <i class="cn-icon cn-icon-category-group"></i>
<span class="row-item-label">{{ $t('entities.category') }}&nbsp;:&nbsp;&nbsp;</span> <span class="row-item-label">{{ $t('entities.category') }}&nbsp;:&nbsp;&nbsp;</span>
<span class="row-item-value">{{ entityData.category ? entityData.category.categoryGroup : '-' }}</span> <span class="row-item-value">{{ $_.get(entityData, 'category.categoryGroup', '-') || '-' }}</span>
</div> </div>
<div class="basic-info__item"> <div class="basic-info__item">
<i class="cn-icon cn-icon-sub-category"></i> <i class="cn-icon cn-icon-sub-category"></i>
<span class="row-item-label">{{ $t('entities.subcategory') }}&nbsp;:&nbsp;&nbsp;</span> <span class="row-item-label">{{ $t('entities.subcategory') }}&nbsp;:&nbsp;&nbsp;</span>
<span class="row-item-value">{{ entityData.category ? entityData.category.categoryName : '-' }}</span> <span class="row-item-value">{{ $_.get(entityData, 'category.categoryName', '-') || '-' }}</span>
</div> </div>
<div class="basic-info__item"> <div class="basic-info__item">
<i class="cn-icon cn-icon-credit-rating"></i> <i class="cn-icon cn-icon-credit-rating"></i>
<span class="row-item-label">{{ $t('entities.reputationLevel') }}&nbsp;:&nbsp;&nbsp;</span> <span class="row-item-label">{{ $t('entities.reputationLevel') }}&nbsp;:&nbsp;&nbsp;</span>
<span class="row-item-value">{{ entityData.category ? entityData.category.reputationLevel : '-' }}</span> <span class="row-item-value">{{ $_.get(entityData, 'category.reputationLevel', '-') || '-' }}</span>
</div> </div>
</template> </template>
<template v-else-if="entityData.entityType === 'app'"> <template v-else-if="entityData.entityType === 'app'">
<div class="basic-info__item"> <div class="basic-info__item">
<i class="cn-icon cn-icon-category2"></i> <i class="cn-icon cn-icon-category2"></i>
<span class="row-item-label">{{ $t('entities.category') }}&nbsp;:&nbsp;&nbsp;</span> <span class="row-item-label">{{ $t('entities.category') }}&nbsp;:&nbsp;&nbsp;</span>
<span class="row-item-value">{{ entityData.category ? entityData.category.appCategory : '-' }}</span> <span class="row-item-value">{{ $_.get(entityData, 'category.appCategory', '-') || '-' }}</span>
</div> </div>
<div class="basic-info__item"> <div class="basic-info__item">
<i class="cn-icon cn-icon-sub-category"></i> <i class="cn-icon cn-icon-sub-category"></i>
<span class="row-item-label">{{ $t('entities.subcategory') }}&nbsp;:&nbsp;&nbsp;</span> <span class="row-item-label">{{ $t('entities.subcategory') }}&nbsp;:&nbsp;&nbsp;</span>
<span class="row-item-value">{{ entityData.category ? entityData.category.appSubcategory : '-' }}</span> <span class="row-item-value">{{ $_.get(entityData, 'category.appSubcategory', '-') || '-' }}</span>
</div> </div>
<div class="basic-info__item"> <div class="basic-info__item">
<i class="cn-icon cn-icon-credit-rating"></i> <i class="cn-icon cn-icon-credit-rating"></i>
@@ -139,7 +143,10 @@
<div class="row-item-label"> <div class="row-item-label">
<span class="row-item-label">{{ $t('network.score') }}&nbsp;:&nbsp;&nbsp;</span> <span class="row-item-label">{{ $t('network.score') }}&nbsp;:&nbsp;&nbsp;</span>
<span class="row-item-value" style="position: relative;"> <span class="row-item-value" style="position: relative;">
<span v-if="!loadingNetworkQuality">{{ score }}</span> <template v-if="!loadingNetworkQuality && score !=='-'">
<span v-for="(dot, i) in scoreDot" :key="i" :class="dot.class"></span>
</template>
<span>{{score}}</span>
<loading :loading="loadingNetworkQuality" size="small"></loading> <loading :loading="loadingNetworkQuality" size="small"></loading>
</span> </span>
</div> </div>
@@ -183,8 +190,9 @@ import relatedServer from '@/mixins/relatedServer'
import Loading from '@/components/common/Loading' import Loading from '@/components/common/Loading'
import axios from 'axios' import axios from 'axios'
import { api } from '@/utils/api' import { api } from '@/utils/api'
import { entityDetailTags, psiphon3IpType } from '@/utils/constants' import { entityDefaultColor, entityDetailTags, tagValueLabelMapping } from '@/utils/constants'
import _ from 'lodash' import _ from 'lodash'
import { getTagColor } from '@/utils/tools'
export default { export default {
name: 'Row', name: 'Row',
@@ -240,6 +248,7 @@ export default {
}) })
}, },
methods: { methods: {
getTagColor,
initData () { initData () {
let url = '' let url = ''
switch (this.entity.entityType) { switch (this.entity.entityType) {
@@ -258,7 +267,7 @@ export default {
} }
axios.get(`${url}?resource=${this.entity.entityValue}`).then(response => { axios.get(`${url}?resource=${this.entity.entityValue}`).then(response => {
this.$nextTick(() => { this.$nextTick(() => {
this.entityData = { ...response.data.data, ...this.entity } this.entityData = { ...this.entityData, ...response.data.data, ...this.entity }
}) })
}) })
}, },
@@ -286,28 +295,21 @@ export default {
Object.keys(res.data[k]).forEach(k2 => { Object.keys(res.data[k]).forEach(k2 => {
const find = entityDetailTags[this.entity.entityType].find(t => t.name === k2) const find = entityDetailTags[this.entity.entityType].find(t => t.name === k2)
if (find) { if (find) {
this.levelTwoTags.push({ key: k2, value: this.tagValueHandler(k, k2, res.data[k][k2]), type: find.type }) this.levelTwoTags.push({ key: k2, value: this.tagValueHandler(res.data[k][k2]), type: find.type })
} }
}) })
} }
}) })
if (_.isArray(res.data.userDefinedTags)) { if (_.isArray(res.data.userDefinedTags)) {
this.levelTwoTags = _.concat(this.levelTwoTags, res.data.userDefinedTags.map(tag => ({ value: tag.tagValue, type: 'normal' }))) this.levelTwoTags = _.concat(this.levelTwoTags, res.data.userDefinedTags.map(tag => ({ value: tag.tagValue, color: tag.knowledgeBase ? tag.knowledgeBase.color : entityDefaultColor })))
} }
this.hideTagArea = _.isEmpty(this.levelTwoTags) this.hideTagArea = _.isEmpty(this.levelTwoTags)
} }
}) })
}, },
tagValueHandler (k, k2, value) { tagValueHandler (value) {
if (k === 'psiphon3Ip') { const find = tagValueLabelMapping.find(t => t.value === value)
if (k2 === 'type') { return find ? find.name : value
const find = psiphon3IpType.find(t => t.value === value)
if (find) {
return find.name
}
}
}
return value
}, },
/* 切换折叠状态 */ /* 切换折叠状态 */
switchCollapse () { switchCollapse () {

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