Compare commits

...

191 Commits

Author SHA1 Message Date
chenjinsong
e26d2b40ab CN-1456 feat: 折线图参考线等国际化 2023-11-21 14:33:16 +08:00
chenjinsong
6c1f1ed71b CN-1475 feat: subscribe配置 2023-11-20 18:48:10 +08:00
chenjinsong
b4b72fc1e1 CN-1468 fix: 补充一些开关的权限控制 2023-11-20 18:14:42 +08:00
hyx
b3d4bbd440 fix:CN-1470 调整新增/修改role页面权限树的逻辑:1.选中节点时,同步选中所有的父辈节点,子节点不进行同步操作;2.取消选中节点时,同步取消选中所有子节点,对父节点不进行同步操作; 2023-11-20 15:27:14 +08:00
刘洪洪
6c90ebe7bc fix: 实体详情tab恢复security事件 2023-11-20 11:52:52 +08:00
chenjinsong
ef3994e14a fix: 修复实体下拉中事件时间问题;behavior pattern的百分比计算逻辑调整 2023-11-20 10:54:03 +08:00
chenjinsong
abc1d07e6c CN-1456 feat: 内置知识库标题和描述增加国际化 2023-11-17 17:10:40 +08:00
hanyuxia
38af43b322 fix: 修改用户时点保存按钮无反应(密码验证代码处的问题) 2023-11-17 16:09:18 +08:00
hanyuxia
90ed543a84 fix:1 检测->事件类型饼图: resize情况下的,legend宽度自适应 2023-11-17 15:21:20 +08:00
chenjinsong
c66a6b7f59 fix: 实体详情security event tab取值问题修复 2023-11-17 14:21:37 +08:00
hanyuxia
a0af36b7c0 fix:1 检测->事件类型饼图legend宽度随分辨率而改变代码补充 2023-11-17 14:13:04 +08:00
hanyuxia
f95dc60cd3 Merge remote-tracking branch 'origin/dev' into dev 2023-11-17 13:54:39 +08:00
hanyuxia
7d64868ce0 fix:1 CN-1470 调整新增/修改role页面权限树的逻辑;2.检查模块下的事件类型饼图legend宽度随分辨率而改变; 2023-11-17 13:54:21 +08:00
chenjinsong
62a7a629e5 fix: 修改一些国际化 2023-11-17 11:41:25 +08:00
chenjinsong
e3c6c21545 fix: 补充一些国际化 2023-11-17 11:20:21 +08:00
chenjinsong
3b2eaf3851 fix: 搜索组件样式调整 2023-11-16 19:45:43 +08:00
chenjinsong
ee05f47f2d fix: 一些国际化和样式调整 2023-11-16 17:29:54 +08:00
hyx
07dd16f2c2 CN-1410 Detections模块中,无法根据面包屑导航进行跳转页面 2023-11-15 22:22:53 +08:00
chenjinsong
9103c454c2 fix: 调整.cn-container样式 2023-11-15 18:11:33 +08:00
chenjinsong
e5a7c2abfa fix: behavior pattern的bar设置最小高度 2023-11-15 17:53:34 +08:00
刘洪洪
e288e20fd7 fix: 实体、检测搜索框高度调整 2023-11-15 16:22:49 +08:00
刘洪洪
ec746f0fc7 CN-1478 fix: detection事件列表搜索框增加历史记录 2023-11-15 15:26:37 +08:00
刘洪洪
4ca33e9ede CN-1407 fix: Detections-Policies-Create Event Policy模块下,添加取消按钮 2023-11-15 14:47:51 +08:00
刘洪洪
b8105a4565 CN-1439 fix: Detections-Security events模块中,事件日志详情中添加与Policy的交互 2023-11-15 14:46:02 +08:00
hyx
29157ae7d3 代码提交错误修正 2023-11-15 10:02:59 +08:00
刘洪洪
9c0ce493ab fix: 实体列表ip实体下拉详情添加ISP信息 2023-11-14 15:49:53 +08:00
刘洪洪
4662061fc5 fix: 实体列表样式调整以及禁止detection右侧活跃攻击方条形图的文字点击 2023-11-14 14:42:42 +08:00
刘洪洪
0eb0346abc fix: 实体历史搜索的时间展示和其他时间统一为YYYY-MM-DD hh:mm:ss 2023-11-14 10:18:37 +08:00
刘洪洪
c8926177f7 fix: 修复单测报错的问题 2023-11-13 18:12:04 +08:00
刘洪洪
5aa5d77511 fix: 1、修复实体列表搜索后city两个值一样时,第一个值颜色变浅的问题;2、当搜索结果为空时,将隐藏相关实体按钮进行隐藏。 2023-11-13 17:52:23 +08:00
hyx
38368a6cb8 CN-1461 提取界面各功能的默认时间条件至config.js 2023-11-13 17:23:32 +08:00
刘洪洪
051aeadbca fix: 调整实体搜索高亮样式 2023-11-13 15:36:52 +08:00
刘洪洪
8c2119e773 CN-1458 fix: 修复detection列表点击echarts图形却没有执行搜索 2023-11-13 11:52:08 +08:00
刘洪洪
cb70fb0236 CN-1439 fix: Detections-Security events模块中,事件日志详情中添加Policy ID 2023-11-13 11:50:15 +08:00
刘洪洪
853fa79d4c CN-1448 fix: 实体搜索界面增加相关实体过滤功能 2023-11-10 18:26:24 +08:00
刘洪洪
2f919d1774 fix: 修复实体搜索切换页码不高亮,以及多个搜索关键字拼接为一个字段时不高亮等问题 2023-11-10 18:15:33 +08:00
刘洪洪
360fffde68 fix: detection列表左侧filter的status和Severity进行排序 2023-11-10 17:00:19 +08:00
刘洪洪
bb2a7676e6 CN-1458 fix: detection事件列表的筛选功能与实体的统一 2023-11-10 14:11:46 +08:00
chenjinsong
de698f0a71 CN-1468 fix: 为一些按钮增加权限控制 2023-11-10 11:43:11 +08:00
刘洪洪
1c1d354a6c CN-1449 fix: 实体下拉展开的相关实体添加搜索高亮 2023-11-09 18:42:41 +08:00
刘洪洪
8126618e54 fix: 修复实体、检测界面不能滚动的问题 2023-11-09 17:44:24 +08:00
chenjinsong
5a8796688a CN-1468 fix: 更改知识库的新路由 2023-11-09 17:35:04 +08:00
chenjinsong
be36b2604b fix: 暂时关闭下钻table的自动化测试用例 2023-11-09 16:58:25 +08:00
chenjinsong
330a4b0d3b CN-1468 fix: 路由动态设置 2023-11-09 16:17:25 +08:00
刘洪洪
1410f890f3 CN-1415 fix: 修复搜索历史排序与时间展示问题 2023-11-08 15:42:23 +08:00
刘洪洪
78105475fb CN-1457 fix: 实体列表检索新增时间筛选框 2023-11-08 14:28:37 +08:00
刘洪洪
02a9f35070 fix: 搜索历史面板添加国际化 2023-11-07 18:42:41 +08:00
刘洪洪
c89397da97 CN-1415 fix: 建议Entity列表页增加Search History用于查看和使用历史搜索条件 2023-11-07 18:36:01 +08:00
chenjinsong
36c3db5dee CN-1414 fix: policy搜索框参数从name改为q 2023-11-07 16:35:28 +08:00
hanyuxia
366bb8f17f CN-1443 Detections-Security events模块中,Event type 下的事件类型名称被遮挡 2023-11-07 10:50:49 +08:00
刘洪洪
1c00f568fa CN-1449 fix: 实体搜索结果对命中字段高亮显示 2023-11-06 19:50:29 +08:00
chenjinsong
93be846064 CN-1461 feat: dashboard增加从config.js取时间范围的逻辑 2023-11-06 17:52:55 +08:00
chenjinsong
ffb822d65d fix: 提取默认默认时间条件为配置 2023-11-06 17:40:24 +08:00
刘洪洪
f8e9d36c8a fix: linkMonitor网格图删除空行空列方法添加空判断 2023-11-06 10:35:15 +08:00
刘洪洪
241665cd80 CN-1416 fix: 增加一些字符的国际化映射 2023-11-06 10:33:47 +08:00
chenjinsong
ee57ca4c6c fix: 修改注释 2023-11-03 15:02:31 +08:00
hyx
2bb6c54cab fix:1.自定义知识库列表,引用列,两个引用之间无空隙 2023-11-02 15:10:09 +08:00
hyx
65827d27e4 fix: 1.CN-1446 学习IP趋势图里未按照选择的时间范围展示对应时间周期;2.初次进入知识库的学习引擎日志tab,底部柱状图未初始化当前tab 2023-11-01 16:05:34 +08:00
刘洪洪
98948ff179 fix: 调整实体搜索框的样式 2023-11-01 15:17:49 +08:00
刘洪洪
74dc057090 CN-1447 fix: 修复在Entity搜索框中添加两个重复的搜索条件和一个不重复条件时,页面报错的问题 2023-11-01 14:50:41 +08:00
hyx
e8959bab27 CN-1424 在Network Overview搜索Providers/Applications后无法滚动查看所有符合搜索条件的数据 2023-11-01 14:37:23 +08:00
刘洪洪
a7ce777e10 fix: 修复detection点击左侧filter后取消不生效的问题 2023-11-01 14:18:59 +08:00
刘洪洪
d7d450222b fix: 修复首次登录时语言没有存入缓存的问题 2023-11-01 12:51:00 +08:00
刘洪洪
3da4b4b20a fix: 调整语言的中英文变量 2023-11-01 12:10:54 +08:00
chenjinsong
a0d2160b43 CN-1406 fix: npm map的client、server增加国际化 2023-11-01 11:44:23 +08:00
chenjinsong
b94dba9ed3 fix: 修复内置知识库更新记录有时候会缺少user列的问题 2023-11-01 10:30:36 +08:00
陈劲松
696b03ad40 Merge branch 'cherry-pick-0152c46d' into 'dev'
fix: 修复知识库更新记录的时间丢失时区的问题

See merge request cyber-narrator/cn-ui!57
2023-11-01 02:02:42 +00:00
chenjinsong
f199442c60 fix: 修复知识库更新记录的时间丢失时区的问题
(cherry picked from commit 0152c46d05)
2023-11-01 02:02:35 +00:00
陈劲松
889903bb46 Merge branch 'cherry-pick-64f376e2' into 'dev'
CN-1445 fix: 修复切换不同知识库的更新页面后,psiphon3的柱状图不能显示的问题

See merge request cyber-narrator/cn-ui!56
2023-11-01 02:02:15 +00:00
chenjinsong
e12d76e2ad CN-1445 fix: 修复切换不同知识库的更新页面后,psiphon3的柱状图不能显示的问题
(cherry picked from commit 64f376e22a)
2023-11-01 02:02:03 +00:00
陈劲松
29df6ec60e Merge branch 'cherry-pick-5b89fca7' into 'dev'
CN-1404 fix: 去掉其他知识库的update按钮

See merge request cyber-narrator/cn-ui!55
2023-11-01 02:01:33 +00:00
chenjinsong
20857e1203 CN-1404 fix: 去掉其他知识库的update按钮
(cherry picked from commit 5b89fca77c)
2023-11-01 02:00:37 +00:00
刘洪洪
8d4924a421 fix: 1、修复实体搜索偶现参数解析错误的问题;2、去除detections--policy无效打印 2023-10-31 18:08:23 +08:00
刘洪洪
41d2f48766 CN-1442 fix: 修复Detections多个搜索条件时,去除其中一个,再点击左侧filter刚去除那项导致删除参数错误的问题 2023-10-31 17:54:51 +08:00
刘洪洪
f151415de6 fix: detection--policy的trigger去除秒时间选项 2023-10-31 17:19:26 +08:00
hanyuxia
73dec68e23 fix:CN-1377 1.日历插件的国际化;2.分页插件的国际化;3.”域名“国际化 2023-10-31 15:47:50 +08:00
刘洪洪
60e821fb16 fix: 修复detection列表刷新界面后页码重置为1的问题 2023-10-31 14:43:29 +08:00
刘洪洪
4cb4aba707 CN-1442 fix: 修复Detections-Security events模块中,去掉搜索框筛选条件,左侧菜单栏仍勾选相应条件的问题 2023-10-31 14:04:51 +08:00
hanyuxia
ba4893dae1 fix:CN-1377 1.知识库自定义列表,reference列更多进行国际化 2023-10-31 11:26:46 +08:00
hanyuxia
19c42021db fix:1.顶部菜单只第四级菜单显示title信息;2.实体界面去掉输入框中的信息(Enter...); 2023-10-31 10:23:38 +08:00
hyx
d6c9bd0ee2 Merge remote-tracking branch 'origin/dev' into dev 2023-10-30 16:41:51 +08:00
hyx
63fd8b268f CN-1389 用户密码修改时不允许包含#号 2023-10-30 16:41:37 +08:00
chenjinsong
bd1f755612 CN-1425 fix: 修复dashboard下钻后切换顶部最后一级面包屑时会回到下钻前页面的问题 2023-10-30 16:28:33 +08:00
刘洪洪
dd4f5e1fba fix: eventType取消国际化转换 2023-10-30 16:25:55 +08:00
刘洪洪
ed1d994d5e fix: eventType取消国际化转换 2023-10-30 16:17:01 +08:00
刘洪洪
815af776aa fix: 修复policy新建时点击save按钮不生效的问题 2023-10-30 16:05:29 +08:00
chenjinsong
a4da1dbfac fix: 去掉部分控制台打印 2023-10-30 15:38:07 +08:00
hyx
9a3bf1a4af Merge remote-tracking branch 'origin/dev' into dev 2023-10-30 15:08:29 +08:00
hyx
6a196ba3f0 CN-1377 国际化内容补充:1.时间选择器国际化(右侧操作板国家化);2.下钻table中,protocol+ports的国际化;3.知识库两个小标题的国际化数据,中文的内容也是英文,需要分别改为:智能情报学习、WebSketch集成; 2023-10-30 15:08:17 +08:00
刘洪洪
7b8ca90436 fix: 修复policy新建时form的时间提示被盖住,以及限制输入框内容长度的问题 2023-10-30 15:05:48 +08:00
刘洪洪
90827fd706 fix: policy新建时添加小时不得超过24等时间限制 2023-10-30 14:37:00 +08: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
157 changed files with 15263 additions and 28043 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

27002
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,20 @@
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'
} }
// 默认时间过滤条件,单位分钟. 0表示请求接口时不传时间参数
const DEFAULT_TIME_FILTER_RANGE = {
dashboard: 60,
entity: {
list: 60,
trafficLine: 60,
informationAggregation: 0,
relatedEntity: 60 * 24 * 7,
openPort: 60 * 24 * 7,
securityEvent: 60 * 24 * 7,
performanceEvent: 60 * 24 * 7,
behaviorPattern: 60 * 24 * 7
},
detection: 60
}

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

@@ -54,7 +54,8 @@ export default {
return { return {
loading: false, loading: false,
username: '', username: '',
pin: '' pin: '',
language: ''
} }
}, },
methods: { methods: {
@@ -75,6 +76,9 @@ export default {
if (!_.isEmpty(res.data.data.user.lang)) { if (!_.isEmpty(res.data.data.user.lang)) {
localStorage.setItem(storageKey.language, res.data.data.user.lang) localStorage.setItem(storageKey.language, res.data.data.user.lang)
} }
if (!localStorage.getItem(storageKey.language)) {
localStorage.setItem(storageKey.language, this.language)
}
if (!_.isEmpty(res.data.data.user.theme)) { if (!_.isEmpty(res.data.data.user.theme)) {
localStorage.setItem(storageKey.theme, res.data.data.user.theme) localStorage.setItem(storageKey.theme, res.data.data.user.theme)
} }
@@ -107,6 +111,7 @@ export default {
}) })
}, },
appearanceOut (data) { appearanceOut (data) {
this.language = data.lang || defaultLang
if (_.isEmpty(localStorage.getItem(storageKey.language))) { if (_.isEmpty(localStorage.getItem(storageKey.language))) {
localStorage.setItem(storageKey.language, data.lang || defaultLang) localStorage.setItem(storageKey.language, data.lang || defaultLang)
} }

View File

@@ -3,84 +3,92 @@
height: 100%; height: 100%;
.search__suffixes { .search__suffixes {
height: 38px;
&.entity-explorer-home {
margin-right: 1px;
color: #3976CB;
&.search__suffixes--text-mode, &.search__suffixes--tag-mode {
.search__suffix:last-of-type {
width: unset;
height: unset;
margin-right: 12px;
background-color: transparent;
.el-icon-search {
color: #3976CB;
}
}
}
}
&.search__suffixes--text-mode, &.search__suffixes--tag-mode { &.search__suffixes--text-mode, &.search__suffixes--tag-mode {
position: absolute; position: absolute;
display: flex; display: flex;
top: 10px; align-items: center;
right: 10px; top: 1px;
right: 0;
.search__suffix { .search__suffix {
// margin-left: 8px; display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
border-radius: 0 2px 2px 0;
.cn-icon-search-advance, .cn-icon-search-normal, .cn-icon-filter { .cn-icon-search-advance, .cn-icon-search-normal, .cn-icon-filter {
color: #A6AAAE; color: #A6AAAE;
font-size: 18px; font-size: 18px;
} }
.el-icon-search {
color: #3976CB;
font-size: 22px;
}
&:hover { &:hover {
cursor: pointer; cursor: pointer;
} }
&:last-of-type {
margin-right: 0;
width: 41px;
height: 38px;
line-height: 39px;
background: #38ACD2;
.el-icon-search {
font-size: 22px;
color: #fff;
}
}
} }
.search__suffix-close { .search__suffix-close {
height: 40px;
line-height: 40px;
margin-top: -9px;
.el-icon-error { .el-icon-error {
font-size: 17px; font-size: 17px;
color: #C4C4C4; color: #C4C4C4;
margin-right: 12px;
cursor: pointer; cursor: pointer;
} }
} }
.entity-explorer-search {
color: #3976CB;
margin-top: -2px;
}
.margin-r-12 {
margin-right: 12px;
}
.new-search__suffix {
width: 41px;
height: 41px;
line-height: 41px;
background: #38ACD2;
text-align: center;
margin-top: -10px;
margin-right: -10px;
.el-icon-search {
color: #fff !important;
margin-top: 9px !important;
}
}
.my-popper-class .el-popper__arrow { .my-popper-class .el-popper__arrow {
display: none; display: none;
} }
} }
&.search__suffixes--tag-mode__block {
background: #fff;
} }
/*.search-tip--error {
font-size: 14px;
color: #F56C6C;
}*/
} }
.advanced-search--show-list .CodeMirror, .advanced-search--show-list .tag-search { }
border: none; .detections {
.tag-search, .CodeMirror {
border: 1px solid #E2E5EC;
}
} }
.tag-search { .tag-search {
display: flex; display: flex;
align-items: center; align-items: center;
height: 40px; height: 40px;
overflow: auto hidden; overflow: auto hidden;
border: 1px solid #CECECE;
border-radius: 2px; border-radius: 2px;
padding-left: 10px; padding-left: 10px;
padding-right: 80px; padding-right: 80px;
background-color: white; background-color: white;
border: 1px solid #DEDEDE;
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 8px; width: 8px;
@@ -104,6 +112,7 @@
border-radius: 1px; border-radius: 1px;
cursor: pointer; cursor: pointer;
transition: all linear .1s; transition: all linear .1s;
margin-right: 30px;
&:hover { &:hover {
background-color: white; background-color: white;
@@ -167,6 +176,9 @@
} }
} }
} }
.entity__search .tag-search {
padding-left: 65px;
}
.el-popover.my-popper-class { .el-popover.my-popper-class {
width: auto !important; width: auto !important;

View File

@@ -13,11 +13,16 @@
color: #ccc; color: #ccc;
} }
} }
.entity__search {
.CodeMirror {
padding-left: 60px;
}
}
/* PADDING */ /* PADDING */
.CodeMirror-lines { .CodeMirror-lines {
padding: 11px 5px; /* Vertical padding around content */ padding: 9px 5px; /* Vertical padding around content */
} }
.CodeMirror pre.CodeMirror-line, .CodeMirror pre.CodeMirror-line,
.CodeMirror pre.CodeMirror-line-like { .CodeMirror pre.CodeMirror-line-like {

View File

@@ -5,6 +5,7 @@
&>div { &>div {
height: 100%; height: 100%;
} }
overflow-y: auto;
} }
.cn-header { .cn-header {

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,7 +128,7 @@ $blue: #046ECA;
} }
} }
} }
.link-statistical-dimension {
.row-dot { .row-dot {
margin-top: 5px; margin-top: 5px;
margin-right: 5px; margin-right: 5px;
@@ -147,6 +147,7 @@ $blue: #046ECA;
border-radius: 50%; border-radius: 50%;
background: #E26154; background: #E26154;
} }
}
.item-popover-up, .item-popover-down { .item-popover-up, .item-popover-down {
font-size: 17px; font-size: 17px;

View File

@@ -15,8 +15,7 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-top: 20px; padding: 20px;
padding: 0 20px 20px;
.panel__title { .panel__title {
font-size: 24px; font-size: 24px;

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;
} }
} }
@@ -190,12 +194,13 @@
} }
} }
.form-setting__btn, .form-setting__btn1 { .form-setting__btn, .form-setting__btn1, .policy-form__footer__btn {
width: 100%; width: 100%;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
.el-button { .el-button {
width: 80px !important;
height: 30px !important; height: 30px !important;
min-height: 30px !important; min-height: 30px !important;
line-height: 30px !important; line-height: 30px !important;
@@ -208,9 +213,25 @@
font-weight: 500; font-weight: 500;
padding: 0 14px; padding: 0 14px;
} }
}
.form-setting__btn1 { .btn1 {
margin-right: 10px;
.el-button {
background: #F5F6F7;
border: 1px solid rgba(215,215,215,1);
color: #353636;
}
}
}
.policy-form__footer__btn {
justify-content: center;
margin-top: 8px;
.btn1 {
margin-right: 16px;
}
}
.form-setting__btn1, .policy-form__footer__btn {
.el-button { .el-button {
padding: 0 11px !important; padding: 0 11px !important;
} }

View File

@@ -10,9 +10,9 @@
} }
.detection-form-content { .detection-form-content {
height: 100%; height: calc(100% - 92px);
overflow: scroll; overflow: scroll;
padding-bottom: 40px; padding-bottom: 20px;
.detection-form-collapse { .detection-form-collapse {
margin-top: 20px; margin-top: 20px;
@@ -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 {
@@ -124,6 +130,12 @@
} }
} }
} }
.policy-form-trigger {
.el-collapse-item__content {
padding-bottom: 0 !important;
}
}
} }
.el-input--mini, .el-input--mini .el-input__inner { .el-input--mini, .el-input--mini .el-input__inner {
@@ -137,4 +149,13 @@
padding-bottom: 20px; padding-bottom: 20px;
} }
.policy-form__footer {
width: calc(100% + 40px);
height: 60px;
margin-left: -20px;
box-shadow: 0 -1px 4px 0 rgba(0,0,0,0.10);
display: flex;
align-items: center;
justify-content: center;
}
} }

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;
@@ -34,6 +34,15 @@
.drawer-basic-id { .drawer-basic-id {
margin-bottom: 10px; margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
i {
font-size: 14px;
font-weight: 400;
cursor: pointer;
}
} }
} }
} }
@@ -58,6 +67,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

@@ -2,98 +2,124 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 280px; width: 280px;
padding: 10px; margin-right: 12px;
margin-right: 10px;
background-color: white;
overflow: auto; overflow: auto;
z-index: 1;
border: 1px solid rgba(226, 229, 236, 1) !important;
border-radius: 4px !important;
.detection-filter { .filter-case__header {
display: flex; padding-left: 8px;
flex-direction: column; height: 36px;
margin-bottom: 10px; line-height: 36px;
color: #666;
font-size: 14px;
background: #F7F7F7;
box-shadow: 0 1px 0 0 rgba(226,229,236,1);
border-radius: 4px 4px 0 0;
}
.filter__header { .filter__header {
display: flex; height: 46px;
flex: 0 0 32px; line-height: 46px;
align-items: center; margin: 0 20px;
padding-left: 10px;
color: #666;
//background-color: #F3F7FA;
cursor: pointer;
span {
font-size: 14px;
padding-left: 6px;
}
i {
font-size: 12px;
transition: all linear .1s;
transform: rotate(0) translate(0, 2px);
}
i.arrow-rotate {
transform: rotate(90deg) translate(2px, 3px);
}
.new-detection-filter-header-title {
font-size: 14px; font-size: 14px;
color: #353636; color: #353636;
font-weight: 600; font-weight: 500;
}
.new-detection-filter-icon {
margin-left: 8px;
margin-bottom: 2px;
font-weight: bold !important;
}
} }
.filter__body { .filter__body {
padding: 5px 0 0 15px; width: calc(100% - 30px);
margin: 0 10px 0 20px;
max-height: 265px;
overflow-y: scroll;
overflow-x: hidden;
.el-checkbox-group { .filter__body-item {
display: flex; height: 26px;
flex-direction: column; line-height: 26px;
.el-checkbox {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 5px 0;
margin-right: 5px;
.el-checkbox__label {
width: 100%;
}
.filter__checkbox-label {
display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center;
.severity-color-block {
width: 4px;
height: 15px;
border-radius: 2px;
}
}
&:last-of-type {
padding-bottom: 0;
}
}
}
}
}
.new-detection-filter-title {
display: flex;
flex: 0 0 32px;
align-items: center;
padding-left: 27px;
background-color: #EFF2F5;
cursor: pointer; cursor: pointer;
.filter__body-item-left {
display: flex;
align-items: center;
font-size: 14px; font-size: 14px;
color: #353636; color: #353636;
font-weight: 600; font-weight: 400;
margin: -10px;
margin-bottom: 10px; .filter__body-item-left-index {
width: 16px;
height: 16px;
text-align: center;
background: #EFF2F5;
border-radius: 2px;
margin-right: 6px;
font-family: NotoSansHans-Black;
font-size: 9px;
color: #96A2B0;
font-weight: 900;
display: flex;
align-items: center;
justify-content: center;
}
.filter__body-item-left-label {
max-width: 180px;
font-family: NotoSansSChineseRegular;
font-size: 14px;
color: #353636;
font-weight: 400;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
} }
} }
.filter__body-item-right {
flex-shrink: 0;
font-family: NotoSansSChineseRegular;
font-size: 12px;
color: #717171;
font-weight: 400;
margin-right: 10px;
}
}
}
}
.filter-country-flag {
width: 18px;
height: 12px;
margin-right: 6px;
border: 1px solid #E8E8E8;
}
.filter-show-more, .filter-no-show-more {
cursor: pointer;
height: 26px;
line-height: 26px;
margin-left: 20px;
color: #046ECA;
user-select: none; // 禁止文本选中
font-size: 12px;
}
.filter-no-show-more {
cursor: not-allowed;
color: rgba(16, 16, 16, 0.3);
}
.filter-hr {
width: calc(100% - 40px);
margin-left: 20px;
margin-top: 6px;
height: 1px;
background: #EFF2F5;
//background: #000;
}
.new-detection-filter-title { .new-detection-filter-title {
height: 32px; height: 32px;
line-height: 32px; line-height: 32px;

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);
@@ -145,7 +149,7 @@
flex-direction: row; flex-direction: row;
flex-wrap: wrap; flex-wrap: wrap;
.basic-info__item { .basic-info__item, .basic-info__item1 {
padding-right: 30px; padding-right: 30px;
display: flex; display: flex;
align-items: center; align-items: center;
@@ -168,6 +172,11 @@
color: #666; color: #666;
} }
} }
.basic-info__item1 {
span: {
color: #666;
}
}
} }
.show-detail { .show-detail {
@@ -262,3 +271,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;
} }
} }
} }
@@ -186,6 +203,7 @@
color: #046ECA; color: #046ECA;
margin-bottom: 10px; margin-bottom: 10px;
font-weight: 500; font-weight: 500;
height: 36px;
} }
.timeline__start-time { .timeline__start-time {
font-size: 12px; font-size: 12px;
@@ -215,13 +233,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

@@ -26,7 +26,7 @@
justify-content: flex-start; justify-content: flex-start;
} }
.explorer-top-tools, .explorer-detection-top-tools { .explorer-top-tools, .explorer-detection-top-tools, .explorer-entity-top-tools {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;
@@ -46,7 +46,10 @@
} }
} }
} }
.explorer-detection-top-tools { .explorer-entity-top-tools {
width: 100%;
}
.explorer-detection-top-tools, .explorer-entity-top-tools {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
@@ -87,6 +90,13 @@
font-size: 14px; font-size: 14px;
color: #353636; color: #353636;
font-weight: 400; font-weight: 400;
.entity-hide-entity {
margin-left: 20px;
.el-checkbox__label {
padding-left: 6px;
}
}
} }
.explorer-container, .explorer-container-new { .explorer-container, .explorer-container-new {
display: flex; display: flex;

View File

@@ -2,7 +2,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 320px; width: 320px;
margin-right: 20px; margin-right: 12px;
overflow: auto; overflow: auto;
z-index: 1; z-index: 1;
border: 1px solid rgba(226, 229, 236, 1) !important; border: 1px solid rgba(226, 229, 236, 1) !important;

View File

@@ -349,6 +349,25 @@
.data-score-green { .data-score-green {
background: #749F4D; background: #749F4D;
} }
.score-dot {
display: inline-block;
margin-bottom: 2px;
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

@@ -79,11 +79,22 @@
.cn-entity__header-title { .cn-entity__header-title {
margin-right: 10px; margin-right: 10px;
} }
.cn-entity__header-tag { .entity-related-entity {
font-size: 12px;
color: #717171;
cursor: pointer;
margin-right: 6px;
} }
} }
.entity-row-tag {
display: flex;
margin-left: 6px;
margin-top: 1px;
flex-wrap: wrap;
margin-bottom: -10px;
}
.cn-entity__body { .cn-entity__body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -98,7 +109,7 @@
flex-direction: row; flex-direction: row;
flex-wrap: wrap; flex-wrap: wrap;
.basic-info__item { .basic-info__item, .basic-info__item1 {
padding-right: 30px; padding-right: 30px;
.item__box { .item__box {
@@ -161,6 +172,17 @@
color: #666; color: #666;
} }
} }
.basic-info__item1 {
span: {
color: #666;
}
span:first-of-type {
color: #666;
}
.row-item-label {
color: #999 !important;
}
}
.row-item-label { .row-item-label {
font-family: NotoSansSChineseRegular; font-family: NotoSansSChineseRegular;
@@ -174,6 +196,26 @@
font-size: 14px; font-size: 14px;
color: #353636; color: #353636;
font-weight: 400; font-weight: 400;
.score-dot {
display: inline-block;
margin-bottom: 2px;
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;
}
}
} }
} }
} }

View File

@@ -26,7 +26,8 @@
padding: 0 20px; padding: 0 20px;
&.explorer-search__input-case--question-mark-in-line { &.explorer-search__input-case--question-mark-in-line {
flex-direction: row; //flex-direction: row;
flex-direction: column;
padding: 0; padding: 0;
.explorer-search__input { .explorer-search__input {
@@ -34,9 +35,8 @@
max-width: unset; max-width: unset;
} }
.explorer-search__input__border { .explorer-search__input--border .CodeMirror {
border: 1px #DEDEDE solid; border: 1px solid #DEDEDE;
height: 43px;
} }
} }
.search-symbol-inline { .search-symbol-inline {
@@ -53,14 +53,14 @@
max-width: 1000px; max-width: 1000px;
height: 40px; height: 40px;
} }
.explorer-search__foot { .explorer-search__foot,.explorer-search__foot-list {
display: flex; display: flex;
padding-top: 18px; padding-top: 9px;
width: 100%; width: 100%;
max-width: 1000px; max-width: 1000px;
position: relative; position: relative;
justify-content: space-between; justify-content: flex-start;
font-weight: bold; //font-weight: bold;
.foot__item { .foot__item {
display: flex; display: flex;
@@ -87,14 +87,15 @@
.search__history { .search__history {
position: absolute; position: absolute;
top: 6px;
display: flex; display: flex;
padding: 10px 0 0 0; padding: 10px 0 0 0;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
max-width: 1000px; max-width: 1000px;
z-index: 2; z-index: 2;
top: 47px; //top: 47px;
border: 1px solid rgba(206,206,206,0.20); border: 1px solid rgba(226,229,236,1);
border-radius: 2px; border-radius: 2px;
background-color: white; background-color: white;
@@ -137,8 +138,32 @@
color: #66b1ff; color: #66b1ff;
} }
} }
.history__items-new {
max-height: 300px;
overflow: auto;
.el-table th,.el-table td {
padding: 6px 0;
border: none !important;
}
.el-table {
font-family: NotoSansSChineseRegular;
font-size: 14px;
color: #575757;
font-weight: 400;
thead {
font-family: NotoSansHans-Medium;
font-size: 14px;
color: #353636;
font-weight: 500;
}
.cell {
padding-left: 18px;
}
}
}
.clear-all { .clear-all {
padding-left: 30px; padding-left: 18px;
font-weight: normal; font-weight: normal;
font-size: 14px; font-size: 14px;
height: 35px; height: 35px;
@@ -152,6 +177,71 @@
} }
} }
} }
.explorer-search__foot-list {
max-width: 756px;
position: absolute;
left: 196px;
top: 44px;
.search__history {
top: 6px;
max-width: 756px;
margin-left: -196px;
.history__items-new {
max-height: 300px;
overflow: auto;
.el-table th,.el-table td {
padding: 4px 0;
border: none !important;
}
.el-table {
font-size: 12px;
thead {
font-size: 12px;
}
}
.el-table--scrollable-x .el-table__body-wrapper {
overflow-x: hidden;
}
}
}
}
} }
} }
.highlight__text {
background: #FEECC2;
padding: 0 3px;
}
.highlight__block {
background: #FEECC2;
}
.explorer-search__foot-list .explorer-search__block {
margin-left: -196px;
top: -44px;
}
.explorer-search__foot .explorer-search__block {
margin-left: 0;
top: -40px;
}
.explorer-search__block {
position: absolute;
padding-left: 15px;
height: 39px;
display: flex;
align-items: center;
cursor: pointer;
z-index: 1;
i {
font-size: 20px;
color: #999;
}
.search-dividing-line {
width: 1px;
height: 26px;
background: #DEDEDE;
margin-left: 15px;
margin-top: 2px;
}
}

View File

@@ -1156,7 +1156,6 @@ height: 100%;
.el-form { .el-form {
margin-top: 20px; margin-top: 20px;
width: 620px; width: 620px;
label { label {
padding-bottom: 6px; padding-bottom: 6px;
font-size: 14px; font-size: 14px;
@@ -1191,6 +1190,7 @@ height: 100%;
.el-input__inner { .el-input__inner {
background-color: white !important; background-color: white !important;
padding-left: 22px !important;
} }
} }
} }
@@ -1550,11 +1550,46 @@ height:300px !important;
} }
} }
} }
.update-knowledge-tip { .confirm-knowledge-switch {
display: flex;
flex-direction: column;
.el-dialog__header {
display: flex;
flex-direction: row;
border-bottom: 1px solid #eee;
height: 42px;
min-height: 42px;
padding-left: 20px;
padding-top: 10px;
padding-bottom: 14px;
background: #F7F7F7;
.el-dialog__headerbtn {
display: flex !important;
flex-direction: row-reverse;
justify-content: center;
align-items: center;
padding-right: 5px !important;
top: 16px;
right: 10px;
i {
font-size: 12px;
}
}
.el-dialog__title {
font-size: 14px !important;
color: #353636;
letter-spacing: 0;
font-weight: 400;
}
}
}
.update-knowledge-tip, .confirm-knowledge-switch {
display:flex; display:flex;
flex-direction: column; flex-direction: column;
padding-bottom: 0 !important; padding-bottom: 0 !important;
width: 480px !important;
.el-dialog__header{ .el-dialog__header{
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -1564,19 +1599,20 @@ height:300px !important;
background: #F7F7F7; background: #F7F7F7;
box-shadow: 0 1px 0 0 rgba(53,54,54,0.08); box-shadow: 0 1px 0 0 rgba(53,54,54,0.08);
padding-left: 20px; padding-left: 20px;
padding-top:14px; padding-top: 10px;
padding-bottom: 14px; padding-bottom: 14px;
.el-dialog__headerbtn { .el-dialog__headerbtn {
display: flex !important; display: flex !important;
flex-direction: row-reverse; flex-direction: row-reverse;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
font-size: 10px;
line-height: 10px;
padding-right: 5px !important; padding-right: 5px !important;
top: 16px;
right: 10px;
i { i {
width:10px; font-size: 12px;
height:10px;
} }
} }
.el-dialog__title { .el-dialog__title {
@@ -1593,14 +1629,9 @@ height:300px !important;
color: #353636; color: #353636;
letter-spacing: 0; letter-spacing: 0;
line-height: 22px; line-height: 22px;
font-weight: 400; padding-top:12px;
padding-top:8px;
padding-right: 20px; padding-right: 20px;
padding-left: 20px; padding-left: 20px;
.dialog-message {
padding-left: 0 !important;
padding-right: 0 !important;
}
} }
.el-dialog__footer { .el-dialog__footer {
height:52px; height:52px;
@@ -1645,7 +1676,8 @@ height:300px !important;
flex-direction: column; flex-direction: column;
padding-bottom:0 !important; padding-bottom:0 !important;
width: 1080px !important; width: 1080px !important;
height: 75vh; height: 90vh;
margin-top:5vh !important;
overflow: hidden; overflow: hidden;
&.update-knowledge--upload { &.update-knowledge--upload {
height: auto; height: auto;
@@ -1699,6 +1731,85 @@ height:300px !important;
} }
} }
} }
.knowledge-update__tab {
position:relative;
.update-log-tab {
.el-tabs__header {
margin-bottom:10px;
.el-tabs__active-bar {
background-color:#046ECA;
height:2px;
}
.el-tabs__item {
font-family: NotoSansSC-Bold;
font-size: 14px;
//color: #353636;
font-weight: 700;
padding:0 16px 0 0;
}
.el-tabs__item:hover {
color:#353636;
}
.el-tabs__item.is-active {
color: #046ECA;
}
.el-tabs__nav-wrap::after {
height:1px;
}
}
.update-title {
line-height: 14px;
font-family: NotoSansSC-Bold;
font-size: 14px;
color: #353636;
font-weight: 700;
}
}
.update-operate {
position: absolute;
top: 0px;
display: flex;
align-items: center;
right: 0px;
height:28px;
flex-direction: row;
justify-content:center;
font-size: 14px;
font-weight: 400;
.top-tool-btn--update {
background-color: #38ACD2 !important;
padding-left:7px;
padding-right:7px;
color:#FFFFFF;
font-size: 12px;
font-weight: 500;
border: 1px solid rgba(46,136,166,0.85);
border-radius: 2px;
line-height: 28px;
height:28px;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
i {
color: #FFFFFF;
font-size:14px !important;
margin-right:5px;
}
}
.top-tool-btn--update:disabled {
cursor: not-allowed;
opacity: 0.66;
i {
}
}
.top-tool-btn--update:hover {
background-color: #57B8D9 !important;
border-color: #2E88A6 !important;
}
}
}
.knowledge-update { .knowledge-update {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -1741,6 +1852,12 @@ height:300px !important;
margin-right:5px; margin-right:5px;
} }
} }
.top-tool-btn--update:disabled {
cursor: not-allowed;
opacity: 0.66;
i {
}
}
.top-tool-btn--update:hover { .top-tool-btn--update:hover {
background-color: #57B8D9 !important; background-color: #57B8D9 !important;
border-color: #2E88A6 !important; border-color: #2E88A6 !important;
@@ -1789,15 +1906,20 @@ height:300px !important;
.update-dialog__table { .update-dialog__table {
border-bottom: none; border-bottom: none;
border-radius: 0.28571rem; border-radius: 0.28571rem;
max-height:calc(75vh - 190px); height: calc(100% - 125px);//dialog为屏幕的90% - 表格以上内容的高度 - pishon3柱状图的高度 - 表格和柱状图之间的距离 - 底部留白的距离
.el-table--border th, .el-table--border td { .el-table--border th, .el-table--border td {
border-right: none; border-right: none;
} }
.el-table__body-wrapper { .el-table__body-wrapper {
height:fit-content; height: calc(100% - 48px);
max-height: calc(75vh - 230px);
overflow-y: auto; overflow-y: auto;
} }
&.update-dialog__table--psiphon3 {
height: calc(90vh - 190px - 200px - 50px - 10px);
}
&.update-dialog__table--system-user {
height: calc(100% - 139px);
}
} }
.update-knowledge-form { .update-knowledge-form {
.el-upload { .el-upload {
@@ -1871,4 +1993,173 @@ height:300px !important;
} }
} }
} }
.psiphon3{
display:flex;
flex-direction: column;
margin-top:25px;
.psiphon3-title {
font-family: NotoSansSC-Medium;
font-size: 12px;
color: #353636;
letter-spacing: 0;
line-height: 14px;
font-weight: 500;
}
.psiphon3-bar {
height: 200px;
width: 100%;
position: relative;
border: 1px solid #E2E5EC;
border-radius: 4px;
margin-top:10px;
//min-height:250px;
.chart-drawing {
height: 100%;
width: 100%;
.echarts-tooltip.echarts-tooltip-dark {
.cn-chart-body {
display: flex;
.cn-chart-tooltip {
display: flex;
flex-direction: column;
flex: 1;
.cn-chart-tooltip-box {
margin-right: 10px;
}
.cn-chart-tooltip-value.cn-chart-tooltip__color {
font-size: 12px;
color: #353636;
line-height: 21px;
font-weight: 600;
}
}
}
}
}
.bar-header {
display: flex;
justify-content: space-between;
//height: 73px;
margin-right:20px;
.bar-select.bar-header-right {
display: flex;
//flex: 1;
.bar-select__operation {
.el-input__inner {
width: 132px;
}
}
.bar-select-time {
display: flex;
align-items: flex-end;
.bar-select__operation {
height: 24px;
margin-left: 3px;
box-shadow: none;
border-radius: 2px;
.cn-icon-Data {
color: #353636;
}
.el-input__inner {
padding-left: 4px;
line-height: 24px;
height: 24px;
font-size: 12px;
color: #353636;
font-weight: 400;
}
.el-input__suffix {
display: flex;
.el-input__suffix-inner {
line-height: 24px;
.el-select__caret {
line-height: 24px;
width: 16px;
color: #353636;
}
}
}
}
}
}
.bar-header-left {
.bar-value-active {
position: absolute;
height: 4px;
border-radius: 4px 0 0 0;
background: #046ECA;
top: 0;
z-index: 1;
transition: all linear .2s;
}
.bar-value {
font-size: 12px;
color: #717171;
font-weight: 400;
display: flex;
justify-content: center;
.is-active {
color: #353636;
}
.bar-value-tabs.mousemove-cursor {
border-top: 4px solid #D3D0D8;
z-index: 0;
}
.bar-value-tabs {
cursor: pointer;
padding: 10px 0 0 20px;
border-top: 4px solid transparent;
width:88px;
.bar-value-tabs-name {
position: relative;
display: flex;
.tabs-name {
flex: 1;
padding-left: 19px;
}
.total,.active,.new {
width: 10px;
height: 10px;
border-radius: 50%;
position: absolute;
top: 6px;
}
.total {
background: #00A7AB;
}
.active {
background: #7FA054;
}
.new {
background: #98709B;
}
}
}
}
}
}
}
}
}
.knowledge-color {
display:flex;
flex-direction: row;
align-items: center;
.knowledge-color__icon {
width: 10px;
height: 10px;
border-radius: 2px;
margin-right:6px;
margin-left:2px;
}
}
.info {
background-color:rgb(119,131,145)
}
.benign {
background-color:rgb(116,159,77)
}
.malicious {
background-color:rgb(226,97,84)
} }

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=1699411209748') format('woff2'),
url('iconfont.woff?t=1693386443164') format('woff'), url('iconfont.woff?t=1699411209748') format('woff'),
url('iconfont.ttf?t=1693386443164') format('truetype'); url('iconfont.ttf?t=1699411209748') format('truetype');
} }
.cn-icon { .cn-icon {
@@ -13,6 +13,22 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.cn-icon-related:before {
content: "\e640";
}
.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

@@ -90,8 +90,8 @@
</template> </template>
</div> </div>
<div class="tag-search__add" @click="addCondition">{{$t('entities.advancedSearch.add')}}</div> <div class="tag-search__add" @click="addCondition">{{$t('entities.advancedSearch.add')}}</div>
<div class="search__suffixes search__suffixes--tag-mode"> <div class="search__suffixes search__suffixes--tag-mode search__suffixes--tag-mode__block" :class="showList ? '' : 'entity-explorer-home'">
<div class="search__suffix" style="margin-right: 12px"> <span class="search__suffix">
<el-popover <el-popover
popper-class="my-popper-class" popper-class="my-popper-class"
placement="top" placement="top"
@@ -102,13 +102,13 @@
<i class="cn-icon cn-icon-search-normal" @click="changeMode"></i> <i class="cn-icon cn-icon-search-normal" @click="changeMode"></i>
</template> </template>
</el-popover> </el-popover>
</div> </span>
<div v-show="metaList.length>0" class="search__suffix-close" @click="cleanMetaList"> <span v-show="metaList.length>0" class="search__suffix search__suffix-close" @click="cleanMetaList">
<i class="el-icon-error"></i> <i class="el-icon-error"></i>
</div> </span>
<div class="search__suffix" :class="showList ? 'new-search__suffix' : 'entity-explorer-search'" @click="search"> <span class="search__suffix" @click="search">
<i class="el-icon-search"></i> <i class="el-icon-search"></i>
</div> </span>
</div> </div>
</div> </div>
</template> </template>
@@ -202,6 +202,7 @@ export default {
if (this.isCustomized(value)) { if (this.isCustomized(value)) {
meta.column.type = columnType.fullText meta.column.type = columnType.fullText
meta.column.label = value meta.column.label = value
meta.column.isFullText = true
meta.resetOperator() meta.resetOperator()
meta.resetValue() meta.resetValue()
} else { } else {
@@ -248,6 +249,7 @@ export default {
if (meta.column && meta.column.type === 'fullText') { if (meta.column && meta.column.type === 'fullText') {
meta.operator.value = '=' meta.operator.value = '='
meta.column.show = false meta.column.show = false
meta.column.isFullText = true
meta.operator.show = false meta.operator.show = false
const label = JSON.parse(JSON.stringify(meta.column.label)) const label = JSON.parse(JSON.stringify(meta.column.label))
meta.column.label = parser.getEntityTypeByValue(meta.column.label) meta.column.label = parser.getEntityTypeByValue(meta.column.label)
@@ -461,13 +463,21 @@ export default {
if (this.metaList.length > 0) { if (this.metaList.length > 0) {
const parser = new Parser(this.columnList) const parser = new Parser(this.columnList)
const errorList = parser.validateMeta(this.metaList) const errorList = parser.validateMeta(this.metaList)
const keywordList = []
this.metaList.forEach(item => {
if (item.column && item.column.isFullText) {
keywordList.push({ type: 'fullText', value: item.value.value })
} else if (item.column && !item.column.isFullText) {
keywordList.push({ type: item.column.type, value: item.value.value })
}
})
if (_.isEmpty(errorList)) { if (_.isEmpty(errorList)) {
const strObj = parser.handleMetaListToStr(this.metaList) const strObj = parser.handleMetaListToStr(this.metaList)
const str = strObj.str ? strObj.str : strObj const str = strObj.str ? strObj.str : strObj
const str2 = strObj.str2 ? strObj.str2 : strObj const str2 = strObj.str2 ? strObj.str2 : strObj
// str为将metaList转成字符串的值str2为地址栏展示的值 // str为将metaList转成字符串的值str2为地址栏展示的值
const key = parser.handleEntityTypeByStr(str) const key = parser.handleEntityTypeByStr(str)
this.$emit('search', { ...parser.parseStr(key), str: str2 }) this.$emit('search', { ...parser.parseStr(key), str: str2, keywordList: keywordList })
} else { } else {
this.$message.error(handleErrorTip(errorList[0])) this.$message.error(handleErrorTip(errorList[0]))
} }
@@ -555,6 +565,8 @@ export default {
const column = this.columnList.find(c => { const column = this.columnList.find(c => {
return c.label === param.column return c.label === param.column
}) })
const obj = this.metaList.find(d => d.column && d.column.label === param.column && d.value && (d.value.value === `'${param.value[0]}'` || d.value.value === param.value[0]))
if (obj) {
const metaIndex = this.metaList.findIndex(m => m.column && m.column.label === param.column && m.operator.value === param.operator && m.value.value === this.handleValue(param.value, column, param.operator)) const metaIndex = this.metaList.findIndex(m => m.column && m.column.label === param.column && m.operator.value === param.operator && m.value.value === this.handleValue(param.value, column, param.operator))
// 不是在首位则删除时顺带删除前一个indexand或or否则顺带删除后一个index // 不是在首位则删除时顺带删除前一个indexand或or否则顺带删除后一个index
if (metaIndex > 0) { if (metaIndex > 0) {
@@ -564,6 +576,7 @@ export default {
} else { } else {
this.metaList.splice(metaIndex, 2) this.metaList.splice(metaIndex, 2)
} }
}
}) })
}, },
/** /**
@@ -622,8 +635,16 @@ export default {
let { q } = this.$route.query let { q } = this.$route.query
if (q && !this.convertMetaList) { if (q && !this.convertMetaList) {
const parser = new Parser(this.columnList) const parser = new Parser(this.columnList)
if (q.indexOf('+') > -1) {
q = q.replace('+', '')
}
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
} }
@@ -645,6 +666,7 @@ export default {
if (item.column && item.column.type === 'fullText') { if (item.column && item.column.type === 'fullText') {
item.operator.value = '=' item.operator.value = '='
item.column.show = false item.column.show = false
item.column.isFullText = true
item.operator.show = false item.operator.show = false
const label = JSON.parse(JSON.stringify(item.column.label)) const label = JSON.parse(JSON.stringify(item.column.label))
item.column.label = parser.getEntityTypeByValue(item.column.label) item.column.label = parser.getEntityTypeByValue(item.column.label)

View File

@@ -1,9 +1,11 @@
<template> <template>
<textarea <textarea
style="text-indent: 65px;"
cols="40"
ref="textSearch" ref="textSearch"
></textarea> ></textarea>
<div class="search__suffixes search__suffixes--text-mode"> <div class="search__suffixes search__suffixes--text-mode" :class="showList ? '' : 'entity-explorer-home'" style="padding-left: 1px">
<div class="search__suffix"> <span class="search__suffix">
<el-popover <el-popover
popper-class="my-popper-class" popper-class="my-popper-class"
placement="top" placement="top"
@@ -11,16 +13,16 @@
:content="$t('entity.switchToAdvancedSearch')" :content="$t('entity.switchToAdvancedSearch')"
> >
<template #reference> <template #reference>
<i class="cn-icon cn-icon-filter margin-r-12" @click="changeMode"></i> <i class="cn-icon cn-icon-filter" @click="changeMode"></i>
</template> </template>
</el-popover> </el-popover>
</div> </span>
<div v-show="isCloseIcon" class="search__suffix-close" @click="cleanParams"> <span v-show="isCloseIcon" class="search__suffix search__suffix-close" @click="cleanParams">
<i class="el-icon-error"></i> <i class="el-icon-error"></i>
</div> </span>
<div class="search__suffix" :class="showList ? 'new-search__suffix' : 'entity-explorer-search'" @click="search"> <span class="search__suffix" @click="search">
<i class="el-icon-search"></i> <i class="el-icon-search"></i>
</div> </span>
</div> </div>
</template> </template>
@@ -68,7 +70,7 @@ export default {
mode: { mode: {
name: 'sql' name: 'sql'
}, },
placeholder: 'Enter...', placeholder: '',
lineNumbers: false lineNumbers: false
}) })
this.codeMirror.setOption('extraKeys', { this.codeMirror.setOption('extraKeys', {
@@ -97,10 +99,19 @@ export default {
if (str) { if (str) {
const parser = new Parser(this.columnList) const parser = new Parser(this.columnList)
const keyInfo = parser.comparedEntityKey(parser.handleEntityTypeByStr(str)) const keyInfo = parser.comparedEntityKey(parser.handleEntityTypeByStr(str))
const metaList = parser.parseStr(_.cloneDeep(str)).metaList
const keywordList = []
metaList.forEach(item => {
if (item.column && item.column.type === columnType.fullText) {
keywordList.push({ type: item.column.type, value: item.column.label })
} else if (item.column && item.column.type === columnType.string) {
keywordList.push({ type: item.column.type, value: item.value.value })
}
})
if (keyInfo.isKey) { if (keyInfo.isKey) {
const errorList = parser.validateStr(keyInfo.key) const errorList = parser.validateStr(keyInfo.key)
if (_.isEmpty(errorList)) { if (_.isEmpty(errorList)) {
this.$emit('search', { ...parser.parseStr(keyInfo.key), str: str }) this.$emit('search', { ...parser.parseStr(keyInfo.key), str: str, keywordList: keywordList })
} else { } else {
this.$message.error(handleErrorTip(errorList[0])) this.$message.error(handleErrorTip(errorList[0]))
} }
@@ -223,8 +234,16 @@ export default {
toRaw(this.codeMirror).setValue(this.str) toRaw(this.codeMirror).setValue(this.str)
} }
if (q) { if (q) {
if (q.indexOf('+') > -1) {
q = q.replace('+', '')
}
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的值组合起来
@@ -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)
} }
@@ -1399,8 +1414,8 @@ export default class Parser {
} else if (i.toLowerCase() === 'tag') { } else if (i.toLowerCase() === 'tag') {
lastObj[i] = `has(${i},${commonObj[i]})` lastObj[i] = `has(${i},${commonObj[i]})`
} else { } else {
// 单独存在的,直接保留 // 单独存在的,直接保留 todo 后续观察当初添加单引号动机和问题
lastObj[i] = `${i} = '${commonObj[i]}'` lastObj[i] = `${i} = ${commonObj[i]}`
} }
} }

View File

@@ -1,5 +1,6 @@
<template> <template>
<div class="pagination" > <div class="pagination" >
<el-config-provider :locale="locale">
<el-pagination <el-pagination
ref="page" ref="page"
@size-change="size" @size-change="size"
@@ -20,16 +21,19 @@
</el-select> </el-select>
</el-pagination> </el-pagination>
</el-config-provider>
</div> </div>
</template> </template>
<script> <script>
import { defaultPageSize, storageKey } from '@/utils/constants' import { defaultPageSize, storageKey, ZH, EN } from '@/utils/constants'
import { urlParamsHandler, overwriteUrl } from '@/utils/tools' import { urlParamsHandler, overwriteUrl } from '@/utils/tools'
import { ref } from 'vue' import { ref } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { parseInt } from 'lodash' import { parseInt } from 'lodash'
import ElConfigProvider from 'element-plus'
import cn from 'element-plus/lib/locale/lang/zh-cn'
import en from 'element-plus/lib/locale/lang/en'
export default { export default {
name: 'pagination', name: 'pagination',
@@ -60,9 +64,15 @@ export default {
const { query } = useRoute() const { query } = useRoute()
const pageSize = ref(defaultPageSize) const pageSize = ref(defaultPageSize)
const currentPageNo = ref(props.storePageNoOnUrl ? (query.pageNo || (props.pageObj.pageNo || 1)) : (props.pageObj.pageNo || 1)) const currentPageNo = ref(props.storePageNoOnUrl ? (query.pageNo || (props.pageObj.pageNo || 1)) : (props.pageObj.pageNo || 1))
const language = localStorage.getItem(storageKey.language) || EN // 初始未选择默认 en 英文
let locale = en
if (language === ZH) {
locale = cn
}
return { return {
pageSize, pageSize,
currentPageNo currentPageNo,
locale
} }
}, },
data () { data () {

View File

@@ -18,7 +18,8 @@
<div v-if="dropdownFlag" class="date-range-panel"> <div v-if="dropdownFlag" class="date-range-panel">
<el-row class="date-range-panel-top" style="position: relative"> <el-row class="date-range-panel-top" style="position: relative">
<el-col :span="16" class="date-range-panel-content date-range-panel-content-left"> <el-col :span="16" class="date-range-panel-content date-range-panel-content-left">
<div class="date-range-title" style="padding-left: 0">Absolute time range</div> <div class="date-range-title" style="padding-left: 0">{{$t('dateTime.absoluteTimeRange')}}</div>
<el-config-provider :locale="locale">
<el-date-picker <el-date-picker
v-model="newDateValue" v-model="newDateValue"
ref="newDatePicker" ref="newDatePicker"
@@ -30,16 +31,17 @@
type="datetimerange" type="datetimerange"
@change="timeArrChange" @change="timeArrChange"
/> />
<div class="content-title">From</div> </el-config-provider>
<div class="content-title">{{$t('dateTime.from')}}</div>
<div @click="myDatePickerShow" tabindex="1" class="content-input"> <div @click="myDatePickerShow" tabindex="1" class="content-input">
{{ dateFormatByAppearance(getMillisecond(myStartTime)) }} {{ dateFormatByAppearance(getMillisecond(myStartTime)) }}
</div> </div>
<div class="content-title">To</div> <div class="content-title">{{$t('dateTime.to')}}</div>
<div @click="myDatePickerShow" tabindex="2" class="content-input"> <div @click="myDatePickerShow" tabindex="2" class="content-input">
{{ dateFormatByAppearance(getMillisecond(myEndTime)) }} {{ dateFormatByAppearance(getMillisecond(myEndTime)) }}
</div> </div>
<div class="date-range-title" style="padding-left: 0">Recently used absolute ranges</div> <div class="date-range-title" style="padding-left: 0">{{$t('dateTime.recentlyUsedRanges')}}</div>
<div class="date-range-history"> <div class="date-range-history">
<div v-for="(item, index) in rangeHistoryArr" :key="index" class="date-range-history-item" <div v-for="(item, index) in rangeHistoryArr" :key="index" class="date-range-history-item"
@click="historyChange(item)"> @click="historyChange(item)">
@@ -53,7 +55,7 @@
:span="8" :span="8"
class="date-range-panel-content date-range-panel-content-right" class="date-range-panel-content date-range-panel-content-right"
style="border-left: 1px solid rgba(0,0,0,0.09);"> style="border-left: 1px solid rgba(0,0,0,0.09);">
<div class="date-range-title">Relatime time ranges</div> <div class="date-range-title">{{$t('dateTime.relativeTimeRanges')}}</div>
<ul class="date-range-item"> <ul class="date-range-item">
<li v-for="item in dateRangeArr" <li v-for="item in dateRangeArr"
@click="quickChange(item.value)" @click="quickChange(item.value)"
@@ -79,9 +81,12 @@
<script> <script>
import { ref, computed, watch, reactive } from 'vue' import { ref, computed, watch, reactive } from 'vue'
import { storageKey } from '@/utils/constants' import { EN, storageKey, ZH } from '@/utils/constants'
import { getMillisecond, millTimestampDiffFromTz, timestampToList } from '@/utils/date-util' import { getMillisecond, millTimestampDiffFromTz, timestampToList } from '@/utils/date-util'
import { useStore } from 'vuex' import { useStore } from 'vuex'
import ElConfigProvider from 'element-plus'
import cn from 'element-plus/lib/locale/lang/zh-cn'
import en from 'element-plus/lib/locale/lang/en'
export default { export default {
name: 'DateTimeRange', name: 'DateTimeRange',
@@ -105,6 +110,31 @@ export default {
} }
}, },
emits: ['change'], emits: ['change'],
data () {
return {
dateRangeArr: [
{ value: 5, name: this.$t('dateTime.last5Mins') }, // 'last 5 mins'
{ value: 15, name: this.$t('dateTime.last15Mins') },
{ value: 30, name: this.$t('dateTime.last30Mins') },
{ value: 60, name: this.$t('dateTime.last1Hour') }, // dateTime.last1Hour
{ value: 180, name: this.$t('dateTime.last3Hours') },
{ value: 360, name: this.$t('dateTime.last6Hours') },
{ value: 720, name: this.$t('dateTime.last12Hours') },
{ value: 1440, name: this.$t('dateTime.last1Day') }, // dateTime.last2Days
{ value: 2880, name: this.$t('dateTime.last2Days') }
]
}
},
computed: {
showDetail () {
let str = ''
if (this.dateRangeValue !== -1) {
const rangeItem = this.dateRangeArr.find(item => item.value === this.dateRangeValue)
str = rangeItem ? rangeItem.name : this.dateRangeArr[0].name
}
return str
}
},
setup (props, ctx) { setup (props, ctx) {
// data // data
const store = useStore() const store = useStore()
@@ -124,24 +154,12 @@ export default {
const rangeHistory = ref(localStorage.getItem(storageKey.dataRangeHistory) ? JSON.parse(localStorage.getItem(storageKey.dataRangeHistory)) : []) const rangeHistory = ref(localStorage.getItem(storageKey.dataRangeHistory) ? JSON.parse(localStorage.getItem(storageKey.dataRangeHistory)) : [])
const dateRangeValue = props.dateRange ? ref(props.dateRange) : ref(60) const dateRangeValue = props.dateRange ? ref(props.dateRange) : ref(60)
const isCustom = ref(dateRangeValue.value === -1) const isCustom = ref(dateRangeValue.value === -1)
const dateRangeArr = [
{ value: 5, name: 'last 5 mins' },
{ value: 15, name: 'last 15 mins' },
{ value: 30, name: 'last 30 mins' },
{ value: 60, name: 'last 1 hour' },
{ value: 180, name: 'last 3 hours' },
{ value: 360, name: 'last 6 hours' },
{ value: 720, name: 'last 12 hours' },
{ value: 1440, name: 'last 1 day' },
{ value: 2880, name: 'last 2 days' }
]
const dropdownFlag = ref(false) const dropdownFlag = ref(false)
// 默认日历选择时间即开始时间YYYY-MM-DD 00:00:00,结束时间YYYY-MM-DD 59:59:59 // 默认日历选择时间即开始时间YYYY-MM-DD 00:00:00,结束时间YYYY-MM-DD 59:59:59
const defaultTime = ref([ const defaultTime = ref([
new Date(2023, 1, 1, 0, 0, 0), new Date(2023, 1, 1, 0, 0, 0),
new Date(2023, 1, 2, 23, 59, 59) new Date(2023, 1, 2, 23, 59, 59)
]) ])
// computed // computed
const utcStr = computed(() => { const utcStr = computed(() => {
let str = 'UTC ' let str = 'UTC '
@@ -159,13 +177,6 @@ export default {
str += ':00 ' str += ':00 '
return str return str
}) })
const showDetail = computed(() => {
let str = ''
if (dateRangeValue.value !== -1) {
str = dateRangeArr.find(item => item.value === dateRangeValue.value).name
}
return str
})
const rangeHistoryArr = rangeHistory const rangeHistoryArr = rangeHistory
// refs // refs
@@ -254,13 +265,15 @@ 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]
if (d.start !== myStartTime.value || d.end !== myEndTime.value) {
rangeHistory.value.unshift({ rangeHistory.value.unshift({
start: myStartTime.value, start: myStartTime.value,
end: myEndTime.value end: myEndTime.value
}) })
} }
}
if (!rangeHistory.value[0]) { if (!rangeHistory.value[0]) {
rangeHistory.value.unshift({ rangeHistory.value.unshift({
start: myStartTime.value, start: myStartTime.value,
@@ -287,6 +300,12 @@ export default {
}) })
} }
} }
const language = localStorage.getItem(storageKey.language) || EN // 初始未选择默认 en 英文
let locale = en
if (language === ZH) {
locale = cn
}
return { return {
myStartTime, myStartTime,
myEndTime, myEndTime,
@@ -295,13 +314,11 @@ export default {
utcStr, utcStr,
rangeEchartsData, rangeEchartsData,
address, address,
dateRangeArr,
defaultTime, defaultTime,
dateRangeValue, dateRangeValue,
isCustom, isCustom,
newDateValue, newDateValue,
newDatePicker, newDatePicker,
showDetail,
rangeHistory, rangeHistory,
rangeHistoryArr, rangeHistoryArr,
getMillisecond, getMillisecond,
@@ -311,7 +328,8 @@ export default {
timeArrChange, timeArrChange,
returnValue, returnValue,
quickChange, quickChange,
historyChange historyChange,
locale
} }
} }
} }

View File

@@ -58,7 +58,7 @@
<script> <script>
import axios from 'axios' import axios from 'axios'
import { storageKey } from '@/utils/constants' import { storageKey, EN } from '@/utils/constants'
export default { export default {
name: 'TopToolMoreOptions', name: 'TopToolMoreOptions',
@@ -108,7 +108,7 @@ export default {
if (this.paramsType) { if (this.paramsType) {
form.append('type', this.paramsType) form.append('type', this.paramsType)
} }
form.append('language', localStorage.getItem(storageKey.language) ? localStorage.getItem(storageKey.language) : 'en') form.append('language', localStorage.getItem(storageKey.language) ? localStorage.getItem(storageKey.language) : EN)
axios.post(this.importUrl, form, { 'Content-Type': 'multipart/form-data' }).then(response => { axios.post(this.importUrl, form, { 'Content-Type': 'multipart/form-data' }).then(response => {
if (response.status === 200 && response.data.msg === 'success') { if (response.status === 200 && response.data.msg === 'success') {
this.$message({ duration: 2000, type: 'success', message: this.$t('tip.importSuccess') }) this.$message({ duration: 2000, type: 'success', message: this.$t('tip.importSuccess') })
@@ -128,7 +128,7 @@ export default {
this.importFile = null this.importFile = null
}, },
downloadTemplate () { downloadTemplate () {
const language = localStorage.getItem(storageKey.language) || 'en' // 初始未选择默认 en 英文 const language = localStorage.getItem(storageKey.language) || EN // 初始未选择默认 en 英文
const fileName = this.exportFileName + '-' + this.$t('overall.template') + '-' + this.getTimeString() + '.json' const fileName = this.exportFileName + '-' + this.$t('overall.template') + '-' + this.getTimeString() + '.json'
let url = null let url = null
@@ -159,7 +159,7 @@ export default {
}) })
} }
params.pageSize = -1 params.pageSize = -1
params.language = localStorage.getItem(storageKey.language) || 'en' params.language = localStorage.getItem(storageKey.language) || EN
this.export(this.exportUrl, params, this.exportFileName + '-' + this.getTimeString() + '.json') this.export(this.exportUrl, params, this.exportFileName + '-' + this.getTimeString() + '.json')
this.closeDialog() this.closeDialog()

View File

@@ -13,12 +13,12 @@
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item> <el-dropdown-item>
<div id="header-to-english" :style="language === 'en'?'color:#0091ff':''" @click="changeLocal('en')"> <div id="header-to-english" :style="language === EN?'color:#0091ff':''" @click="changeLocal(EN)">
English English
</div> </div>
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item> <el-dropdown-item>
<div id="header-to-chinese" :style="language === 'cn'?'color:#0091ff':''" @click="changeLocal('cn')"> <div id="header-to-chinese" :style="language === ZH?'color:#0091ff':''" @click="changeLocal(ZH)">
中文 中文
</div> </div>
</el-dropdown-item> </el-dropdown-item>
@@ -42,8 +42,8 @@
</div> </div>
<div class="cn-header__nav"> <div class="cn-header__nav">
<i class="cn-icon cn-icon-a-NetworkAnalytics"></i> <i class="cn-icon cn-icon-a-NetworkAnalytics"></i>
<el-breadcrumb class="header__left-breadcrumb" separator=">"> <el-breadcrumb class="header__left-breadcrumb" separator=">" v-if="route.startsWith('/panel')">
<el-breadcrumb-item class="header__left-breadcrumb-item" :id="`breadcrumb${item.value}`" :title="item.value" <el-breadcrumb-item class="header__left-breadcrumb-item" :id="`breadcrumb${item.value}`" :title="index===3?item.value:''"
v-for="(item,index) in breadcrumb" :key="item.value"> v-for="(item,index) in breadcrumb" :key="item.value">
<template v-if="index===3" > <template v-if="index===3" >
<div class="header__left-breadcrumb-item-select" > <div class="header__left-breadcrumb-item-select" >
@@ -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里的数据控制 -->
@@ -113,6 +113,13 @@
</template> </template>
</el-breadcrumb-item> </el-breadcrumb-item>
</el-breadcrumb> </el-breadcrumb>
<el-breadcrumb class="header__left-breadcrumb" separator=">" v-else>
<el-breadcrumb-item class="header__left-breadcrumb-item" :id="`breadcrumb${item.value}`"
v-for="(item,index) in breadcrumb" :key="item.value">
<span v-if="item.clickable" class="route-menu" @click="jumpOther(item.route,index)">{{ item.value }}</span>
<span v-else>{{ item.value }}</span>
</el-breadcrumb-item>
</el-breadcrumb>
</div> </div>
<!-- 菜单 --> <!-- 菜单 -->
@@ -196,7 +203,9 @@ import {
networkTable, networkTable,
operationType, operationType,
storageKey, storageKey,
wholeScreenRouterMapping wholeScreenRouterMapping,
ZH,
EN
} from '@/utils/constants' } from '@/utils/constants'
import { api } from '@/utils/api' import { api } from '@/utils/api'
import { ref } from 'vue' import { ref } from 'vue'
@@ -234,7 +243,7 @@ export default {
return { return {
username: localStorage.getItem(storageKey.username), username: localStorage.getItem(storageKey.username),
nickName: localStorage.getItem(storageKey.nickName), nickName: localStorage.getItem(storageKey.nickName),
language: localStorage.getItem(storageKey.language) ? localStorage.getItem(storageKey.language) : 'en', language: localStorage.getItem(storageKey.language) ? localStorage.getItem(storageKey.language) : EN,
showChangePin: false, showChangePin: false,
from: '', // entity类型 from: '', // entity类型
changePassForm: { changePassForm: {
@@ -296,7 +305,9 @@ export default {
curTabState: curTabState, curTabState: curTabState,
urlChangeParams: {}, urlChangeParams: {},
wholeScreenRouterMapping, wholeScreenRouterMapping,
logo: 'images/logo-header.svg' logo: 'images/logo-header.svg',
ZH,
EN
} }
}, },
computed: { computed: {
@@ -304,7 +315,7 @@ export default {
return this.$store.getters.menuList.find(menu => menu.code === 'networkAnalytics') return this.$store.getters.menuList.find(menu => menu.code === 'networkAnalytics')
}, },
otherMenu () { otherMenu () {
return this.$store.getters.menuList.filter(menu => ['networkAnalytics', 'chart', 'I18N', 'entityDetail', 'temp', 'entityGraph', 'detectionPolicy'].indexOf(menu.code) === -1) return this.$store.getters.menuList.filter(menu => ['networkAnalytics', 'I18N', 'entityDetail', 'entityGraph', 'detectionPolicy'].indexOf(menu.code) === -1)
/* function excludeButton (menu) { /* function excludeButton (menu) {
for (let i = 0; i < menu.length; i++) { for (let i = 0; i < menu.length; i++) {
@@ -322,15 +333,17 @@ export default {
breadcrumb () { breadcrumb () {
const breadcrumb = [] const breadcrumb = []
this.generateBreadcrumb(breadcrumb, this.$store.getters.menuList) this.generateBreadcrumb(breadcrumb, this.$store.getters.menuList)
// 写死一级和二级菜单是否可以点击跳转 if (breadcrumb) {
if (breadcrumb[0]) { // panel菜单是否可以点击跳转一级菜单不可点击二级菜单可以点击
if (['knowledgeBase'].indexOf(breadcrumb[0].code) > -1) { if (breadcrumb[0] && breadcrumb[1] && breadcrumb[1].route &&
breadcrumb[0].clickable = true breadcrumb[1].route.indexOf('/panel/') === 0) {
}
if (breadcrumb[1]) {
if (breadcrumb[1].route && breadcrumb[1].route.indexOf('/panel/') === 0) {
breadcrumb[1].clickable = true breadcrumb[1].clickable = true
} else { // 除panel外的菜单是否可以点击跳转:除了新增、编辑,其它均可点击
breadcrumb.forEach(item => {
if (item.value !== 'Create' && item.value !== 'Edit') {
item.clickable = true
} }
})
} }
} }
@@ -366,7 +379,7 @@ export default {
}, },
async breadcrumb (n) { async breadcrumb (n) {
this.curTabProp = this.$route.query.dimensionType ? this.$route.query.dimensionType : null this.curTabProp = this.$route.query.dimensionType ? this.$route.query.dimensionType : null
if (this.$route.params.typeName === fromRoute.dnsServiceInsights) { if (this.$route.path.replace('/panel/', '') === fromRoute.dnsServiceInsights) {
if (this.dnsQtypeMapData.size === 0) { if (this.dnsQtypeMapData.size === 0) {
this.dnsQtypeMapData = await getDnsMapData('dnsQtype') this.dnsQtypeMapData = await getDnsMapData('dnsQtype')
} }
@@ -384,7 +397,7 @@ export default {
async mounted () { async mounted () {
this.from = Object.keys(this.entityType)[0] this.from = Object.keys(this.entityType)[0]
// 是否需要dns的qtype和rcode的数据字典 // 是否需要dns的qtype和rcode的数据字典
if (this.$route.params.typeName === fromRoute.dnsServiceInsights) { if (this.$route.path.replace('/panel/', '') === fromRoute.dnsServiceInsights) {
if (this.dnsQtypeMapData.size === 0) { if (this.dnsQtypeMapData.size === 0) {
this.dnsQtypeMapData = await getDnsMapData('dnsQtype') this.dnsQtypeMapData = await getDnsMapData('dnsQtype')
} }
@@ -392,7 +405,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()
} }
@@ -405,10 +418,10 @@ export default {
const endTimeParam = query.endTime const endTimeParam = query.endTime
// 若url携带了使用携带的值否则使用默认值。 // 若url携带了使用携带的值否则使用默认值。
const dateRangeValue = rangeParam ? parseInt(query.range) : 60 const dateRangeValue = rangeParam ? parseInt(rangeParam) : 60
const chartTimeFilter = ref({ dateRangeValue }) const chartTimeFilter = ref({ dateRangeValue })
if (!startTimeParam || !endTimeParam) { if (!startTimeParam || !endTimeParam) {
const { startTime, endTime } = getNowTime(60) const { startTime, endTime } = getNowTime(dateRangeValue)
chartTimeFilter.value.startTime = startTime chartTimeFilter.value.startTime = startTime
chartTimeFilter.value.endTime = endTime chartTimeFilter.value.endTime = endTime
} else { } else {
@@ -423,55 +436,6 @@ export default {
}, },
methods: { methods: {
generateBreadcrumb (breadcrumb, menus) { generateBreadcrumb (breadcrumb, menus) {
if (this.route === '/entityDetail') {
const entityMenu = menus.find(m => m.route === '/entityExplorer')
const entityDetailMenu = menus.find(m => m.route === '/entityDetail')
breadcrumb.push({
code: entityMenu.code,
value: entityMenu.i18n ? this.$t(entityMenu.i18n) : entityMenu.name,
route: entityMenu.route,
type: entityMenu.type
})
breadcrumb.push({
code: entityDetailMenu.code,
value: entityDetailMenu.i18n ? this.$t(entityDetailMenu.i18n) : entityDetailMenu.name,
route: entityDetailMenu.route,
type: entityDetailMenu.type
})
return true
} else if (this.route === '/entityGraph') {
const entityMenu = menus.find(m => m.route === '/entityExplorer')
const entityGraphMenu = menus.find(m => m.route === '/entityGraph')
breadcrumb.push({
code: entityMenu.code,
value: entityMenu.i18n ? this.$t(entityMenu.i18n) : entityMenu.name,
route: entityMenu.route,
type: entityMenu.type
})
breadcrumb.push({
code: entityGraphMenu.code,
value: entityGraphMenu.i18n ? this.$t(entityGraphMenu.i18n) : entityGraphMenu.name,
route: entityGraphMenu.route,
type: entityGraphMenu.type
})
return true
} else if (this.route === '/detectionsNew') {
const detectionMenu = menus.find(m => m.route === '/detection')
const policyMenu = menus.find(m => m.route === '/detectionsNew')
breadcrumb.push({
code: detectionMenu.code,
value: detectionMenu.i18n ? this.$t(detectionMenu.i18n) : detectionMenu.name,
route: detectionMenu.route,
type: detectionMenu.type
})
breadcrumb.push({
code: policyMenu.code,
value: policyMenu.i18n ? this.$t(policyMenu.i18n) : policyMenu.name,
route: policyMenu.route,
type: policyMenu.type
})
return true
}
const menu = menus.find(m => m.route === this.route) const menu = menus.find(m => m.route === this.route)
if (menu) { if (menu) {
breadcrumb.unshift({ breadcrumb.unshift({
@@ -523,7 +487,7 @@ export default {
}, },
getCurTabByLabel (label) { getCurTabByLabel (label) {
let curTab = null let curTab = null
const tableType = this.$route.params ? this.$route.params.typeName : 'networkOverview' const tableType = this.$route.path.replace('/panel/', '') || 'networkOverview'
const curTableInCode = networkTable[tableType] ? networkTable[tableType] : networkTable.networkOverview const curTableInCode = networkTable[tableType] ? networkTable[tableType] : networkTable.networkOverview
if (curTableInCode && curTableInCode.tabList) { if (curTableInCode && curTableInCode.tabList) {
curTab = curTableInCode.tabList.find(item => item.label === label) curTab = curTableInCode.tabList.find(item => item.label === label)
@@ -536,7 +500,7 @@ export default {
const currentValue = document.getElementById('breadcrumbValue') ? document.getElementById('breadcrumbValue').innerText : '' const currentValue = document.getElementById('breadcrumbValue') ? document.getElementById('breadcrumbValue').innerText : ''
const columnName = this.getUrlParam(this.curTabState.thirdMenu, '') const columnName = this.getUrlParam(this.curTabState.thirdMenu, '')
let type = 'ip' let type = 'ip'
const tableType = this.$route.params ? this.$route.params.typeName : 'networkOverview' const tableType = this.$route.path.replace('/panel/', '') || 'networkOverview'
const curTableInCode = networkTable[tableType] ? networkTable[tableType] : networkTable.networkOverview const curTableInCode = networkTable[tableType] ? networkTable[tableType] : networkTable.networkOverview
if (curTableInCode && curTableInCode.tabList) { if (curTableInCode && curTableInCode.tabList) {
const curTab = curTableInCode.tabList.find(item => item.label === columnName) const curTab = curTableInCode.tabList.find(item => item.label === columnName)
@@ -560,7 +524,7 @@ export default {
axios.get(curTableInCode.url.drilldownList, { params }).then(async response => { axios.get(curTableInCode.url.drilldownList, { params }).then(async response => {
if (response.status === 200) { if (response.status === 200) {
this.breadcrumbColumnValueListShow = response.data.data.result this.breadcrumbColumnValueListShow = response.data.data.result
if (this.$route.params.typeName === fromRoute.dnsServiceInsights) { if (this.$route.path.replace('/panel/', '') === fromRoute.dnsServiceInsights) {
if (this.dnsQtypeMapData.size === 0) { if (this.dnsQtypeMapData.size === 0) {
this.dnsQtypeMapData = await getDnsMapData('dnsQtype') this.dnsQtypeMapData = await getDnsMapData('dnsQtype')
} }
@@ -691,7 +655,7 @@ export default {
}, },
async handleCurDrilldownTableConfig (thirdMenu) { async handleCurDrilldownTableConfig (thirdMenu) {
// const userId = localStorage.getItem(storageKey.userId) // const userId = localStorage.getItem(storageKey.userId)
const tableType = this.$route.params ? this.$route.params.typeName : 'networkOverview' const tableType = this.$route.path.replace('/panel/', '') || 'networkOverview'
const metric = this.getUrlParam(this.curTabState.tableMetric, 'Bits/s') const metric = this.getUrlParam(this.curTabState.tableMetric, 'Bits/s')
const drillDownTableConfigs = await combineDrilldownTableWithUserConfig() const drillDownTableConfigs = await combineDrilldownTableWithUserConfig()
const currentTableConfig = drillDownTableConfigs.find(config => config.route === tableType) const currentTableConfig = drillDownTableConfigs.find(config => config.route === tableType)
@@ -712,7 +676,26 @@ export default {
} }
} }
}, },
// 仅处理除panel外的相关路径的导航
async jumpOther (route, index) {
route = route.replace('redirect:', '')
this.showMenu = false
if (route === this.route && index > 0) { // 当前只有一级菜单时,点击不进行刷新,重新跳转
this.refresh()
return
}
if (route) {
this.$router.push({
path: route,
query: {
t: +new Date()
}
})
}
},
// 仅处理panel相关路径的导航
async jump (route, columnName, columnValue, opeType) { async jump (route, columnName, columnValue, opeType) {
route = route.replace('redirect:', '')
if (route === '/panel/linkMonitor' && opeType === 3) { if (route === '/panel/linkMonitor' && opeType === 3) {
return true return true
} }
@@ -732,7 +715,7 @@ export default {
this.$store.commit('setNetworkOverviewTabList', []) this.$store.commit('setNetworkOverviewTabList', [])
} }
// 清空网络概况的特殊面包屑 // 清空网络概况的特殊面包屑
const tableType = this.$route.params ? this.$route.params.typeName : 'networkOverview' const tableType = this.$route.path.replace('/panel/', '') || 'networkOverview'
const metric = this.getUrlParam(this.curTabState.tableMetric, 'Bits/s') const metric = this.getUrlParam(this.curTabState.tableMetric, 'Bits/s')
const curTab = await getDefaultCurTab(tableType, metric, columnName) const curTab = await getDefaultCurTab(tableType, metric, columnName)
this.$store.getters.menuList.forEach(menu => { this.$store.getters.menuList.forEach(menu => {
@@ -744,11 +727,6 @@ export default {
child.columnName = columnName child.columnName = columnName
this.urlChangeParams[this.curTabState.thirdMenu] = columnName this.urlChangeParams[this.curTabState.thirdMenu] = columnName
this.urlChangeParams[this.curTabState.fourthMenu] = columnValue this.urlChangeParams[this.curTabState.fourthMenu] = columnValue
// const tabObjGroup = networkOverviewTabList.filter(item => item.label == columnName)
// let curTab = this.getCurTabByLabel()
// const type = curTab ? curTab.prop : ''
// this.curTabProp = this.$route.query.dimensionType ? this.$route.query.dimensionType : null
// this.urlChangeParams[this.curTabState.dimensionType] = type
this.urlChangeParams[this.curTabState.panelName] = columnValue this.urlChangeParams[this.curTabState.panelName] = columnValue
} else if (columnName) { // 点击的为列名 } else if (columnName) { // 点击的为列名
child.columnValue = '' child.columnValue = ''
@@ -786,6 +764,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 +773,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 +783,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

@@ -23,7 +23,8 @@
<button type="button" class="cn-btn cn-btn-size-small-new cn-btn-style-light-new option-btn" style="margin-left: 0px;" @click="expandAllOrNone" :class="{'btn-active':expandAllFlag}">展开/收缩</button> <button type="button" class="cn-btn cn-btn-size-small-new cn-btn-style-light-new option-btn" style="margin-left: 0px;" @click="expandAllOrNone" :class="{'btn-active':expandAllFlag}">展开/收缩</button>
<button type="button" class="cn-btn cn-btn-size-small-new cn-btn-style-light-new option-btn" @click="selectAllOrNone" :class="{'btn-active':selectAllFlag}"><span ><i class="cn-icon cn-icon-delete"></i></span></button> <button type="button" class="cn-btn cn-btn-size-small-new cn-btn-style-light-new option-btn" @click="selectAllOrNone" :class="{'btn-active':selectAllFlag}"><span ><i class="cn-icon cn-icon-delete"></i></span></button>
</div>--> </div>-->
<el-tree :data="menus" :default-expand-all="expandAllFlag" :props="{label:labelFormatter}" @check-change="selectChange" class="tree-border" node-key="id" ref="menuTree" show-checkbox id="role-box-input-menus"> <el-checkbox v-model="isCheckAll" :label="$t('overall.all')" @change="checkAll"></el-checkbox>
<el-tree :data="menus" :default-expand-all="expandAllFlag" check-strictly="true" :props="{label:labelFormatter}" @check-change="selectChange" class="tree-border" node-key="id" ref="menuTree" show-checkbox id="role-box-input-menus">
<template #default="{ data }"> <template #default="{ data }">
<span> <span>
<i v-if="data.type === '1'" class="el-icon-menu"></i> <i v-if="data.type === '1'" class="el-icon-menu"></i>
@@ -90,7 +91,8 @@ export default {
menus: [], menus: [],
selectedIds: [], selectedIds: [],
selectAllFlag: false, selectAllFlag: false,
expandAllFlag: true expandAllFlag: true,
isCheckAll: false
} }
}, },
watch: { watch: {
@@ -150,9 +152,59 @@ export default {
labelFormatter: function (data, node) { labelFormatter: function (data, node) {
return data && data.i18n ? this.$t(data.i18n) : data.name return data && data.i18n ? this.$t(data.i18n) : data.name
}, },
selectChange: function (data, isCheck, childIsCheck) { getChildNodes (menu) {
let nodeGroup = []
if (menu.children && menu.children.length > 0) {
nodeGroup = menu.children
const _this = this
menu.children.forEach(node => {
const childNodes = _this.getChildNodes(node)
if (childNodes && childNodes.length > 0) {
nodeGroup = nodeGroup.concat(childNodes)
}
})
}
return nodeGroup
},
checkAll () {
if (this.$refs.menuTree) { if (this.$refs.menuTree) {
this.editRole.menuIds = this.$refs.menuTree.getCheckedKeys(true) if (this.isCheckAll) {
let nodeGroup = this.menus
const _this = this
this.menus.forEach(menu => {
const childNodes = _this.getChildNodes(menu)
if (childNodes && childNodes.length > 0) {
nodeGroup = nodeGroup.concat(childNodes)
}
})
this.$refs.menuTree.setCheckedNodes(nodeGroup)
} else {
this.$refs.menuTree.setCheckedNodes([])
}
}
},
checkParentNode(node) {
if(node && this.$refs.menuTree.getNode(node)){
let parent = this.$refs.menuTree.getNode(node).parent
let parentNode = parent.data
if(parentNode && parentNode.id && parentNode.id !== 0 ){
this.$refs.menuTree.setChecked(parentNode,true,false)
this.checkParentNode(parentNode)
}
}
},
selectChange: function (data, isCheck, childIsCheck) {
if(isCheck) {//如果是选中节点,则同步选中所有的父辈节点(有全选和半选两种状态)
this.checkParentNode(data)
} else {//如果是取消节点,则同步取消选中所有子节点
if(data.children && data.children.length > 0) {
data.children.forEach(node => {
this.$refs.menuTree.setChecked(node, false, true)
})
}
}
if (this.$refs.menuTree) {
this.editRole.menuIds = this.$refs.menuTree.getCheckedKeys(false)
} }
}, },
selectAllOrNone: function () { selectAllOrNone: function () {

View File

@@ -70,7 +70,7 @@
</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"
@@ -83,7 +83,7 @@
<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
@@ -125,16 +125,16 @@ export default {
mixins: [rightBoxMixin], mixins: [rightBoxMixin],
data () { data () {
const validatePin = (rule, value, callback) => { // 确认密码 const validatePin = (rule, value, callback) => { // 确认密码
if (value.length < 5) { if (value && value.length < 5) {
callback(new Error(this.$t('validate.atLeastFive'))) callback(new Error(this.$t('validate.atLeastFive')))
} else { } else {
callback() callback()
} }
} }
const validateConfirmPin = (rule, value, callback) => { // 确认密码的二次校验 const validateConfirmPin = (rule, value, callback) => { // 确认密码的二次校验
if (value === '' && this.editObject.pin) { if (_.isEmpty(value) && !_.isEmpty(this.editObject.pin)) {//密码有内容,确认密码没内容
callback(new Error(this.$t('config.user.confirmPin'))) callback(new Error(this.$t('config.user.confirmPin')))
} else if (value !== this.editObject.pin) { } else if (!_.isEmpty(value) && value !== this.editObject.pin) {//密码有内容,确认密码也有内容,内容不一致
callback(new Error(this.$t('config.user.confirmPinErr'))) callback(new Error(this.$t('config.user.confirmPinErr')))
} else { } else {
callback() callback()
@@ -207,7 +207,7 @@ export default {
], ],
pinChange: [ pinChange: [
{ validator: validateConfirmPin, trigger: 'blur' }, { validator: validateConfirmPin, trigger: 'blur' },
{ pattern: /^[a-zA-Z0-9]{5,64}$/, message: this.$t('validate.atLeastFive') } { validator: validatePin, trigger: 'blur' }
], ],
roleIds: [ roleIds: [
{ required: true, message: this.$t('validate.required'), trigger: 'blur' } { required: true, message: this.$t('validate.required'), trigger: 'blur' }

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

@@ -52,13 +52,17 @@
</template> </template>
<template v-else-if="item.prop === 'status'"> <template v-else-if="item.prop === 'status'">
<el-switch <el-switch
v-if="scope.row.id" v-if="scope.row.id && hasPermission('editUser')"
v-model="scope.row.status" v-model="scope.row.status"
active-value="1" active-value="1"
:disabled="(scope.row.username === loginName) || (scope.row.username==='admin' && scope.row.id===1) || scope.row.buildIn === 1" :disabled="(scope.row.username === loginName) || (scope.row.username==='admin' && scope.row.id===1) || scope.row.buildIn === 1"
inactive-value="0" inactive-value="0"
@change="()=>{statusChange(scope.row)}"> @change="()=>{statusChange(scope.row)}">
</el-switch> </el-switch>
<template v-else>
<span v-if="scope.row.status === '1'">{{$t('detection.create.enabled')}}</span>
<span v-else>{{$t('detection.create.disabled')}}</span>
</template>
</template> </template>
<span v-else>{{scope.row[item.prop] || '-'}}</span> <span v-else>{{scope.row[item.prop] || '-'}}</span>
</template> </template>

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===ZH" 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, ZH, EN } 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,22 @@ export default {
trigger: 'blur' trigger: 'blur'
} }
] ]
},
language: EN,
ZH
}
},
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 +184,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 +194,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 +215,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 || []
this.levelList = securityLevel || []
// threshold模式还没确定所以数据暂时静态数据后续根据需要修改
this.conditionList = detectionUnitList.conditionList || []
this.metricList = detectionUnitList.metricList || []
if (this.mySettingObj.ruleType === this.detectionRuleType.indicator) {
axios.get(api.knowledgeBaseList, { params: { pageSize: -1 } }).then(response => {
if (response.status === 200) { if (response.status === 200) {
this.sourceList = response.data.data.sourceList || [] this.libraryList = _.get(response, 'data.data.list', []).filter(l => l.isBuiltIn === 0)
this.levelList = response.data.data.levelList || []
this.conditionList = response.data.data.conditionList || []
this.metricList = response.data.data.metricList || []
this.libraryList = response.data.data.libraryList || []
} else { } else {
console.error(response.data) 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'))
} }
}).finally(() => { }
}).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

@@ -61,7 +61,7 @@
<div class="reference-tag__group"> <div class="reference-tag__group">
<span class="reference-tag" v-for="(refer, index) in scope.row[item.prop].slice(0,2)" >{{refer}}</span> <span class="reference-tag" v-for="(refer, index) in scope.row[item.prop].slice(0,2)" >{{refer}}</span>
</div> </div>
<div class="reference-more">+{{scope.row[item.prop].length - 2}} more</div> <div class="reference-more">+{{scope.row[item.prop].length - 2}} {{$t('overall.more')}}</div>
</div> </div>
</template> </template>
<div class="reference-tag__tip"> <div class="reference-tag__tip">
@@ -70,9 +70,11 @@
</el-popover> </el-popover>
</templage> </templage>
<template v-else> <template v-else>
<template v-for="(refer, index) in scope.row[item.prop]"> <div class="reference-tag__show">
<div class="type-tag">{{refer}}</div> <div class="reference-tag__group">
</template> <span class="reference-tag" v-for="(refer, index) in scope.row[item.prop]" >{{refer}}</span>
</div>
</div>
</template> </template>
</template> </template>
<template v-else-if="item.prop === 'opTime' || item.prop === 'ctime'"> <template v-else-if="item.prop === 'opTime' || item.prop === 'ctime'">
@@ -96,6 +98,7 @@
</template> </template>
<template v-else-if="item.prop === 'status'"> <template v-else-if="item.prop === 'status'">
<el-switch v-model="scope.row.status" <el-switch v-model="scope.row.status"
v-if="hasPermission('editUserDefinedLibrary')"
active-color="#38ACD2" active-color="#38ACD2"
inactive-color="#C0CEDB" inactive-color="#C0CEDB"
:active-value="1" :active-value="1"
@@ -103,6 +106,15 @@
@change="changeStatus($event,scope.row.knowledgeId)" @change="changeStatus($event,scope.row.knowledgeId)"
> >
</el-switch> </el-switch>
<template v-else>
<span v-if="scope.row.status === 1">{{$t('detection.create.enabled')}}</span>
<span v-else>{{$t('detection.create.disabled')}}</span>
</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> </template>
<span v-else>{{scope.row[item.prop] || '-'}}</span> <span v-else>{{scope.row[item.prop] || '-'}}</span>
</template> </template>
@@ -151,6 +163,11 @@ export default {
}, { }, {
label: this.$t('knowledge.reference'), label: this.$t('knowledge.reference'),
prop: 'reference', prop: 'reference',
width: 190,
show: true
}, {
label: this.$t('overall.color'),
prop: 'color',
width: 180, width: 180,
show: true show: true
}, { }, {
@@ -189,6 +206,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 +255,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

@@ -6,35 +6,27 @@
<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
effect="light"
trigger="hover"
:content="$t('tip.notAvailable')"
placement="right"
popper-class="panel-tooltip"
>
<el-switch v-model="data.status" <el-switch v-model="data.status"
v-if="hasPermission('editBuiltInKnowledgeBase')"
class="card-enable" class="card-enable"
active-color="#38ACD2" active-color="#38ACD2"
inactive-color="#C0CEDB" inactive-color="#C0CEDB"
:disabled="true"
:active-value="1" :active-value="1"
:inactive-value="0" :inactive-value="0"
@change="changeStatus($event,data.knowledgeId)" :before-change="(knowledgeId) => confirmSwitchLearning(data.knowledgeId)"
> >
</el-switch> </el-switch>
</el-tooltip>
</div> </div>
<div class="card-icon"> <div class="card-icon">
<img :src="data.iconUrl"/> <img :src="data.iconUrl"/>
</div> </div>
<div class="card-title"> <div class="card-title">
<div class="card-title-name" :title="data.label">{{data.label}}</div> <div class="card-title-name" :title="$t(data.label)">{{$t(data.label)}}</div>
</div> </div>
<div class="card-desc" :title="data.desc">{{data.desc ? data.desc : '—'}}</div> <div class="card-desc" :title="$t(data.desc)">{{$t(data.desc) || '—'}}</div>
</div> </div>
<div class="card-operate__footer"> <div class="card-operate__footer">
<button v-if="data.showUpdate" <button v-if="data.showUpdate && hasPermission('editBuiltInKnowledgeBase')"
class="top-tool-btn--update" class="top-tool-btn--update"
@click="jumpToUpdatePage(data,true)"> @click="jumpToUpdatePage(data,true)">
<i class="cn-icon-update-knowledge-base cn-icon"></i> <i class="cn-icon-update-knowledge-base cn-icon"></i>
@@ -53,12 +45,12 @@
<img :src="data.iconUrl"/> <img :src="data.iconUrl"/>
</div> </div>
<div class="card-title"> <div class="card-title">
<div class="card-title-name" :title="data.label">{{data.label}}</div> <div class="card-title-name" :title="$t(data.label)">{{$t(data.label)}}</div>
</div> </div>
<div class="card-desc" :title="data.desc ? data.desc:'—'">{{data.desc ? data.desc : '—'}}</div> <div class="card-desc" :title="data.desc ? $t(data.desc) : '—'">{{data.desc ? $t(data.desc) : '—'}}</div>
</div> </div>
<div class="card-operate__footer"> <div class="card-operate__footer">
<button v-if="data.showUpdate" :title="$t('overall.update')" class="top-tool-btn--update" <button v-if="data.showUpdate && hasPermission('editBuiltInKnowledgeBase')" :title="$t('overall.update')" class="top-tool-btn--update"
@click="jumpToUpdatePage(data,false)"> @click="jumpToUpdatePage(data,false)">
<i class="cn-icon-update-knowledge-base cn-icon"></i> <i class="cn-icon-update-knowledge-base cn-icon"></i>
<span>{{$t('overall.update')}}</span> <span>{{$t('overall.update')}}</span>
@@ -75,7 +67,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">
@@ -84,60 +78,144 @@
<div class="update-right"> <div class="update-right">
<div class="knowledge-enable"> <div class="knowledge-enable">
<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="$t(updateKnowledge.label)">{{$t(updateKnowledge.label)}}</div>
</div> </div>
<el-tooltip
effect="light"
trigger="hover"
v-if="showEnable"
:content="$t('tip.notAvailable')"
placement="right"
popper-class="panel-tooltip"
>
<el-switch v-model="updateKnowledge.status" <el-switch v-model="updateKnowledge.status"
active-color="#38ACD2" active-color="#38ACD2"
inactive-color="#C0CEDB" inactive-color="#C0CEDB"
:disabled="true"
:active-value="1" :active-value="1"
:inactive-value="0" :inactive-value="0"
@change="changeStatus($event,updateKnowledge.knowledgeId)" :before-change="(knowledgeId) => confirmSwitchLearning(updateKnowledge.knowledgeId)"
v-if="updateKnowledge.source === 'cn_psiphon3_ip' && hasPermission('editBuiltInKnowledgeBase')"
> >
</el-switch> </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 ? $t(updateKnowledge.desc) : '-'">{{updateKnowledge.desc ? $t(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"
</div> @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"> <div class="update-operate">
<button :title="$t('overall.update')" class="top-tool-btn--update" <button :title="$t('overall.update')" class="top-tool-btn--update"
@click="uploadRecord"> @click="uploadRecord"><!--:disabled="hasUpdatingRecord"-->
<i class="cn-icon-update-knowledge-base cn-icon"></i> <i class="cn-icon-update-knowledge-base cn-icon"></i>
<span>{{$t('overall.update')}}</span> <span>{{$t('overall.update')}}</span>
</button> </button>
</div> </div>
</div> </div>
<div class="knowledge-update" v-else>
<div class="update-title" >
<div class="card-title-name">{{$t('knowledgeBase.updateRecord')}}</div>
</div>
<div class="update-operate">
<button :title="$t('overall.update')" class="top-tool-btn--update"
@click="uploadRecord"><!-- :disabled="hasUpdatingRecord" -->
<i class="cn-icon-update-knowledge-base cn-icon"></i>
<span>{{$t('overall.update')}}</span>
</button>
</div>
</div>
<div :style="{height: updateKnowledge.source === 'cn_psiphon3_ip' && activeTab === 'intelligenceLearning' ? 'calc(90vh - 190px - 200px - 50px - 42px)' : 'calc(100% - 242px)', marginTop: '42px', position: 'absolute', width: 'calc(100% - 60px)'}">
<loading :loading="updateLogLoading"></loading>
</div>
<el-table ref="updateDataTable" <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="updateKnowledge.source !== 'cn_psiphon3_ip' || 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">
@@ -179,7 +257,7 @@
</el-form> </el-form>
</div> </div>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button @click="showAddUpdateDialog = false">{{ $t('overall.cancel') }}</el-button> <el-button @click="cancle">{{ $t('overall.cancel') }}</el-button>
<el-button type="primary" @click="submitConfirm">{{ $t('tip.confirm') }}</el-button> <el-button type="primary" @click="submitConfirm">{{ $t('tip.confirm') }}</el-button>
</div> </div>
</template> </template>
@@ -187,6 +265,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 +275,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="cancleSwitch">{{ $t('overall.cancel') }}</el-button>
<el-button type="primary" @click="switchLearning">{{$t('tip.confirm')}}</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 { knowledgeCategoryValue, unitTypes, storageKey, builtInKnowledgeBaseBasicInfo } from '@/utils/constants' import { getSecond, getMillisecond, xAxisTimeFormatter, xAxisTimeRich } from '@/utils/date-util'
import { ref } from 'vue' import { knowledgeCategoryValue, unitTypes, storageKey, builtInKnowledgeBaseBasicInfo, knowledgeCardUpdateRecordType } from '@/utils/constants'
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 +319,8 @@ export default {
} }
}, },
components: { components: {
Loading Loading,
ChartNoData
}, },
data () { data () {
return { return {
@@ -237,7 +336,48 @@ export default {
updateHistoryList: [], updateHistoryList: [],
updateObject: {}, updateObject: {},
currentVersion: 0, currentVersion: 0,
uploadLoading: false uploadLoading: false,
psiphon3Loading: false,
updateLogLoading: false,
showConfirmSwitch: false,
// timer: null,
switchKnowledgeId: '',
activeTab: 'updateRecord',
isNoDataForPsiphon3: false,
showErrorForPsiphon3: false,
errorMsgForPsiphon3: '',
leftOffset: 0,
tabType: 'total',
mousemoveCursor: '',
selectTime: 1440,
// hasUpdatingRecord: false,
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 () {
@@ -252,13 +392,177 @@ 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)
} }
}, },
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',
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 endTime = window.$dayJs.tz().valueOf()
const params = {
startTime: getSecond(endTime - this.selectTime * 60 * 1000),
endTime: getSecond(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]
})
if (this.activeTab === knowledgeCardUpdateRecordType.intelligenceLearning) {
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 () {
if (this.updateKnowledge.source === 'cn_psiphon3_ip') {
this.init()
}
if (this.activeTab === knowledgeCardUpdateRecordType.intelligenceLearning) {
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
if (this.activeTab === knowledgeCardUpdateRecordType.intelligenceLearning) {
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 = []
@@ -283,17 +587,9 @@ export default {
uploadSuccess (response) { uploadSuccess (response) {
this.uploadLoading = false this.uploadLoading = false
this.uploaded = true this.uploaded = true
/* this.uploaded = 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
this.currentVersion = this.updateHistoryList[0].commitVersion + 1
})
/* } else {
this.$message.error(this.$t('tip.uploadFailed', { msg: response.message }))
} */
}, },
beforeUpload (file) { beforeUpload (file) {
this.uploadLoading = true this.uploadLoading = true
@@ -305,6 +601,9 @@ export default {
submit () { submit () {
this.$refs.knowledgeUpload.submit() this.$refs.knowledgeUpload.submit()
}, },
cancle () {
this.showAddUpdateDialog = false
},
clickCard (data, event) { clickCard (data, event) {
if (data.isSelected) { // 原来为选中,当前点击后未选中 if (data.isSelected) { // 原来为选中,当前点击后未选中
const index = this.checkList.indexOf(data) const index = this.checkList.indexOf(data)
@@ -325,6 +624,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,14 +646,19 @@ 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.updateHistoryList = res.data.data.list
this.currentVersion = this.updateHistoryList[0].commitVersion + 1
this.showEnable = showEnable this.showEnable = showEnable
await this.getCurTabData()
if (data.source === 'cn_psiphon3_ip') {
await this.init()
}
this.showUpdate() this.showUpdate()
if (this.activeTab === knowledgeCardUpdateRecordType.intelligenceLearning) {
this.$nextTick(() => {
this.handleActiveBar()
}) })
}
}, },
uploadRecord () { uploadRecord () {
this.showAddUpdateDialog = true this.showAddUpdateDialog = true
@@ -355,6 +666,52 @@ 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 === knowledgeCardUpdateRecordType.updateRecord) {
params = {
...params,
opUser: -1
}
} else if (this.activeTab === knowledgeCardUpdateRecordType.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
}
/*
this.hasUpdatingRecord = false
this.updateHistoryList.forEach(item => {
if (item.isUpdating) { // if(item.isUpdating){//????????
this.hasUpdatingRecord = true
}
})
*/
}).catch(e => {
console.error(e)
}).finally(() => {
this.updateLogLoading = false
})
},
// 切换tab
handleClick (tab) {
this.getCurTabData()
if (tab.index === '1') {
this.timeChange()
}
},
clearSelect () { clearSelect () {
this.$nextTick(() => { this.$nextTick(() => {
this.checkList = [] this.checkList = []
@@ -366,18 +723,10 @@ export default {
}) })
}, },
mouseenter (card) { mouseenter (card) {
this.tableData.forEach(t => {
if (t.knowledgeId === card.knowledgeId) {
card.showUpdate = true card.showUpdate = true
}
})
}, },
mouseleave (card) { mouseleave (card) {
this.tableData.forEach(t => {
if (t.knowledgeId === card.knowledgeId) {
card.showUpdate = false card.showUpdate = false
}
})
}, },
del (data) { del (data) {
this.$emit('delete', data) this.$emit('delete', data)
@@ -394,9 +743,50 @@ export default {
dataType: dataType dataType: dataType
} }
}) })
},
confirmSwitchLearning (id) {
this.showConfirmSwitch = true
this.switchKnowledgeId = id
return false
},
cancleSwitch () {
this.showConfirmSwitch = 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.timeChange()
},
/*
hasUpdatingRecord (n) {
if (n) { // update record页存在“正在更新”的记录时每20秒自动请求一次接口
this.timer = setTimeout(() => {
this.getCurTabData()
}, 20000)
} else { // 直到出现新的记录,出现新记录后(失败或者成功),取消定时请求接口,右上角"update"按钮恢复可用。"正在更新"和"失败都会有对应的强调样式
clearTimeout(this.timer)
}
},
*/
tableData: { tableData: {
handler (n) { handler (n) {
if (this.tableData && this.tableData.length > 0) { if (this.tableData && this.tableData.length > 0) {
@@ -420,15 +810,33 @@ 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 = []
this.timeChange()
} 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 +848,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 +871,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

@@ -1,10 +1,10 @@
import { createI18n } from 'vue-i18n/index' import { createI18n } from 'vue-i18n/index'
import { storageKey } from '@/utils/constants' import { storageKey, EN } from '@/utils/constants'
import { getI18n } from '@/utils/api' import { getI18n } from '@/utils/api'
import store from '@/store' import store from '@/store'
const i18n = createI18n({ const i18n = createI18n({
locale: localStorage.getItem(storageKey.language) || 'en' locale: localStorage.getItem(storageKey.language) || EN
}) })
export async function loadI18n () { export async function loadI18n () {
if (!store.state.i18n) { if (!store.state.i18n) {

View File

@@ -5,9 +5,8 @@ import router from '@/router'
import store from '@/store' import store from '@/store'
import App from '@/App.vue' import App from '@/App.vue'
import '@/utils/http.js' import '@/utils/http.js'
import { hasPermission } from '@/permission'
import commonMixin from '@/mixins/common' import commonMixin from '@/mixins/common'
import { cancelWithChange, noData } from '@/utils/tools' import { cancelWithChange, noData, myHighLight } from '@/utils/tools'
import { ClickOutside } from 'element-plus/lib/directives' import { ClickOutside } from 'element-plus/lib/directives'
import i18n from '@/i18n' import i18n from '@/i18n'
// import '@/mock/index.js' // import '@/mock/index.js'
@@ -37,10 +36,10 @@ app.use(i18n)
app.use(hljsVuePlugin) app.use(hljsVuePlugin)
app.use(VueGridLayout) app.use(VueGridLayout)
app.directive('has', hasPermission) // 注册指令
app.directive('ele-click-outside', ClickOutside) app.directive('ele-click-outside', ClickOutside)
app.directive('cancel', cancelWithChange) app.directive('cancel', cancelWithChange)
app.directive('no-data', noData) app.directive('no-data', noData)
app.directive('high-light', myHighLight)
app.config.globalProperties.$_ = _ app.config.globalProperties.$_ = _
app.mixin(commonMixin) app.mixin(commonMixin)

View File

@@ -1,4 +1,4 @@
import { hasButton } from '@/permission' import { hasPermission } from '@/permission'
import { dateFormatByAppearance } from '@/utils/date-util' import { dateFormatByAppearance } from '@/utils/date-util'
import { commonErrorTip } from '@/utils/constants' import { commonErrorTip } from '@/utils/constants'
export default { export default {
@@ -28,9 +28,7 @@ export default {
} }
}, },
methods: { methods: {
hasButton (code) { hasPermission,
return hasButton(this.$store.getters.buttonList, code)
},
errorMsgHandler (axiosError) { errorMsgHandler (axiosError) {
if (axiosError.response) { if (axiosError.response) {
if (axiosError.response.data) { if (axiosError.response.data) {

View File

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

@@ -17,7 +17,7 @@ export default {
// 请求数据 relationshipUrlOne => 路由 refOne => ref // 请求数据 relationshipUrlOne => 路由 refOne => ref
getRelatedServerDataOne (relationshipUrlOne, refOne) { getRelatedServerDataOne (relationshipUrlOne, refOne) {
this.loadingRelationshipOne = true this.loadingRelationshipOne = true
axios.get(relationshipUrlOne, { params: this.getQueryParams() }).then(response => { axios.get(relationshipUrlOne, { params: this.getQueryParams(DEFAULT_TIME_FILTER_RANGE.entity.relatedEntity) }).then(response => {
if (response.status === 200) { if (response.status === 200) {
const relationshipDataOne = [] const relationshipDataOne = []
if (response.data.data.result.length > 0) { if (response.data.data.result.length > 0) {
@@ -33,7 +33,7 @@ export default {
}, },
getRelatedServerDataTwo (relationshipUrlTow, refTow) { getRelatedServerDataTwo (relationshipUrlTow, refTow) {
this.loadingRelationshipTwo = true this.loadingRelationshipTwo = true
axios.get(relationshipUrlTow, { params: this.getQueryParams() }).then(response => { axios.get(relationshipUrlTow, { params: this.getQueryParams(DEFAULT_TIME_FILTER_RANGE.entity.relatedEntity) }).then(response => {
if (response.status === 200) { if (response.status === 200) {
const relationshipDataTwo = [] const relationshipDataTwo = []
if (response.data.data.result.length > 0) { if (response.data.data.result.length > 0) {
@@ -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

@@ -1,4 +1,4 @@
import { chartTableOrderOptionsMapping, storageKey, knowledgeCategoryValue } from '@/utils/constants' import { chartTableOrderOptionsMapping, storageKey, knowledgeCategoryValue, ZH } from '@/utils/constants'
import { getWidthByLanguage } from '@/utils/tools' import { getWidthByLanguage } from '@/utils/tools'
import { api } from '@/utils/api' import { api } from '@/utils/api'
import axios from 'axios' import axios from 'axios'
@@ -58,7 +58,7 @@ export default {
const language = localStorage.getItem(storageKey.language) const language = localStorage.getItem(storageKey.language)
// 文字所占宽度一个英文字母占7px中文16px // 文字所占宽度一个英文字母占7px中文16px
let num = getWidthByLanguage(language) || 7 let num = getWidthByLanguage(language) || 7
if (language !== 'cn') { if (language !== ZH) {
num = num + 1 // 最后一位加空格 num = num + 1 // 最后一位加空格
} }

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',
knowledgeBase: {
knowledgeId: 10, knowledgeId: 10,
level: 10 name: 'cn_ioc_darkweb',
category: 'websketch',
source: 'cn_ioc_darkweb'
}, },
trigger: { level: 'critical'
},
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

@@ -32,14 +32,19 @@ router.beforeEach(async (to, from, next) => {
store.commit('setMenuList', menuList) store.commit('setMenuList', menuList)
store.commit('setButtonList', buttonList) store.commit('setButtonList', buttonList)
store.commit('setRoleList', roleList) store.commit('setRoleList', roleList)
const homeRoute = {
path: '/',
name: 'home',
component: () => import('@/components/layout/Home'),
children: []
} }
handleRoutes(menuList, homeRoute.children)
router.addRoute(homeRoute)
next({ ...to, replace: true })
} else {
if (to.path) { if (to.path) {
next() next()
/* if (hasMenu(store.getters.menuList, to.path)) { }
next()
} else {
ElMessage.error('No access') // TODO 国际化
} */
} }
} }
} else { } else {
@@ -60,14 +65,14 @@ router.beforeEach(async (to, from, next) => {
} }
}) })
// menuList中是否包含route权限 // menuList中是否包含code
export function hasMenu (menuList, route) { export function hasMenu (menuList, code) {
return menuList.some(menu => { return menuList.some(menu => {
if (menu.route === route) { if (menu.code === code) {
return true return true
} else { } else {
if (menu.children) { if (menu.children) {
if (hasMenu(menu.children, route)) { if (hasMenu(menu.children, code)) {
return true return true
} }
} }
@@ -96,36 +101,9 @@ export function hasButton (buttonList, code) {
return buttonList.some(button => button === code) return buttonList.some(button => button === code)
} }
// 用法 v-has="code" | v-has="[code...]" 任意匹配一个 | v-has:all="[code...]" 全匹配 // 根据code从menuList和buttonList中判断是否有权限
export const hasPermission = { export function hasPermission (code) {
beforeMount (el, binding) { return hasButton(store.getters.buttonList, code) || hasMenu(store.getters.menuList, code)
// 节点权限处理
const buttonCode = binding.value
const arg = binding.arg
if (buttonCode) {
if (buttonCode instanceof Array) {
let has = true
if (arg && arg === 'all') { // 全匹配
buttonCode.forEach(button => {
if (has) {
has = hasButton(store.getters.buttonList, button)
}
})
} else { // 任意匹配
has = buttonCode.some(button => {
return hasButton(store.getters.buttonList, button)
})
}
if (!has) {
el.parentNode.removeChild(el)
}
} else { // 单个匹配
if (!hasButton(store.getters.buttonList, buttonCode)) {
el.parentNode && el.parentNode.removeChild(el)
}
}
}
}
} }
// 根据orderNum排序 // 根据orderNum排序
@@ -160,3 +138,100 @@ export function getWelcomeMenu (menu) {
} }
} }
} }
export function handleComponent (code) {
switch (code) {
case 'networkOverview':
case 'networkAppPerformance':
case 'dnsServiceInsights':
case 'linkMonitor':
return () => import('@/views/charts2/Panel')
case 'entity':
return () => import('@/views/entityExplorer/EntityExplorer')
case 'entityDetail':
return () => import('@/views/entityExplorer/EntityDetail')
case 'entityGraph':
return () => import('@/views/entityExplorer/EntityGraph')
case 'securityEvents':
case 'performanceEvents':
return () => import('@/views/detections/Index')
case 'detectionPolicy':
return () => import('@/views/detections/detectionPolicies/Index')
case 'createDetectionPolicy':
case 'editDetectionPolicy':
return () => import('@/views/detections/detectionPolicies/PolicyForm')
case 'report':
return () => import('@/views/report/Report')
case 'knowledgeBase':
return () => import('@/views/setting/KnowledgeBase')
case 'userDefinedLibrary':
return () => import('@/views/setting/KnowledgeBaseUserDefinedList')
case 'createUserDefinedLibrary':
case 'editUserDefinedLibrary':
return () => import('@/views/setting/KnowledgeBaseForm')
case 'administration':
return () => import('@/views/administration/Index')
case 'user':
return () => import('@/views/administration/User')
case 'role':
return () => import('@/views/administration/Roles')
case 'operationLog':
return () => import('@/views/administration/OperationLog')
case 'appearance':
return () => import('@/views/administration/Appearance')
case 'I18N':
return () => import('@/views/administration/I18n')
default:
return null
}
}
export function handleRoutes (menus, routes) {
menus.forEach(menu => {
if (menu.route === '' && (!menu.children || menu.children.length < 0)) {
return false
}
// administration的路由使用了嵌套其他的是平铺
if (menu.pid === 0 && menu.code === 'administration') {
const path = menu.route.replace('redirect:', '')
if (menu.children && menu.children.length > 0) {
const route = {
name: menu.name,
path,
redirect: menu.children[0].route,
component: handleComponent(menu.code),
children: []
}
menu.children.forEach(c => {
route.children.push({
name: c.name,
path: c.route,
component: handleComponent(c.code)
})
})
routes.push(route)
}
} else {
if (menu.route && menu.route.startsWith('redirect:')) {
const path = menu.route.replace('redirect:', '')
if (menu.children && menu.children.length > 0) {
routes.push({
name: menu.name,
path,
redirect: menu.children[0].route
})
handleRoutes(menu.children, routes)
}
} else {
routes.push({
name: menu.code,
path: menu.route,
component: handleComponent(menu.code)
})
}
if (menu.children && menu.children.length > 0) {
handleRoutes(menu.children, routes)
}
}
})
}

View File

@@ -5,111 +5,6 @@ const routes = [
{ {
path: '/login', path: '/login',
component: () => import('@/Login') component: () => import('@/Login')
},
{
path: '/',
component: () => import('@/components/layout/Home'),
children: [
{
name: 'panel',
path: '/panel/:typeName',
component: () => import('@/views/charts2/Panel')
},
{
path: '/report/builtIn',
component: () => import('@/views/report/Report')
},
{
path: '/entityExplorer',
component: () => import('@/views/entityExplorer/EntityExplorer')
},
{
path: '/entityDetail',
component: () => import('@/views/entityExplorer/EntityDetail')
},
{
path: '/entityGraph',
component: () => import('@/views/entityExplorer/EntityGraph')
},
{
path: '/detection',
redirect: '/detection/securityEvent'
},
{
path: '/detection/:typeName',
component: () => import('@/views/detections/Index')
},
{
path: '/businessLog/viewer',
component: () => import('@/views/businessLog/Viewer')
},
{
path: '/knowledgeBase',
component: () => import('@/views/setting/KnowledgeBase')
},
{
path: '/knowledgeBase/userDefinedLibrary',
component: () => import('@/views/setting/KnowledgeBase')
},
{
path: '/knowledgeBase/userDefinedLibrary/create',
component: () => import('@/views/setting/KnowledgeBaseForm')
},
{
path: '/knowledgeBase/userDefinedLibrary/edit',
component: () => import('@/views/setting/KnowledgeBaseForm')
},
{
name: 'Administration',
path: '/administration',
redirect: '/administration/user',
component: () => import('@/views/administration/Index'),
children: [
{
name: 'User',
path: '/administration/user',
component: () => import('@/views/administration/User')
},
{
name: 'Role',
path: '/administration/role',
component: () => import('@/views/administration/Roles')
},
{
name: 'OperationLog',
path: '/administration/operationLog',
component: () => import('@/views/administration/OperationLog')
},
{
name: 'Appearance',
path: '/administration/appearance',
component: () => import('@/views/administration/Appearance')
}
]
},
{
name: 'I18n',
path: '/i18n',
component: () => import('@/views/administration/I18n')
},
{
name: 'Chart',
path: '/chart',
component: () => import('@/views/administration/Chart')
},
{
path: '/detectionsNew',
component: () => import('@/views/detectionsNew/Index')
},
{
path: '/detection/policies',
component: () => import('@/views/detectionsNew/Index')
},
{
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

@@ -1,6 +1,6 @@
import axios from 'axios' import axios from 'axios'
import router from '@/router' import router from '@/router'
import { getWelcomeMenu, sortByOrderNum } from '@/permission' import { getWelcomeMenu, sortByOrderNum, handleRoutes } from '@/permission'
import { ElMessage } from 'element-plus' // dependent on utc plugin import { ElMessage } from 'element-plus' // dependent on utc plugin
import { dbDrilldownTableConfig, storageKey } from '@/utils/constants' import { dbDrilldownTableConfig, storageKey } from '@/utils/constants'
import { getConfigVersion } from '@/utils/tools' import { getConfigVersion } from '@/utils/tools'
@@ -64,7 +64,14 @@ const user = {
store.commit('setMenuList', menuList) store.commit('setMenuList', menuList)
store.commit('setButtonList', res2.data.buttons) store.commit('setButtonList', res2.data.buttons)
store.commit('setRoleList', res2.data.roles) store.commit('setRoleList', res2.data.roles)
const homeRoute = {
path: '/',
name: 'home',
component: () => import('@/components/layout/Home'),
children: []
}
handleRoutes(menuList, homeRoute.children)
router.addRoute(homeRoute)
if (res.loginSuccessPath) { if (res.loginSuccessPath) {
let tempArr = res.loginSuccessPath.split('?') let tempArr = res.loginSuccessPath.split('?')
const path = tempArr[0] const path = tempArr[0]
@@ -118,7 +125,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' }
] ]
} }
@@ -80,15 +80,15 @@ export const dataForNetworkOverviewLine = {
options2: [ options2: [
{ {
value: 'Average', value: 'Average',
label: 'Average' label: 'overall.average'
}, },
{ {
value: '95th Percentile', value: '95th Percentile',
label: '95th Percentile' label: ['overall.percentileNumber', { number: 95 }]
}, },
{ {
value: 'Maximum', value: 'Maximum',
label: 'Maximum' label: 'overall.maximum'
} }
], ],
tabsTemplate: [ tabsTemplate: [
@@ -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' },
@@ -200,15 +200,15 @@ export const dataForDnsTrafficLine = {
options2: [ options2: [
{ {
value: 'Average', value: 'Average',
label: 'Average' label: 'overall.average'
}, },
{ {
value: '95th Percentile', value: '95th Percentile',
label: '95th Percentile' label: ['overall.percentileNumber', { number: 95 }]
}, },
{ {
value: 'Maximum', value: 'Maximum',
label: 'Maximum' label: 'overall.maximum'
} }
], ],
tabs: [ tabs: [
@@ -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

@@ -1,12 +1,13 @@
import { ElMessageBox, ElMessage } from 'element-plus' import { ElMessageBox, ElMessage } from 'element-plus'
import i18n from '@/i18n' import i18n from '@/i18n'
import _ from 'lodash' import _ from 'lodash'
import { storageKey, iso36112, topDomain, echartsFontSize, dbGeoDataTableName, networkTable, dbDrilldownTableConfig } from '@/utils/constants' import { storageKey, iso36112, topDomain, echartsFontSize, dbGeoDataTableName, networkTable, dbDrilldownTableConfig, ZH, EN } from '@/utils/constants'
import { getIso36112JsonData, getDictList } from '@/utils/api' import { getIso36112JsonData, getDictList } from '@/utils/api'
import { format } from 'echarts' import { format } from 'echarts'
import router from '@/router' import router from '@/router'
import store from '@/store' import store from '@/store'
import indexedDBUtils from '@/indexedDB' import indexedDBUtils from '@/indexedDB'
import { columnType } from '@/components/advancedSearch/meta/meta'
export const tableSort = { export const tableSort = {
// 是否需要排序 // 是否需要排序
@@ -784,7 +785,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 +800,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] <= 50) { } else if (data[t] <= scoreBase[t].p10) {
score = 1 score = 1
} else if (data[t] > 200) { } else if (data[t] >= scoreBase[t].p90) {
score = 0 score = 0
} else { } else {
score = (data[t] - 200) / (50 - 200) score = (data[t] - scoreBase[t].p90) / (scoreBase[t].p10 - scoreBase[t].p90)
}
} 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)
}) })
@@ -1260,10 +1249,10 @@ export function getQueryByFlag2 (type, condition) {
*/ */
export function getWidthByLanguage (language) { export function getWidthByLanguage (language) {
switch (language) { switch (language) {
case 'en': { case EN: {
return 7 return 7
} }
case 'cn': { case ZH: {
return 16 return 16
} }
} }
@@ -1318,12 +1307,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 +1346,104 @@ 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};`
}
}
/**
* 字段高亮
* 用法: <span v-high-light=[{type: 'string', value: '搜索'}, {type: 'fullText', value: '高亮'}]>搜索关键字高亮<span>
* 其中type为fullText的为模糊搜索
* @type {{updated(*, *): (*|undefined)}}
*/
export const myHighLight = {
updated (el, binding) {
if (el && binding.value) {
const { value } = binding
if (value && value.length > 0) {
const text = _.cloneDeep(el.innerHTML)
const regex = new RegExp(value.map(item => `${item.value}`).join('|'), 'g')
if (el.getElementsByClassName('highlight__text').length === 0) {
const newText = text.replace(regex, (match) => {
// 将value中的value提取出来对比string即精准搜索fullText模糊搜索
for (const item of value) {
if ((item.type === columnType.string && item.value === el.innerHTML) || el.className.indexOf('high-location') > -1) {
if (el.className.indexOf('high-light-block') > -1) {
return `<span class="highlight__block">${match}</span>`
} else {
return `<span class="highlight__text">${match}</span>`
}
} else if (item.type === columnType.fullText && item.value === match) {
if (el.className.indexOf('high-light-block') > -1) {
return `<span class="highlight__block">${match}</span>`
} else {
return `<span class="highlight__text">${match}</span>`
}
}
}
return match
})
if (newText && newText !== '-') {
// 此处不用el.className.indexOf('high-light-block')判断是因为block可能会有多个有一个满足所有的都会渲染
if (newText.indexOf('highlight__block') > -1) {
el.style.cssText = el.style.cssText + 'background: #FEECC2;'
// 此处是相关app、相关ip、相关domain弹窗获取不到dom的草错
let dom
if (document.getElementById('showRelatedApp')) {
dom = document.getElementById('showRelatedApp')
} else if (document.getElementById('showRelatedDomain')) {
dom = document.getElementById('showRelatedDomain')
}
if (dom) {
const itemDom = dom.getElementsByClassName('high-light-block')
if (itemDom) {
for (let i = 0; i < itemDom.length; i++) {
if (itemDom[i].innerHTML === el.innerHTML) {
itemDom[i].style.cssText = itemDom[i].style.cssText + 'background: #FEECC2;'
}
}
}
}
} else {
el.innerHTML = newText
}
} else {
return newText
}
}
}
}
}
}
export const changeTimeByDate = (date) => {
if (date) {
const nowDate = Date.now()
const oldDate = isNaN(date) ? Date.parse(date) : date
const diff = Math.floor((nowDate - oldDate) / 1000)
if (diff < 60) {
return diff + i18n.global.t('entity.search.secondsAgo')
} else if (diff >= 60 && diff < 3600) {
const minutes = Math.floor(diff / 60)
return minutes === 1 ? `${minutes} ${i18n.global.t('entity.search.minuteAgo')}` : `${minutes} ${i18n.global.t('entity.search.minutesAgo')}`
} else if (diff >= 3600 && diff < 86400) {
const hours = Math.floor(diff / 3600)
return hours === 1 ? `${hours} ${i18n.global.t('entity.search.hourAgo')}` : `${hours} ${i18n.global.t('entity.search.hoursAgo')}`
} else {
return date
}
}
}

View File

@@ -48,7 +48,9 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<div class="edit-appearance-base__footer"> <div class="edit-appearance-base__footer">
<button style="position: relative;" :class="{'footer__btn--disabled': blockOperation.save}" :disabled="blockOperation.save" class="footer__btn" @click="save"> <button style="position: relative;" :class="{'footer__btn--disabled': blockOperation.save}" :disabled="blockOperation.save" class="footer__btn" @click="save"
v-if="hasPermission('editAppearence')"
>
<loading size="small" :loading="blockOperation.save"></loading> <loading size="small" :loading="blockOperation.save"></loading>
<span>{{$t('overall.save')}}</span> <span>{{$t('overall.save')}}</span>
</button> </button>
@@ -58,7 +60,7 @@
</template> </template>
<script> <script>
import { api } from '@/utils/api' import { api } from '@/utils/api'
import { storageKey } from '@/utils/constants' import { storageKey, ZH, EN } from '@/utils/constants'
import axios from 'axios' import axios from 'axios'
import dayjs from 'dayjs' import dayjs from 'dayjs'
@@ -97,12 +99,12 @@ export default {
{ {
id: 1, id: 1,
label: 'English', label: 'English',
value: 'en' value: EN
}, },
{ {
id: 2, id: 2,
label: '中文', label: '中文',
value: 'zh' value: ZH
} }
] ]
} }

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

@@ -11,16 +11,19 @@
> >
<template v-slot:top-tool-left> <template v-slot:top-tool-left>
<button id="roles-add" class="top-tool-btn margin-r-10 top-tool-btn--create" <button id="roles-add" class="top-tool-btn margin-r-10 top-tool-btn--create"
v-if="hasPermission('createRole')"
@click="add"> @click="add">
<i class="cn-icon-xinjian cn-icon"></i> <i class="cn-icon-xinjian cn-icon"></i>
<span>{{$t('overall.create')}}</span> <span>{{$t('overall.create')}}</span>
</button> </button>
<button id="roles-edit" class="top-tool-btn margin-r-10" :disabled="disableEdit" <button id="roles-edit" class="top-tool-btn margin-r-10" :disabled="disableEdit"
v-if="hasPermission('editRole')"
@click="edit"> @click="edit">
<i class="cn-icon-edit cn-icon"></i> <i class="cn-icon-edit cn-icon"></i>
<span>{{$t('overall.edit')}}</span> <span>{{$t('overall.edit')}}</span>
</button> </button>
<button id="roles-delete" class="top-tool-btn margin-r-10" :disabled="disableDelete" <button id="roles-delete" class="top-tool-btn margin-r-10" :disabled="disableDelete"
v-if="hasPermission('deleteRole')"
@click="delBatch"> @click="delBatch">
<i class="cn-icon-delete cn-icon"></i> <i class="cn-icon-delete cn-icon"></i>
<span>{{$t('overall.delete')}}</span> <span>{{$t('overall.delete')}}</span>

View File

@@ -11,16 +11,19 @@
> >
<template #top-tool-left> <template #top-tool-left>
<button id="account-add" class="top-tool-btn margin-r-10 top-tool-btn--create" <button id="account-add" class="top-tool-btn margin-r-10 top-tool-btn--create"
v-if="hasPermission('createUser')"
@click="add"> @click="add">
<i class="cn-icon-xinjian cn-icon"></i> <i class="cn-icon-xinjian cn-icon"></i>
<span>{{$t('overall.create')}}</span> <span>{{$t('overall.create')}}</span>
</button> </button>
<button id="account-edit" class="top-tool-btn margin-r-10" :disabled="disableEdit" <button id="account-edit" class="top-tool-btn margin-r-10" :disabled="disableEdit"
v-if="hasPermission('editUser')"
@click="editSelectRecord"> @click="editSelectRecord">
<i class="cn-icon-edit cn-icon"></i> <i class="cn-icon-edit cn-icon"></i>
<span>{{$t('overall.edit')}}</span> <span>{{$t('overall.edit')}}</span>
</button> </button>
<button id="account-delete" class="top-tool-btn margin-r-10" :disabled="disableDelete" <button id="account-delete" class="top-tool-btn margin-r-10" :disabled="disableDelete"
v-if="hasPermission('deleteUser')"
@click="delBatch"> @click="delBatch">
<i class="cn-icon-delete cn-icon"></i> <i class="cn-icon-delete cn-icon"></i>
<span>{{$t('overall.delete')}}</span> <span>{{$t('overall.delete')}}</span>

View File

@@ -5,12 +5,12 @@
v-if="panel.params && panel.params.wholeScreenScroll" v-if="panel.params && panel.params.wholeScreenScroll"
id="wholeScreenBox" id="wholeScreenBox"
> >
<dns-screen v-if="currentPath === wholeScreenRouterMapping.dns" <!-- <dns-screen v-if="currentPath === wholeScreenRouterMapping.dns"
:copy-data-list="chartList" :copy-data-list="chartList"
ref="dnsScreen" ref="dnsScreen"
:time-filter="timeFilter" :time-filter="timeFilter"
:entity="entity"> :entity="entity">
</dns-screen> </dns-screen>-->
</div> </div>
<div id="panelList" v-if="!isEntityDetail" class="panel-list"> <div id="panelList" v-if="!isEntityDetail" class="panel-list">
<div id="cn-panel" class="cn-panel2"> <div id="cn-panel" class="cn-panel2">
@@ -49,7 +49,7 @@
<script> <script>
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { ref } from 'vue' import { ref } from 'vue'
import DnsScreen from '@/views/charts/wholeScreenScroll/DnsScreen' // import DnsScreen from '@/views/charts/wholeScreenScroll/DnsScreen'
import { panelTypeAndRouteMapping, wholeScreenRouterMapping } from '@/utils/constants' import { panelTypeAndRouteMapping, wholeScreenRouterMapping } from '@/utils/constants'
import { api, getPanelList, getChartList } from '@/utils/api' import { api, getPanelList, getChartList } from '@/utils/api'
import { getNowTime } from '@/utils/date-util' import { getNowTime } from '@/utils/date-util'
@@ -66,9 +66,9 @@ export default {
isEntityDetail: Boolean, isEntityDetail: Boolean,
typeName: String typeName: String
}, },
components: { /* components: {
DnsScreen DnsScreen
}, }, */
data () { data () {
return { return {
chartList: [], // 普通panel的chart chartList: [], // 普通panel的chart
@@ -104,8 +104,8 @@ export default {
setup (props, ctx) { setup (props, ctx) {
const panel = ref({}) const panel = ref({})
let panelType = 1 // 取得panel的type let panelType = 1 // 取得panel的type
const { params } = useRoute() const { path } = useRoute()
panelType = props.entity ? props.entity.type : panelTypeAndRouteMapping[params.typeName] panelType = props.entity ? props.entity.type : panelTypeAndRouteMapping[path.replace('/panel/', '')]
// date // date
const dateRangeValue = 60 const dateRangeValue = 60

View File

@@ -338,7 +338,10 @@ export function axisFormatter (params) {
return str return str
} }
export function tooLongFormatter (name) { export function tooLongFormatter (name) {
return format.truncateText(name, 110, '12px') return format.truncateText(name, 160, '12px')
}
export function tooLongFormatterFor2Columns (name) {
return format.truncateText(name, 100, '12px')
} }
export function timeHorizontalFormatter (params) { export function timeHorizontalFormatter (params) {
let str = '<div>' let str = '<div>'

View File

@@ -31,7 +31,7 @@
@change="metricChange" @change="metricChange"
> >
<template #prefix> <template #prefix>
<span class="select-prefix">Metric:</span> <span class="select-prefix">{{$t('detections.metric')}}:</span>
</template> </template>
<el-option v-for="item in metricOptions" :key="item.value" :label="item.label" :value="item.value"></el-option> <el-option v-for="item in metricOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select> </el-select>
@@ -104,7 +104,9 @@ export default {
dnsRcodeMapData: [], dnsRcodeMapData: [],
dnsQtypeMapData: [], dnsQtypeMapData: [],
score: null, score: null,
curTabState: curTabState curTabState: curTabState,
performanceData: {},
scoreDataState: false // 评分数据是否加载完成
} }
}, },
computed: { computed: {
@@ -114,29 +116,39 @@ 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.$store.commit('resetScoreBase')
this.queryScoreBase()
if (this.lineQueryCondition || this.networkOverviewBeforeTab) {
this.scoreCalculation() this.scoreCalculation()
} }
} }
} }
}, },
scoreBaseState (n) {
if (n && this.scoreDataState) {
this.handleScoreData()
}
},
scoreDataState (n) {
if (n && this.scoreBaseState) {
this.handleScoreData()
}
}
},
async mounted () { async mounted () {
// this.panelName = this.$store.getters.getPanelName // this.panelName = this.$store.getters.getPanelName
const pName = this.$route.query.panelName ? this.$t(this.$route.query.panelName) : '' const pName = this.$route.query.panelName ? this.$t(this.$route.query.panelName) : ''
const curTabProp = this.$route.query.dimensionType ? this.$route.query.dimensionType : null const curTabProp = this.$route.query.dimensionType ? this.$route.query.dimensionType : null
if (this.$route.params.typeName === fromRoute.dnsServiceInsights) { if (this.$route.path.replace('/panel/', '') === fromRoute.dnsServiceInsights) {
this.dnsQtypeMapData = await getDnsMapData('dnsQtype') this.dnsQtypeMapData = await getDnsMapData('dnsQtype')
this.dnsRcodeMapData = await getDnsMapData('dnsRcode') this.dnsRcodeMapData = await getDnsMapData('dnsRcode')
this.$store.commit('setDnsQtypeMapData', this.dnsQtypeMapData) this.$store.commit('setDnsQtypeMapData', this.dnsQtypeMapData)
@@ -212,9 +224,15 @@ export default {
return chart return chart
}) })
}) })
if (this.$route.path === '/panel/networkAppPerformance' && (this.lineQueryCondition || this.networkOverviewBeforeTab)) { if (this.$route.path === '/panel/networkAppPerformance') {
if (this.lineQueryCondition || this.networkOverviewBeforeTab) {
this.scoreCalculation() this.scoreCalculation()
} }
}
if (this.$route.path === '/panel/networkAppPerformance' || this.$route.path === '/panel/linkMonitor') {
this.$store.commit('resetScoreBase')
this.queryScoreBase()
}
}, },
setup (props) { setup (props) {
// router引入store会报错故在panel里调用 // router引入store会报错故在panel里调用
@@ -231,12 +249,11 @@ export default {
const panel = ref({}) const panel = ref({})
let panelType = 1 // 取得panel的type let panelType = 1 // 取得panel的type
let { params, query, path } = useRoute() let { query, path } = useRoute()
// 获取路由跳转过的历史状态,赋值给当前界面,起到保留状态的作用,如浏览器的回退前进等 // 获取路由跳转过的历史状态,赋值给当前界面,起到保留状态的作用,如浏览器的回退前进等
const routerObj = store.getters.getRouterHistoryList.find(item => item.t === query.t) const routerObj = store.getters.getRouterHistoryList.find(item => item.t === query.t)
if (routerObj) { if (routerObj) {
params = routerObj.params
query = routerObj.query query = routerObj.query
path = routerObj.path path = routerObj.path
@@ -274,19 +291,19 @@ export default {
} else if (thirdPanel) { } else if (thirdPanel) {
panelType = Number(thirdPanel) panelType = Number(thirdPanel)
} else { } else {
panelType = props.entity ? props.entity.type : panelTypeAndRouteMapping[params.typeName] panelType = props.entity ? props.entity.type : panelTypeAndRouteMapping[path.replace('/panel/', '')]
} }
// 获取url携带的range、startTime、endTime // 获取url携带的range、startTime、endTime
const rangeParam = query.range const rangeParam = query.range
const startTimeParam = query.startTime const startTimeParam = query.startTime
const endTimeParam = query.endTime const endTimeParam = query.endTime
// 若url携带了使用携带的值否则使用默认值。
const dateRangeValue = rangeParam ? parseInt(query.range) : 60 // 优先级url > config.js > 默认值。
const dateRangeValue = rangeParam ? parseInt(rangeParam) : (DEFAULT_TIME_FILTER_RANGE.dashboard || 60)
const timeFilter = ref({ dateRangeValue }) const timeFilter = ref({ dateRangeValue })
if (!startTimeParam || !endTimeParam) { if (!startTimeParam || !endTimeParam) {
const { startTime, endTime } = getNowTime(60) const { startTime, endTime } = getNowTime(dateRangeValue)
timeFilter.value.startTime = getSecond(startTime) timeFilter.value.startTime = getSecond(startTime)
timeFilter.value.endTime = getSecond(endTime) timeFilter.value.endTime = getSecond(endTime)
// 如果没有时间参数就将参数写入url // 如果没有时间参数就将参数写入url
@@ -396,6 +413,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,30 +498,36 @@ 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
}) })
} }
}, },
jumpEntityDetail () { jumpEntityDetail () {
const { href } = this.$router.resolve({ const { href } = this.$router.resolve({
path: '/entityDetail', path: '/entity/detail',
query: { query: {
entityType: this.entityType, entityType: this.entityType,
entityName: this.entityValue entityName: this.entityValue
} }
}) })
window.open(href, '_blank') window.open(href, '_blank')
},
handleScoreData () {
this.score = computeScore(this.performanceData, this.$store.getters.getScoreBase)
} }
}, },
/** /**

View File

@@ -1,4 +1,5 @@
import { entityDetailRelatedEntitiesShowSize } from '@/utils/constants' import { entityDetailRelatedEntitiesShowSize, entityDetailTabsName } from '@/utils/constants'
import { getSecond } from '@/utils/date-util'
export default { export default {
props: { props: {
@@ -28,6 +29,59 @@ export default {
} }
} }
}, },
getParamsByTabType (tabType) {
let params = {
resource: this.entity.entityName
}
let dataRangeValue = 60 * 24 * 7
switch (tabType) {
case entityDetailTabsName.relatedEntity:
dataRangeValue = DEFAULT_TIME_FILTER_RANGE.entity.relatedEntity
break
case entityDetailTabsName.performanceEvent:
dataRangeValue = DEFAULT_TIME_FILTER_RANGE.entity.performanceEvent
break
case entityDetailTabsName.securityEvent:
dataRangeValue = DEFAULT_TIME_FILTER_RANGE.entity.securityEvent
break
case entityDetailTabsName.openPort:
dataRangeValue = DEFAULT_TIME_FILTER_RANGE.entity.openPort
break
case entityDetailTabsName.informationAggregation:
dataRangeValue = DEFAULT_TIME_FILTER_RANGE.entity.informationAggregation
break
case entityDetailTabsName.behaviorPattern:
dataRangeValue = DEFAULT_TIME_FILTER_RANGE.entity.behaviorPattern
break
default:
dataRangeValue = 60 * 24 * 7
}
if (dataRangeValue !== 0) {
const endTime = window.$dayJs.tz().valueOf()
const startTime = endTime - dataRangeValue * 60 * 1000
params = {
...params,
startTime: getSecond(startTime),
endTime: getSecond(endTime)
}
}
return params
},
getParams () {
const range = this.timeFilter.dateRangeValue
let params = {
resource: this.entity.entityName
}
if (range !== 0) {
params = {
...params,
startTime: getSecond(this.timeFilter.startTime),
endTime: getSecond(this.timeFilter.endTime)
}
}
return params
},
handleShowDataNum (showListInfo, allList) { handleShowDataNum (showListInfo, allList) {
if (allList.length <= entityDetailRelatedEntitiesShowSize) { if (allList.length <= entityDetailRelatedEntitiesShowSize) {
showListInfo.num = allList.length showListInfo.num = allList.length

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,19 +41,22 @@
<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')}}&nbsp;:&nbsp;</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"
@change="referenceSelectChange" @change="referenceSelectChange"
> >
<el-option v-for="item in options2" :key="item.value" :label="item.label" :value="item.value"></el-option> <el-option :key="options2[0].value" :label="$t(options2[0].label)" :value="options2[0].value"></el-option>
<el-option :key="options2[1].value" :label="$t(options2[1].label[0], options2[1].label[1])" :value="options2[1].value"></el-option>
<el-option :key="options2[2].value" :label="$t(options2[2].label)" :value="options2[2].value"></el-option>
</el-select> </el-select>
</div> </div>
</div> </div>
@@ -174,6 +178,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)
@@ -250,6 +257,14 @@ export default {
label: { label: {
formatter (params) { formatter (params) {
const arr = unitConvert(params.value, unitTypes.number).join('') const arr = unitConvert(params.value, unitTypes.number).join('')
const referIndex = _this.options2.findIndex(o => o.value === _this.lineRefer)
if (referIndex > -1) {
if (referIndex === 1) {
return _this.$t(_this.options2[1].label[0], _this.options2[1].label[1]) + '(' + arr + echartsData[0].unitType + ')'
} else {
return _this.$t(_this.options2[referIndex].label) + '(' + arr + echartsData[0].unitType + ')'
}
}
return _this.lineRefer + '(' + arr + echartsData[0].unitType + ')' return _this.lineRefer + '(' + arr + echartsData[0].unitType + ')'
}, },
position: 'insideStartTop', position: 'insideStartTop',
@@ -445,6 +460,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 +480,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 +490,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 +510,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

@@ -1,7 +1,7 @@
<template> <template>
<div class="entity-detail-basic-info"> <div class="entity-detail-basic-info">
<chart-error v-if="showError" :content="errorMsg"/> <chart-error v-if="showError" :content="errorMsg"/>
<div class="entity-type">{{entityType[entity.entityType]}}</div> <div class="entity-type">{{entityTypeName}}</div>
<div class="entity-basic-info"> <div class="entity-basic-info">
<div class="entity-basic-info__name"> <div class="entity-basic-info__name">
<span id="entityName">{{entity.entityName}}</span> <span id="entityName">{{entity.entityName}}</span>
@@ -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'
@@ -83,17 +90,34 @@ export default {
hideTagArea: false hideTagArea: false
} }
}, },
computed: {
entityTypeName () {
const type = this.entity.entityType
let entityTypeName = '-'
switch (type) {
case ('ip'): {
entityTypeName = 'IP'
break
}
case ('domain'): {
entityTypeName = this.$t('overall.domain')
break
}
case ('app'): {
entityTypeName = 'APP'
break
}
default:
break
}
return entityTypeName
}
},
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 +138,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(() => {
@@ -258,7 +282,7 @@ export default {
icon: 'cn-icon cn-icon-graph', icon: 'cn-icon cn-icon-graph',
label: i18n.global.t('entities.graph'), label: i18n.global.t('entities.graph'),
url: resolvePath({ url: resolvePath({
path: '/entityGraph', path: '/entity/graph',
query: { query: {
entityType: props.entity.entityType, entityType: props.entity.entityType,
entityName: props.entity.entityName entityName: props.entity.entityName

View File

@@ -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"
@@ -123,12 +124,12 @@ export default {
const rangeParam = query.range const rangeParam = query.range
const startTimeParam = query.startTime const startTimeParam = query.startTime
const endTimeParam = query.endTime const endTimeParam = query.endTime
// 若url携带了使用携带的值否则使用默认值。
const dateRangeValue = rangeParam ? parseInt(query.range) : 60 // 优先级url > config.js > 默认值。
const dateRangeValue = rangeParam ? parseInt(rangeParam) : (DEFAULT_TIME_FILTER_RANGE.entity.trafficLine || 60)
const timeFilter = ref({ dateRangeValue }) const timeFilter = ref({ dateRangeValue })
if (!startTimeParam || !endTimeParam) { if (!startTimeParam || !endTimeParam) {
const { startTime, endTime } = getNowTime(60) const { startTime, endTime } = getNowTime(dateRangeValue)
timeFilter.value.startTime = startTime timeFilter.value.startTime = startTime
timeFilter.value.endTime = endTime timeFilter.value.endTime = endTime
} else { } else {
@@ -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

@@ -15,11 +15,12 @@
</el-tag> </el-tag>
</template> </template>
<information-aggregation v-if="tab.name === entityDetailTabsName.informationAggregation && tab.name === activeTab" @toggleLoading="setLoading" :entity="entity" @checkTag="setTag"></information-aggregation> <information-aggregation v-if="tab.name === entityDetailTabsName.informationAggregation && tab.name === activeTab" @toggleLoading="setLoading" :entity="entity" @checkTag="setTag"></information-aggregation>
<domain-name-resolution v-else-if="tab.name === entityDetailTabsName.relatedEntity && tab.name === activeTab" @toggleLoading="setLoading" :entity="entity" :timeFilter="oneDayTimeFilter" @checkTag="setTag"></domain-name-resolution> <domain-name-resolution v-else-if="tab.name === entityDetailTabsName.relatedEntity && tab.name === activeTab" @toggleLoading="setLoading" :entity="entity" @checkTag="setTag"></domain-name-resolution>
<digital-certificate v-else-if="tab.name === entityDetailTabsName.digitalCertificate && tab.name === activeTab" @toggleLoading="setLoading" :timeFilter="oneDayTimeFilter" @checkTag="setTag" /> <digital-certificate v-else-if="tab.name === entityDetailTabsName.digitalCertificate && tab.name === activeTab" @toggleLoading="setLoading" @checkTag="setTag" />
<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" :entity="entity" @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" :entity="entity" @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" @checkTag="setTag"></open-port>
<behavior-pattern v-else-if="tab.name === entityDetailTabsName.behaviorPattern && tab.name === activeTab" @toggleLoading="setLoading" :entity="entity" @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,
@@ -54,12 +58,7 @@ export default {
}, },
data () { data () {
return { return {
timer: null, timer: null
// 最近一天的时间
oneDayTimeFilter: {
startTime: window.$dayJs.tz().valueOf() - 1440 * 60 * 1000,
endTime: window.$dayJs.tz().valueOf()
}
} }
}, },
watch: { watch: {
@@ -90,6 +89,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()
@@ -107,22 +109,17 @@ export default {
}, },
methods: { methods: {
initData () { initData () {
const params = {
resource: this.entity.entityName
// startTime: getSecond(this.oneDayTimeFilter.startTime),
// endTime: getSecond(this.oneDayTimeFilter.endTime)
}
const url = this.getUrlByEntityType(this.entity.entityType) const url = this.getUrlByEntityType(this.entity.entityType)
const informationAggregation = axios.get(`${api.entity.informationAggregation}/${this.entity.entityType}?resource=${this.entity.entityName}&pageSize=100&pageNo=1`, { params: params }) const informationAggregation = axios.get(`${api.entity.informationAggregation}/${this.entity.entityType}?resource=${this.entity.entityName}&pageSize=100&pageNo=1`, { params: this.getParamsByTabType(entityDetailTabsName.informationAggregation) })
const openPort = axios.get(url, { params: params }) const openPort = axios.get(url, { params: this.getParamsByTabType(entityDetailTabsName.openPort) })
// const security = axios.get(`${api.entity.security}/${this.entity.entityType}`, { params: params }) const security = axios.get(`${api.entity.security}/${this.entity.entityType}`, { params: this.getParamsByTabType(entityDetailTabsName.securityEvent) })
// const performance = axios.get(`${api.entity.performance}/${this.entityType}`, { params: params }) const performance = axios.get(`${api.entity.performance}/${this.entityType}`, { params: this.getParamsByTabType(entityDetailTabsName.performanceEvent) })
Promise.all([informationAggregation, openPort]).then(response => { Promise.allSettled([informationAggregation, openPort, security, performance]).then(response => {
if (response[0].status === 200) { const informationAggregationResponse = response[0].value
if (informationAggregationResponse.status === 200) {
const list = [] const list = []
response[0].data.data.result.forEach(r => { informationAggregationResponse.data.data.result.forEach(r => {
Object.keys(r).forEach(k => { Object.keys(r).forEach(k => {
const aggregation = { const aggregation = {
createTime: r[k].createTime, createTime: r[k].createTime,
@@ -136,7 +133,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 })
} }
}) })
} }
@@ -145,39 +142,42 @@ export default {
} }
}) })
}) })
this.initSetTag(entityDetailTabsName.informationAggregation, list.length) this.initSetTag(entityDetailTabsName.informationAggregation, list.length)
} }
if (response[1].status === 200) { const openPortResponse = response[1].value
this.initSetTag(entityDetailTabsName.openPort, response[1].data.data.result.length) if (openPortResponse.status === 200) {
this.initSetTag(entityDetailTabsName.openPort, openPortResponse.data.data.result.length)
} }
// if (response[2].status === 200) { const securityResponse = response[2].value
// this.initSetTag(entityDetailTabsName.securityEvent, response[2].data.data.result.length) if (securityResponse.status === 200) {
this.initSetTag(entityDetailTabsName.securityEvent, securityResponse.data.data.result.length)
}
// let performanceResponse = response[3].value
// if (performanceResponse.status === 200) {
// this.initSetTag(entityDetailTabsName.performanceEvent, performanceResponse.data.data.result.length)
// } // }
// if (response[3].status === 200) {
// this.initSetTag(entityDetailTabsName.performanceEvent, response[3].data.data.result.length)
// }
this.initSetTag(entityDetailTabsName.securityEvent, 0)
this.initSetTag(entityDetailTabsName.performanceEvent, 0) this.initSetTag(entityDetailTabsName.performanceEvent, 0)
}) })
const relatedEntityParams = this.getParamsByTabType(entityDetailTabsName.relatedEntity)
// 域名解析 // 域名解析
if (this.entity.entityType === 'app') { if (this.entity.entityType === 'app') {
const ipsOfApp = axios.get(api.entity.domainNameResolutionAboutIpsOfApp, { params: params }) const ipsOfApp = axios.get(api.entity.domainNameResolutionAboutIpsOfApp, { params: relatedEntityParams })
const domainsOfApp = axios.get(api.entity.domainNameResolutionAboutDomainsOfApp, { params: params }) const domainsOfApp = axios.get(api.entity.domainNameResolutionAboutDomainsOfApp, { params: relatedEntityParams })
this.promiseData(ipsOfApp, domainsOfApp) this.promiseData(ipsOfApp, domainsOfApp)
} }
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: relatedEntityParams })
const domainsOfIp = axios.get(api.entity.domainNameResolutionAboutDomainsOfIp, { params: params }) const domainsOfIp = axios.get(api.entity.domainNameResolutionAboutDomainsOfIp, { params: relatedEntityParams })
this.promiseData(appsOfIp, domainsOfIp) const behaviorPattern = axios.get(api.entity.behaviorPattern, { params: relatedEntityParams })
this.promiseData(appsOfIp, domainsOfIp, behaviorPattern)
} }
if (this.entity.entityType === 'domain') { if (this.entity.entityType === 'domain') {
const appsOfDomain = axios.get(api.entity.domainNameResolutionAboutAppsOfDomain, { params: params }) const appsOfDomain = axios.get(api.entity.domainNameResolutionAboutAppsOfDomain, { params: relatedEntityParams })
const ipsOfDomain = axios.get(api.entity.domainNameResolutionAboutIpsOfDomain, { params: params }) const ipsOfDomain = axios.get(api.entity.domainNameResolutionAboutIpsOfDomain, { params: relatedEntityParams })
const fqdnsOfDomain = axios.get(api.entity.domainNameResolutionAboutFQDNsOfDomain, { params: params }) const fqdnsOfDomain = axios.get(api.entity.domainNameResolutionAboutFQDNsOfDomain, { params: relatedEntityParams })
this.promiseData(appsOfDomain, ipsOfDomain, fqdnsOfDomain) this.promiseData(appsOfDomain, ipsOfDomain, fqdnsOfDomain)
} }
}, },
@@ -191,6 +191,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 +263,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,190 @@
<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" >{{ valueToRangeValue(data.value, unitTypes.number).join('')}}</div>
<div class="legend-percent">{{ valueToRangeValue(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, getNowTime } from '@/utils/date-util'
import * as echarts from 'echarts'
import { pieChartOption4 } from '@/views/charts2/charts/options/echartOption'
import { shallowRef, ref } from 'vue'
import { entityDetailTabsName, chartColorForBehaviorPattern, unitTypes } from '@/utils/constants'
import { valueToRangeValue } 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
// range取 config.js 中配置的值
const dateRangeValue = DEFAULT_TIME_FILTER_RANGE.entity.behaviorPattern
const timeFilter = ref({ dateRangeValue })
const { startTime, endTime } = getNowTime(dateRangeValue)
timeFilter.value.startTime = startTime
timeFilter.value.endTime = endTime
return {
entityType,
entityName,
myChart: shallowRef(null),
chartColorForBehaviorPattern,
unitTypes,
timeFilter
}
},
async mounted () {
await this.initData()
this.toggleLoading(true)
const timer = setTimeout(() => {
this.toggleLoading(false)
clearInterval(timer)
}, 200)
},
methods: {
valueToRangeValue,
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 = this.getParams()
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 = []
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 && key !== 'total') {
this.tableData.push({
name: key,
value: value,
percent: dataObject.total ? value / dataObject.total : '-'
})
}
})
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

@@ -70,15 +70,15 @@
<script> <script>
import axios from 'axios' import axios from 'axios'
import { api } from '@/utils/api' import { api } from '@/utils/api'
import { getNowTime } from '@/utils/date-util'
import chartMixin from '@/views/charts2/chart-mixin' import chartMixin from '@/views/charts2/chart-mixin'
import chartNoData from '@/views/charts/charts/ChartNoData' import chartNoData from '@/views/charts/charts/ChartNoData'
import { entityDetailTabsName } from '@/utils/constants' import { entityDetailTabsName } from '@/utils/constants'
import { ref } from 'vue'
export default { export default {
name: 'DomainNameResolution', name: 'DomainNameResolution',
mixins: [chartMixin], mixins: [chartMixin],
props: {
},
components: { components: {
chartNoData chartNoData
}, },
@@ -105,16 +105,24 @@ export default {
errorMsg2: '' errorMsg2: ''
} }
}, },
setup (props) {
// range取 config.js 中配置的值
const dateRangeValue = DEFAULT_TIME_FILTER_RANGE.entity.relatedEntity
const timeFilter = ref({ dateRangeValue })
const { startTime, endTime } = getNowTime(dateRangeValue)
timeFilter.value.startTime = startTime
timeFilter.value.endTime = endTime
return {
timeFilter
}
},
mounted () { mounted () {
this.initData() this.initData()
}, },
methods: { methods: {
initData () { initData () {
const params = { const params = this.getParams()
resource: this.entity.entityName
// startTime: getSecond(this.timeFilter.startTime),
// endTime: getSecond(this.timeFilter.endTime)
}
if (this.entity.entityType === 'app') { if (this.entity.entityType === 'app') {
const ipsOfApp = axios.get(api.entity.domainNameResolutionAboutIpsOfApp, { params: params }) const ipsOfApp = axios.get(api.entity.domainNameResolutionAboutIpsOfApp, { params: params })
const domainsOfApp = axios.get(api.entity.domainNameResolutionAboutDomainsOfApp, { params: params }) const domainsOfApp = axios.get(api.entity.domainNameResolutionAboutDomainsOfApp, { params: params })

View File

@@ -71,9 +71,10 @@
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, getNowTime } from '@/utils/date-util'
import chartNoData from '@/views/charts/charts/ChartNoData' import chartNoData from '@/views/charts/charts/ChartNoData'
import { ref } from 'vue'
export default { export default {
name: 'InformationAggregation', name: 'InformationAggregation',
@@ -83,6 +84,18 @@ export default {
loading: true loading: true
} }
}, },
setup (props) {
// range取 config.js 中配置的值
const dateRangeValue = DEFAULT_TIME_FILTER_RANGE.entity.informationAggregation
const timeFilter = ref({ dateRangeValue })
const { startTime, endTime } = getNowTime(dateRangeValue)
timeFilter.value.startTime = startTime
timeFilter.value.endTime = endTime
return {
timeFilter
}
},
mixins: [chartMixin], mixins: [chartMixin],
components: { chartNoData }, components: { chartNoData },
methods: { methods: {
@@ -95,7 +108,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
} }
@@ -108,7 +121,12 @@ export default {
this.showError = false this.showError = false
this.toggleLoading(true) this.toggleLoading(true)
this.informationAggregationList = [] this.informationAggregationList = []
axios.get(`${api.entity.informationAggregation}/${this.entity.entityType}?resource=${this.entity.entityName}&pageSize=100&pageNo=1`).then(response => { const params = this.getParams()
let timeStr = ''
if (params.startTime && params.endTime) {
timeStr = '&startTime=' + params.startTime + '&endTime=' + params.endTime
}
axios.get(`${api.entity.informationAggregation}/${this.entity.entityType}?resource=${this.entity.entityName}&pageSize=100&pageNo=1${timeStr}`).then(response => {
const res = response.data const res = response.data
if (response.status === 200) { if (response.status === 200) {
// this.isNoData = res.data.result.length === 0 // this.isNoData = res.data.result.length === 0

View File

@@ -7,7 +7,7 @@
<div class="type-data"> <div class="type-data">
<div class="type-title"> <div class="type-title">
<span class="title-mark"></span> <span class="title-mark"></span>
<span class="type-title-word">{{ $t('entities.tab.currentDevelopmentPortsAndServices') }}</span>({{ openPortList.length }}) <span class="type-title-word">{{ $t('entities.tab.currentOpenPortsAndServices') }}</span>({{ openPortList.length }})
</div> </div>
<div class="type-content"> <div class="type-content">
<div v-for="(openPort, index) in openPortList.slice(0,showOpenPortListInfo.num)" :key="index" class="data-item"> <div v-for="(openPort, index) in openPortList.slice(0,showOpenPortListInfo.num)" :key="index" class="data-item">
@@ -27,6 +27,8 @@ import chartMixin from '@/views/charts2/chart-mixin'
import { api } from '@/utils/api' import { api } from '@/utils/api'
import chartNoData from '@/views/charts/charts/ChartNoData' import chartNoData from '@/views/charts/charts/ChartNoData'
import { entityDetailTabsName } from '@/utils/constants' import { entityDetailTabsName } from '@/utils/constants'
import { ref } from 'vue'
import { getNowTime } from '@/utils/date-util'
export default { export default {
name: 'OpenPort', name: 'OpenPort',
@@ -50,16 +52,21 @@ export default {
mounted () { mounted () {
this.initData() this.initData()
}, },
setup () { setup (props) {
// range取 config.js 中配置的值
const dateRangeValue = DEFAULT_TIME_FILTER_RANGE.entity.openPort
const timeFilter = ref({ dateRangeValue })
const { startTime, endTime } = getNowTime(dateRangeValue)
timeFilter.value.startTime = startTime
timeFilter.value.endTime = endTime
return {
timeFilter
}
}, },
methods: { methods: {
initData () { initData () {
const params = { const params = this.getParams()
resource: this.entity.entityName
// startTime: getSecond(this.timeFilter.startTime),
// endTime: getSecond(this.timeFilter.endTime)
}
this.toggleLoading(true) this.toggleLoading(true)
const url = this.getUrlByEntityType(this.entity.entityType) const url = this.getUrlByEntityType(this.entity.entityType)
axios.get(url, { params: params }).then(response => { axios.get(url, { params: params }).then(response => {

View File

@@ -26,7 +26,7 @@
<div class="basic-info__item" v-if="item.eventSeverity"> <div class="basic-info__item" v-if="item.eventSeverity">
<i class="cn-icon cn-icon-severity-level"></i> <i class="cn-icon cn-icon-severity-level"></i>
<span>{{ $t('network.severity') }}&nbsp;:&nbsp;&nbsp;</span> <span>{{ $t('network.severity') }}&nbsp;:&nbsp;&nbsp;</span>
<span :test-id="`severity${index}`">{{ toUpperCaseByString(item.eventSeverity) || '-' }}</span> <span :test-id="`severity${index}`">{{ changeSecurity(item.eventSeverity) }}</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>
@@ -50,8 +50,8 @@
</template> </template>
<script> <script>
import { dateFormatByAppearance } from '@/utils/date-util' import { dateFormatByAppearance, getNowTime } from '@/utils/date-util'
import { eventSeverityColor, entityDetailTabsName } from '@/utils/constants' import { eventSeverityColor, entityDetailTabsName, securityLevel } from '@/utils/constants'
import unitConvert from '@/utils/unit-convert' import unitConvert from '@/utils/unit-convert'
import axios from 'axios' import axios from 'axios'
import { api } from '@/utils/api' import { api } from '@/utils/api'
@@ -60,6 +60,7 @@ import chartMixin from '@/views/charts2/chart-mixin'
import ChartError from '@/components/common/Error' import ChartError from '@/components/common/Error'
import { toUpperCaseByString } from '@/utils/tools' import { toUpperCaseByString } from '@/utils/tools'
import ChartNoData from '@/views/charts/charts/ChartNoData' import ChartNoData from '@/views/charts/charts/ChartNoData'
import { ref } from 'vue'
export default { export default {
name: 'PerformanceEvent', name: 'PerformanceEvent',
@@ -77,33 +78,36 @@ export default {
const { query } = useRoute() const { query } = useRoute()
const entityType = query.entityType const entityType = query.entityType
const entityName = query.entityName const entityName = query.entityName
// range取 config.js 中配置的值
const dateRangeValue = DEFAULT_TIME_FILTER_RANGE.entity.performanceEvent
const timeFilter = ref({ dateRangeValue })
const { startTime, endTime } = getNowTime(dateRangeValue)
timeFilter.value.startTime = startTime
timeFilter.value.endTime = endTime
return { return {
entityType, entityType,
entityName entityName,
timeFilter
} }
}, },
mounted () { mounted () {
// this.initData() this.initData()
/*
this.isNoData = true this.isNoData = true
this.$emit('checkTag', entityDetailTabsName.performanceEvent, 0) this.$emit('checkTag', entityDetailTabsName.performanceEvent, 0)
this.toggleLoading(true) this.toggleLoading(true)
const timer = setTimeout(() => { const timer = setTimeout(() => {
this.toggleLoading(false) this.toggleLoading(false)
clearInterval(timer) clearInterval(timer)
}, 200) }, 200) */
}, },
methods: { methods: {
unitConvert, unitConvert,
toUpperCaseByString, toUpperCaseByString,
dateFormatByAppearance, dateFormatByAppearance,
initData () { initData () {
const params = { const params = this.getParams()
resource: this.entityName
// startTime: getSecond(this.timeFilter.startTime),
// endTime: getSecond(this.timeFilter.endTime)
}
this.toggleLoading(true) this.toggleLoading(true)
axios.get(`${api.entity.performance}/${this.entityType}`, { params: params }).then(response => { axios.get(`${api.entity.performance}/${this.entityType}`, { params: params }).then(response => {
const res = response.data const res = response.data
@@ -130,6 +134,18 @@ export default {
this.showError = true this.showError = true
this.errorMsg = this.errorMsgHandler(e) this.errorMsg = this.errorMsgHandler(e)
this.$emit('checkTag', entityDetailTabsName.performanceEvent, 0) this.$emit('checkTag', entityDetailTabsName.performanceEvent, 0)
},
changeSecurity (value) {
if (value) {
const obj = securityLevel.find(d => d.value === value)
let label = value
if (obj) {
label = this.$t(obj.label)
}
return label
} else {
return '-'
}
} }
} }
} }

View File

@@ -9,7 +9,7 @@
:key="item.eventId"> :key="item.eventId">
<div class="cn-detection--list"> <div class="cn-detection--list">
<div class="cn-detection__case entity-detail-security"> <div class="cn-detection__case entity-detail-security">
<div class="cn-detection__icon" :style="`background-color: ${eventSeverityColor[item.eventSecurity]}`"></div> <div class="cn-detection__icon"></div>
<div class="cn-detection__row"> <div class="cn-detection__row">
<div class="cn-detection__header"> <div class="cn-detection__header">
<span <span
@@ -17,7 +17,7 @@
class="detection-event-severity-color-block" class="detection-event-severity-color-block"
:style="`background-color: ${eventSeverityColor[item.eventSeverity]}`"> :style="`background-color: ${eventSeverityColor[item.eventSeverity]}`">
</span> </span>
<span class="detection-event-severity-block">{{ toUpperCaseByString(item.securityType) || '-' }}</span> <span class="detection-event-severity-block">{{ item.eventName || '-' }}</span>
<i class="cn-icon cn-icon-attacker"></i> <i class="cn-icon cn-icon-attacker"></i>
<span :test-id="`offender-ip${index}`">{{ item.offenderIp || '-' }}</span> <span :test-id="`offender-ip${index}`">{{ item.offenderIp || '-' }}</span>
<div class="domain">{{ item.offenderDomain }}</div> <div class="domain">{{ item.offenderDomain }}</div>
@@ -38,7 +38,7 @@
<div class="basic-info__item" v-if="item.eventSeverity"> <div class="basic-info__item" v-if="item.eventSeverity">
<i class="cn-icon cn-icon-severity-level"></i> <i class="cn-icon cn-icon-severity-level"></i>
<span>{{ $t('network.severity') }}&nbsp;:&nbsp;&nbsp;</span> <span>{{ $t('network.severity') }}&nbsp;:&nbsp;&nbsp;</span>
<span>{{ toUpperCaseByString(item.eventSeverity) || '-' }}</span> <span>{{ changeSecurity(item.eventSeverity) }}</span>
</div> </div>
<div class="basic-info__item" v-if="item.eventType"> <div class="basic-info__item" v-if="item.eventType">
<i class="cn-icon cn-icon-event-type"></i> <i class="cn-icon cn-icon-event-type"></i>
@@ -76,8 +76,8 @@
</template> </template>
<script> <script>
import { dateFormatByAppearance } from '@/utils/date-util' import { dateFormatByAppearance, getNowTime } from '@/utils/date-util'
import { eventSeverityColor, entityDetailTabsName } from '@/utils/constants' import { eventSeverityColor, entityDetailTabsName, securityLevel } from '@/utils/constants'
import unitConvert from '@/utils/unit-convert' import unitConvert from '@/utils/unit-convert'
import axios from 'axios' import axios from 'axios'
import { api } from '@/utils/api' import { api } from '@/utils/api'
@@ -85,6 +85,7 @@ import { useRoute } from 'vue-router'
import chartMixin from '@/views/charts2/chart-mixin' import chartMixin from '@/views/charts2/chart-mixin'
import { toUpperCaseByString } from '@/utils/tools' import { toUpperCaseByString } from '@/utils/tools'
import chartNoData from '@/views/charts/charts/ChartNoData' import chartNoData from '@/views/charts/charts/ChartNoData'
import { ref } from 'vue'
export default { export default {
name: 'SecurityEvent', name: 'SecurityEvent',
@@ -102,33 +103,36 @@ export default {
const { query } = useRoute() const { query } = useRoute()
const entityType = query.entityType const entityType = query.entityType
const entityName = query.entityName const entityName = query.entityName
// range取 config.js 中配置的值
const dateRangeValue = DEFAULT_TIME_FILTER_RANGE.entity.securityEvent
const timeFilter = ref({ dateRangeValue })
const { startTime, endTime } = getNowTime(dateRangeValue)
timeFilter.value.startTime = startTime
timeFilter.value.endTime = endTime
return { return {
entityType, entityType,
entityName entityName,
timeFilter
} }
}, },
mounted () { mounted () {
// this.initData() this.initData()
/*
this.isNoData = true this.isNoData = true
this.$emit('checkTag', entityDetailTabsName.securityEvent, 0) this.$emit('checkTag', entityDetailTabsName.securityEvent, 0)
this.toggleLoading(true) this.toggleLoading(true)
const timer = setTimeout(() => { const timer = setTimeout(() => {
this.toggleLoading(false) this.toggleLoading(false)
clearInterval(timer) clearInterval(timer)
}, 200) }, 200) */
}, },
methods: { methods: {
unitConvert, unitConvert,
toUpperCaseByString, toUpperCaseByString,
dateFormatByAppearance, dateFormatByAppearance,
initData () { initData () {
const params = { const params = this.getParams()
resource: this.entityName
// startTime: getSecond(this.timeFilter.startTime),
// endTime: getSecond(this.timeFilter.endTime)
}
this.toggleLoading(true) this.toggleLoading(true)
axios.get(`${api.entity.security}/${this.entityType}`, { params: params }).then(response => { axios.get(`${api.entity.security}/${this.entityType}`, { params: params }).then(response => {
const res = response.data const res = response.data
@@ -155,6 +159,18 @@ export default {
this.isNoData = false this.isNoData = false
this.showError = true this.showError = true
this.errorMsg = this.errorMsgHandler(e) this.errorMsg = this.errorMsgHandler(e)
},
changeSecurity (value) {
if (value) {
const obj = securityLevel.find(d => d.value === value)
let label = value
if (obj) {
label = this.$t(obj.label)
}
return label
} else {
return '-'
}
} }
} }
} }

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

@@ -12,7 +12,7 @@
import chartMixin from '@/views/charts2/chart-mixin' import chartMixin from '@/views/charts2/chart-mixin'
import { getSecond } from '@/utils/date-util' import { getSecond } from '@/utils/date-util'
import { api } from '@/utils/api' import { api } from '@/utils/api'
import { storageKey } from '@/utils/constants' import { storageKey, ZH } from '@/utils/constants'
import PopoverContent from './LinkDirectionGrid/PopoverContent' import PopoverContent from './LinkDirectionGrid/PopoverContent'
import { computeScore } from '@/utils/tools' import { computeScore } from '@/utils/tools'
import axios from 'axios' import axios from 'axios'
@@ -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
@@ -137,11 +156,9 @@ export default {
}) })
// 一行如果无数据则删除该行默认10*10矩阵 // 一行如果无数据则删除该行默认10*10矩阵
const rowXIndex = 0 this.handleXRowNoData(linkGridData, 0)
this.handleXRowNoData(linkGridData, rowXIndex)
// 一列如果无数据则删除该列默认10*10矩阵 // 一列如果无数据则删除该列默认10*10矩阵
const rowYIndex = 0 this.handleYRowNoData(linkGridData, 0)
this.handleYRowNoData(linkGridData, rowYIndex)
this.isLinkNoData = linkGridData.length === 0 this.isLinkNoData = linkGridData.length === 0
this.linkGridData = linkGridData this.linkGridData = linkGridData
} }
@@ -159,9 +176,9 @@ export default {
// 接口数据乱序,根据入方向排序,再根据同个入方向下的出方向进行排序 // 接口数据乱序,根据入方向排序,再根据同个入方向下的出方向进行排序
nextLinkData.sort((a, b) => { nextLinkData.sort((a, b) => {
if (a.inLinkDirection !== b.inLinkDirection) { if (a.inLinkDirection !== b.inLinkDirection) {
return a.inLinkDirection.localeCompare(b.inLinkDirection, 'zh') return a.inLinkDirection.localeCompare(b.inLinkDirection, ZH)
} }
return a.outLinkDirection.localeCompare(b.outLinkDirection, 'zh') return a.outLinkDirection.localeCompare(b.outLinkDirection, ZH)
}) })
this.isNextNoData = nextLinkData.length === 0 this.isNextNoData = nextLinkData.length === 0
@@ -210,9 +227,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
@@ -238,11 +255,9 @@ export default {
}) })
// 一行如果无数据则删除该行默认3*3矩阵 // 一行如果无数据则删除该行默认3*3矩阵
const rowXIndex = 0 this.handleXRowNoData(nextGridData, 0)
this.handleXRowNoData(nextGridData, rowXIndex)
// 一列如果无数据则删除该列默认3*3矩阵 // 一列如果无数据则删除该列默认3*3矩阵
const rowYIndex = 0 this.handleYRowNoData(nextGridData, 0)
this.handleYRowNoData(nextGridData, rowYIndex)
this.isNextNoData = nextGridData.length === 0 this.isNextNoData = nextGridData.length === 0
this.nextGridData = nextGridData this.nextGridData = nextGridData
@@ -254,6 +269,7 @@ export default {
} }
} }
}).finally(() => { }).finally(() => {
this.scoreDataState = true
this.toggleLoading(false) this.toggleLoading(false)
}) })
}, },
@@ -282,6 +298,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
@@ -320,7 +353,7 @@ export default {
* @param index * @param index
*/ */
handleXRowNoData (data, index) { handleXRowNoData (data, index) {
if (data) { if (data && data.length > 0) {
const item = data[index] const item = data[index]
let tempList = [] let tempList = []
if (item) { if (item) {
@@ -343,7 +376,7 @@ export default {
*/ */
handleYRowNoData (data, index) { handleYRowNoData (data, index) {
const rowList = [] const rowList = []
if (data) { if (data && data.length > 0) {
data.forEach(item => { data.forEach(item => {
if (item.out[index]) { if (item.out[index]) {
if (item.out[index].noData) { if (item.out[index].noData) {

View File

@@ -37,11 +37,12 @@
</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('detections.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"
@@ -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

@@ -326,7 +326,7 @@ export default {
if (tabType) { if (tabType) {
const oldCurTab = this.getUrlParam(this.curTabState.networkOverviewBeforeTab, '') const oldCurTab = this.getUrlParam(this.curTabState.networkOverviewBeforeTab, '')
const curTable = networkTable.networkOverview const curTable = networkTable.networkOverview
const tableType = this.$route.params ? this.$route.params.typeName : 'networkOverview' const tableType = this.$route.path.replace('/panel/', '') || 'networkOverview'
const metric = this.getUrlParam(this.curTabState.tableMetric, 'Bits/s') const metric = this.getUrlParam(this.curTabState.tableMetric, 'Bits/s')
const list = await getUserDrilldownTableConfig(tableType, metric) const list = await getUserDrilldownTableConfig(tableType, metric)
const tabGroup = list.filter(item => item.label === tabType) const tabGroup = list.filter(item => item.label === tabType)
@@ -493,11 +493,9 @@ export default {
} }
}) })
}) })
if (val) { if (!show) { // 非滚动滚动条操作,直接覆盖之前的数据
this.providerOptions = res.data.list this.providerOptions = res.data.list
} else if (!val && !show) { } else { // 滚动条操作,则将新数据和之前的数据组合
this.providerOptions = res.data.list
} else {
this.providerOptions.push(...res.data.list) this.providerOptions.push(...res.data.list)
this.appListData([], this.providerOptions) this.appListData([], this.providerOptions)
} }
@@ -519,11 +517,9 @@ export default {
} }
}) })
}) })
if (val) { if (!show) { // 非滚动滚动条操作,直接覆盖之前的数据
this.appOptions = res.data.list this.appOptions = res.data.list
} else if (!val && !show) { } else { // 滚动条操作,则将新数据和之前的数据组合
this.appOptions = res.data.list
} else {
this.appOptions.push(...res.data.list) this.appOptions.push(...res.data.list)
this.appListData(this.appOptions, []) this.appListData(this.appOptions, [])
} }

View File

@@ -32,17 +32,20 @@
</div> </div>
<div class="line-select line-header-right"> <div class="line-select line-header-right">
<div class="line-select-reference-line"> <div class="line-select-reference-line">
<span>{{ $t('network.referenceLine') }}:</span> <span>{{ $t('network.referenceLine') }}&nbsp;:&nbsp;</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"
: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"
> >
<el-option v-for="item in options2" :key="item.value" :label="item.label" :value="item.value"></el-option> <el-option :key="options2[0].value" :label="$t(options2[0].label)" :value="options2[0].value"></el-option>
<el-option :key="options2[1].value" :label="$t(options2[1].label[0], options2[1].label[1])" :value="options2[1].value"></el-option>
<el-option :key="options2[2].value" :label="$t(options2[2].label)" :value="options2[2].value"></el-option>
</el-select> </el-select>
</div> </div>
</div> </div>
@@ -192,6 +195,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)
@@ -270,6 +276,14 @@ export default {
label: { label: {
formatter (params) { formatter (params) {
const arr = valueToRangeValue(params.value, unitTypes.number).join('') const arr = valueToRangeValue(params.value, unitTypes.number).join('')
const referIndex = _this.options2.findIndex(o => o.value === _this.lineRefer)
if (referIndex > -1) {
if (referIndex === 1) {
return _this.$t(_this.options2[1].label[0], _this.options2[1].label[1]) + '(' + arr + echartsData[0].unitType + ')'
} else {
return _this.$t(_this.options2[referIndex].label) + '(' + arr + echartsData[0].unitType + ')'
}
}
return _this.lineRefer + '(' + arr + echartsData[0].unitType + ')' return _this.lineRefer + '(' + arr + echartsData[0].unitType + ')'
}, },
position: 'insideStartTop', position: 'insideStartTop',
@@ -464,8 +478,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 +491,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 +526,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 +544,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 +554,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

@@ -289,7 +289,9 @@ import {
dbDrilldownTableConfig, dbDrilldownTableConfig,
fromRoute, fromRoute,
drillDownPanelTypeMapping, drillDownPanelTypeMapping,
commonErrorTip commonErrorTip,
ZH,
EN
} from '@/utils/constants' } from '@/utils/constants'
import axios from 'axios' import axios from 'axios'
import unitConvert, { valueToRangeValue } from '@/utils/unit-convert' import unitConvert, { valueToRangeValue } from '@/utils/unit-convert'
@@ -379,12 +381,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 +414,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,8 +486,8 @@ 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'
@@ -485,7 +503,7 @@ export default {
const currentValue = document.getElementById('tabSearchValue' + tabProp) ? document.getElementById('tabSearchValue' + tabProp).value : '' const currentValue = document.getElementById('tabSearchValue' + tabProp) ? document.getElementById('tabSearchValue' + tabProp).value : ''
const columnName = curTab ? curTab.label : '' const columnName = curTab ? curTab.label : ''
let type = 'ip' let type = 'ip'
const tableType = this.$route.params ? this.$route.params.typeName : 'networkOverview' const tableType = this.$route.path.replace('/panel/', '') || 'networkOverview'
const curTableInCode = networkTable[tableType] ? networkTable[tableType] : networkTable.networkOverview const curTableInCode = networkTable[tableType] ? networkTable[tableType] : networkTable.networkOverview
if (curTableInCode && curTableInCode.tabList) { if (curTableInCode && curTableInCode.tabList) {
const curTab = curTableInCode.tabList.find(item => item.label === columnName) const curTab = curTableInCode.tabList.find(item => item.label === columnName)
@@ -518,7 +536,7 @@ export default {
axios.get(url, { params }).then(async response => { axios.get(url, { params }).then(async response => {
if (response.status === 200) { if (response.status === 200) {
this.tabSearchColumnValueListShow = response.data.data.result this.tabSearchColumnValueListShow = response.data.data.result
if (this.$route.params.typeName === fromRoute.dnsServiceInsights) { if (this.$route.path.replace('/panel/', '') === fromRoute.dnsServiceInsights) {
if (this.dnsQtypeMapData.size === 0) { if (this.dnsQtypeMapData.size === 0) {
this.dnsQtypeMapData = await getDnsMapData('dnsQtype') this.dnsQtypeMapData = await getDnsMapData('dnsQtype')
} }
@@ -1038,12 +1056,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
@@ -1154,10 +1172,10 @@ export default {
// 数字可按照排序的要求进行自定义,我这边产品的要求是 // 数字可按照排序的要求进行自定义,我这边产品的要求是
// 数字0->9->大写字母A->Z->小写字母a->z->中文拼音a->z // 数字0->9->大写字母A->Z->小写字母a->z->中文拼音a->z
if (/^[\u4e00-\u9fa5]$/.test(char)) { if (/^[\u4e00-\u9fa5]$/.test(char)) {
return ['zh', 300] return [ZH, 300]
} }
if (/^[a-zA-Z]$/.test(char)) { if (/^[a-zA-Z]$/.test(char)) {
return ['en', 200] return [EN, 200]
} }
if (/^[0-9]$/.test(char)) { if (/^[0-9]$/.test(char)) {
return ['number', 100] return ['number', 100]
@@ -1206,9 +1224,9 @@ export default {
if (char1 === char2) { if (char1 === char2) {
continue continue
} else { } else {
if (char1Type[0] === 'zh') { if (char1Type[0] === ZH) {
res = char2.localeCompare(char1) res = char2.localeCompare(char1)
} else if (char1Type[0] === 'en') { } else if (char1Type[0] === EN) {
res = char2.charCodeAt(0) - char1.charCodeAt(0) res = char2.charCodeAt(0) - char1.charCodeAt(0)
} else { } else {
res = char2 - char1 res = char2 - char1
@@ -1246,9 +1264,9 @@ export default {
if (char1 === char2) { if (char1 === char2) {
continue continue
} else { } else {
if (char1Type[0] === 'zh') { if (char1Type[0] === ZH) {
res = char1.localeCompare(char2) res = char1.localeCompare(char2)
} else if (char1Type[0] === 'en') { } else if (char1Type[0] === EN) {
res = char1.charCodeAt(0) - char2.charCodeAt(0) res = char1.charCodeAt(0) - char2.charCodeAt(0)
} else { } else {
res = char1 - char2 res = char1 - char2
@@ -2223,6 +2241,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 () {
@@ -2232,10 +2262,9 @@ export default {
this.drillDownTableConfigs = null this.drillDownTableConfigs = null
this.curTable = null this.curTable = null
this.commonColumnList = null this.commonColumnList = null
this.userId = localStorage.getItem(storageKey.userId) this.userId = localStorage.getItem(storageKey.userId)
this.drillDownTableConfigs = await combineDrilldownTableWithUserConfig() this.drillDownTableConfigs = await combineDrilldownTableWithUserConfig()
this.tableType = this.$route.params ? this.$route.params.typeName : 'networkOverview' this.tableType = this.$route.path.replace('/panel/', '') || 'networkOverview'
// 是否需要dns的qtype和rcode的数据字典 // 是否需要dns的qtype和rcode的数据字典
if (this.tableType === fromRoute.dnsServiceInsights) { if (this.tableType === fromRoute.dnsServiceInsights) {
this.dnsQtypeMapData = this.$store.getters.getDnsQtypeMapData this.dnsQtypeMapData = this.$store.getters.getDnsQtypeMapData
@@ -2317,8 +2346,6 @@ export default {
await this.saveUserLocalConfig() await this.saveUserLocalConfig()
this.getChartData() this.getChartData()
}, },
setup (props) {
},
beforeUnmount () { beforeUnmount () {
// 以下元素,检测到内存并未释放 // 以下元素,检测到内存并未释放
this.unitConvert = null this.unitConvert = null

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 = []
@@ -301,7 +322,7 @@ export default {
const tabType = 'network.applicationCategories' const tabType = 'network.applicationCategories'
const oldCurTab = this.getUrlParam(this.curTabState.networkOverviewBeforeTab, '') const oldCurTab = this.getUrlParam(this.curTabState.networkOverviewBeforeTab, '')
const curTable = networkTable.networkAppPerformance const curTable = networkTable.networkAppPerformance
const tableType = this.$route.params ? this.$route.params.typeName : 'networkOverview' const tableType = this.$route.path.replace('/panel/', '') || 'networkOverview'
const metric = this.getUrlParam(this.curTabState.tableMetric, 'Bits/s') const metric = this.getUrlParam(this.curTabState.tableMetric, 'Bits/s')
const list = await getUserDrilldownTableConfig(tableType, metric) const list = await getUserDrilldownTableConfig(tableType, metric)
const tabGroup = list.filter(item => item.label === tabType) const tabGroup = list.filter(item => item.label === tabType)
@@ -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>

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