Compare commits

..

351 Commits

Author SHA1 Message Date
刘洪洪
5c4993cf12 fix: 修复report编辑按钮不会disabled的问题 2024-05-11 18:17:55 +08:00
刘洪洪
b8ee685230 fix: report一系列问题修改 2024-05-11 16:28:27 +08:00
陈劲松
30b1328576 Merge branch 'cherry-pick-3513a9c1' into 'dev-24.01-m22'
fix: 修复entity的搜索测试用例报错的问题

See merge request cyber-narrator/cn-ui!72
2024-05-11 06:49:45 +00:00
刘洪洪
740e7bbae6 fix: 修复entity的搜索测试用例报错的问题
(cherry picked from commit 3513a9c145)
2024-05-11 06:49:38 +00:00
陈劲松
f020fe33b5 Merge branch 'cherry-pick-df183c18' into 'dev-24.01-m22'
fix: 修复entity和detection搜索时随意输入字段,没有错误提示且resource为空的问题

See merge request cyber-narrator/cn-ui!71
2024-05-09 09:08:56 +00:00
刘洪洪
75b47d7ba7 fix: 修复entity和detection搜索时随意输入字段,没有错误提示且resource为空的问题
(cherry picked from commit df183c1836)
2024-05-09 09:08:48 +00:00
chenjinsong
a26b0c848d fix: 修复链路四元组图字段名未升级的问题 2024-04-30 17:06:46 +08:00
陈劲松
d2ad042cf9 Merge branch 'cherry-pick-ca6f6a02' into 'dev-24.01-m22'
CN-1651 fix: 修复链路links下钻报错问题

See merge request cyber-narrator/cn-ui!69
2024-04-28 10:31:54 +00:00
chenjinsong
a844d44d1d CN-1651 fix: 修复链路links下钻报错问题
(cherry picked from commit ca6f6a02e9)
2024-04-28 10:31:45 +00:00
chenj
ad603339de fix: 隐藏detection页的policy入口 2024-04-01 21:06:04 +08:00
陈劲松
08f0cd4522 Merge branch 'cherry-pick-e645eeb1' into 'dev-24.01'
fix: 更改license失效码

See merge request cyber-narrator/cn-ui!68
2024-03-25 07:51:28 +00:00
chenjinsong
0c20757d07 fix: 更改license失效码
(cherry picked from commit e645eeb1a5)
2024-03-25 07:51:22 +00:00
陈劲松
2c7c7293b4 Merge branch 'cherry-pick-5511191c' into 'dev-24.01'
fix: license上传文件后进行页面刷新

See merge request cyber-narrator/cn-ui!67
2024-03-25 07:51:06 +00:00
hanyuxia
d035677c2f fix: license上传文件后进行页面刷新
(cherry picked from commit 5511191c90)
2024-03-25 07:50:57 +00:00
陈劲松
50cdbde8bf Merge branch 'cherry-pick-8a18fb09' into 'dev-24.01'
fix: 许可证下载文件名称从响应头中的Content-Disposition获取

See merge request cyber-narrator/cn-ui!66
2024-03-25 07:49:56 +00:00
hanyuxia
d27738b9ec fix: 许可证下载文件名称从响应头中的Content-Disposition获取
(cherry picked from commit 8a18fb09f4)
2024-03-25 07:49:49 +00:00
chenjinsong
85456c1e47 fix: 更改插件的周期信息 2024-03-20 20:08:33 +08:00
陈劲松
327cbde233 Merge branch 'cherry-pick-bcf57611' into 'dev-24.01'
feat:插件id及逻辑变动(有一些同名,但type(ip、domain)不同的,合并成一条;合并后的记录,type列既有ip标签又有domain标签)

See merge request cyber-narrator/cn-ui!65
2024-03-20 09:56:48 +00:00
hyx
cbf71eb1b9 feat:插件id及逻辑变动(有一些同名,但type(ip、domain)不同的,合并成一条;合并后的记录,type列既有ip标签又有domain标签)
(cherry picked from commit bcf57611ef)
2024-03-20 09:56:40 +00:00
chenjinsong
4e0eb3f143 fix: 修复license未认证的情况下,按回车键可以登录的问题 2024-03-19 16:03:21 +08:00
陈劲松
a17c68bb26 Merge branch 'cherry-pick-da015603' into 'dev-24.01'
fix: 修复下载license时文件名不对的问题

See merge request cyber-narrator/cn-ui!63
2024-03-19 07:46:16 +00:00
chenjinsong
d1ae513124 fix: 修复下载license时文件名不对的问题
(cherry picked from commit da0156031c)
2024-03-19 07:46:09 +00:00
陈劲松
91915996c0 Merge branch 'cherry-pick-d0b29c24' into 'dev-24.01'
feat: CN-1558 开发license页面,支持license拦截

See merge request cyber-narrator/cn-ui!62
2024-03-19 03:04:37 +00:00
hanyuxia
8f17647752 feat: CN-1558 开发license页面,支持license拦截
(cherry picked from commit d0b29c24ba)
2024-03-19 03:04:31 +00:00
陈劲松
29a319609e Merge branch 'cherry-pick-21da4f0d' into 'dev-24.01'
feat: CN-1558 开发license页面,支持license拦截

See merge request cyber-narrator/cn-ui!61
2024-03-19 03:04:10 +00:00
hanyuxia
08ae827e31 feat: CN-1558 开发license页面,支持license拦截
(cherry picked from commit 21da4f0d49)
2024-03-19 03:04:04 +00:00
陈劲松
854a846296 Merge branch 'cherry-pick-06a8bd96' into 'dev-24.01'
feat: CN-1558 开发license页面,支持license拦截

See merge request cyber-narrator/cn-ui!60
2024-03-19 03:02:37 +00:00
hanyuxia
42aa7671a3 feat: CN-1558 开发license页面,支持license拦截
(cherry picked from commit 06a8bd9626)
2024-03-19 03:02:30 +00:00
陈劲松
fb89134f95 Merge branch 'cherry-pick-4429d225' into 'dev-24.01'
feat: CN-1558 开发license页面(未完成:登陆页支持license拦截)

See merge request cyber-narrator/cn-ui!59
2024-03-19 03:01:31 +00:00
hyx
584ad8d538 feat: CN-1558 开发license页面(未完成:登陆页支持license拦截)
(cherry picked from commit 4429d225f1)
2024-03-19 03:01:23 +00:00
chenjinsong
c43a17c984 feat: 新增一个vpn 2024-01-30 10:31:37 +08:00
chenjinsong
df5decc3b4 feat: 新增一个vpn 2024-01-30 10:31:36 +08:00
刘洪洪
cca54359a8 fix: 修复schema字段变动导致模糊搜索不能识别的问题 2024-01-30 10:30:03 +08:00
chenjinsong
9e8b3e9d64 fix: vpn类的tag都标位红色 2024-01-29 14:17:36 +08:00
chenjinsong
8e5f5b49cb fix: plugin增加请求参数pageSize=-1 2024-01-29 11:05:59 +08:00
chenjinsong
4441bdef10 fix: 调整plugin页面样式 2024-01-26 17:35:01 +08:00
hyx
849df8c8d8 feat:增加9个系统插件 2024-01-25 12:29:58 +08:00
hyx
475b112835 fix: 调整npm下钻表格列 loading样式 2024-01-18 19:05:17 +08:00
hyx
b286861137 fix:CN-1546 知识库导入空csv,接口报错界面未提示 2024-01-15 18:41:42 +08:00
chenjinsong
e67ec0905a fix: 修改三个新插件的id 2024-01-15 16:26:02 +08:00
hyx
7f697658f4 feat:CN-1545 npm下钻table异步加载的列增加loading提示 2024-01-15 14:29:00 +08:00
刘洪洪
ff17e52bc2 fix: 修复新增事件策略界面返回时,第三步校验总被触发的问题 2024-01-12 17:22:25 +08:00
hanyuxia
b13e313114 feat: 增加3个插件配置 2024-01-12 15:16:04 +08:00
刘洪洪
dbf5885b72 Merge remote-tracking branch 'origin/dev' into dev 2024-01-12 11:56:33 +08:00
刘洪洪
ab10f7ff0f fix: 优化滚动加载代码,避免占用资源 2024-01-12 11:56:08 +08:00
hanyuxia
6d00137260 fix: 知识库删除操作报错 2024-01-12 11:54:23 +08:00
chenjinsong
ddafbf44f2 fix: 修复不能识别9位和12位时间戳的问题 2024-01-12 11:44:20 +08:00
hanyuxia
096666ce69 fix:实体Subscriber详情页,top应用样式调整(柱状图右侧数据显示不全) 2024-01-12 09:51:05 +08:00
hyx
bf34b4321c fix: 1.Subscribe实体详情页top 应用:数据为0时,显示<1B 2024-01-11 18:09:53 +08:00
刘洪洪
83c8f400e2 fix: 修复搜索组件tag模式下in操作多选后没有回显的问题 2024-01-11 18:06:09 +08:00
chenjinsong
cb93658be4 fix: 更改subscriber实体地图最大缩放级别为13 2024-01-11 17:28:45 +08:00
hyx
e546f39c93 fix: 1.Subscribe实体详情页top 应用样式调整 2024-01-11 17:16:27 +08:00
hyx
a3e770963f fix: 1.Subscribe实体详情页top 应用样式调整;2.插件列表展示内容过滤; 2024-01-11 16:36:07 +08:00
刘洪洪
8e4d5bff88 fix: 去除无效打印 2024-01-11 15:16:16 +08:00
刘洪洪
877c008fe7 fix: 1.修复实体详情安全事件只显示10条的问题;2.实体列表的实体关系radio初始化高亮 2024-01-11 15:12:27 +08:00
刘洪洪
fc85069227 Merge remote-tracking branch 'origin/dev' into dev 2024-01-11 14:56:17 +08:00
刘洪洪
9ac9cd93e1 fix: 更改实体列表关于实体、相关实体的选择方式 2024-01-11 14:56:06 +08:00
chenjinsong
426301fce0 fix: 更改subscriber实体地图最大缩放级别 2024-01-11 14:55:10 +08:00
chenjinsong
b8c90fee71 fix: 修复subscriber实体地图时间参数长度不对的问题 2024-01-11 11:50:10 +08:00
chenjinsong
9ae4453587 fix: 修复subscriber实体app曲线中app数量大于6时显示异常的问题 2024-01-11 11:42:35 +08:00
hyx
734bfb33d3 fix: 实体subscribe详情页的Top 应用部分不显示数据 2024-01-11 10:58:01 +08:00
刘洪洪
ada66ee9d2 Merge remote-tracking branch 'origin/dev' into dev 2024-01-11 10:18:45 +08:00
刘洪洪
facbd0f17c fix: 搜索时向地址栏添加hideRelated参数 2024-01-11 10:18:32 +08:00
chenjinsong
471e5f2342 fix: 修复实体地图请求接口未带时间参数的问题 2024-01-11 10:16:54 +08:00
hyx
ad800637fd fix: DNS下钻表格,当前tab为Qtypes或者Rcodes时,自定义中取消当前tab,重新加载的页面的第一列数据为空 2024-01-11 09:20:00 +08:00
刘洪洪
03400f1fab fix: 修复给搜索条件的=添加空格导致识别失败的问题 2024-01-10 17:22:00 +08:00
hyx
0e51d49e15 fix: 1.调整subscriber实体中tab错误提示信息的位置;2.暂时不请求subscriber实体对应的4个tab的接口,显示未noData; 2024-01-10 17:09:56 +08:00
刘洪洪
a3ab4530b7 fix: 1.新增报告时间范围添加国际化;2.detection跳转实体详情缺少参数 2024-01-10 15:49:41 +08:00
hyx
a8ecdd48bd fix: 删除subscriber模块相关的测试内容 2024-01-10 14:54:14 +08:00
刘洪洪
6bdefa5497 fix: 修复搜索组件高度有滚动条的问题 2024-01-09 17:06:00 +08:00
刘洪洪
e0a8815d4a fix: 修复实体切换时间搜索高亮不生效的问题 2024-01-09 11:19:44 +08:00
hanyuxia
f4bf9a1f31 feat:CN-1522 插件管理界面与接口调试 2024-01-09 11:10:28 +08:00
刘洪洪
2661b5e69d CN-1540 fix: 更新获取tag字段名pluginName为vpnServiceName 2024-01-08 16:42:41 +08:00
刘洪洪
0e13925908 fix: 修复隐藏相关实体按钮错误的问题 2024-01-05 17:17:17 +08:00
刘洪洪
2219bba781 fix: 修复实体详情tab单测实例的tag未经过映射转换导致报错的问题 2024-01-04 16:54:20 +08:00
刘洪洪
80027fbd46 CN-1540 fix: 提取Tag展示逻辑公共部分 2024-01-04 16:39:39 +08:00
刘洪洪
8b81181ae2 CN-1540 fix: 实体智能学习类型的Tag展示逻辑更改 2024-01-03 17:45:52 +08:00
刘洪洪
a9215469fb fix: 调整实体详情信誉等级tag字体居中 2024-01-02 17:52:21 +08:00
hanyuxia
467b2c27b7 feat:CN-1522 插件管理界面与接口调试 2023-12-29 18:04:50 +08:00
hanyuxia
ca3b9d926c fix: 详情实体behavior pattern标签顶部数值计算逻辑修改(去掉key为total的内容) 2023-12-29 17:59:55 +08:00
hanyuxia
160f708596 feat:CN-1522 插件管理页开发 2023-12-29 17:06:14 +08:00
刘洪洪
3d3ad53395 fix: 实体详情tab的安全、性能tab的持续时间字段修改 2023-12-29 15:09:06 +08:00
刘洪洪
c874665397 fix: detection顶部柱状图位置调整 2023-12-29 14:13:54 +08:00
刘洪洪
f40e9ae759 fix: 修复搜索组件的历史搜索中同一条搜索语句点击第二次不能填充搜索框的问题 2023-12-29 11:57:45 +08:00
刘洪洪
1f33834146 fix: 完善npm--location的Throughput曲线图的legend初始化 2023-12-29 11:23:20 +08:00
刘洪洪
cbb27e2538 fix: npm曲线图去除重复功能接口调用 2023-12-29 09:50:50 +08:00
hanyuxia
1d5bd39f54 feat:CN-1522 删除内置知识库列表的“智能情报学习”栏 2023-12-29 09:40:42 +08:00
刘洪洪
2e113df17b fix: 1、修复detection顶部柱状图无数据的地方鼠标移上去会显示上一个或下一个柱子的数据;2、x轴显示的时间并非时间选择器的起始时间。 2023-12-28 17:27:02 +08:00
刘洪洪
ec0b3b28d4 fix: 将实体详情的信誉等级与detection的实体信誉等级进行样式统一 2023-12-28 10:38:21 +08:00
hyx
83971fa08b feat:CN-1522 删除内置知识库列表的“智能情报学习”栏 2023-12-28 09:23:00 +08:00
刘洪洪
fc24f6bbfb fix: 修复实体详情的安全事件tab时间显示异常的问题 2023-12-27 18:10:25 +08:00
刘洪洪
d7bd49a9ac fix: 去除未被使用的文件引用,以及调整部分代码格式 2023-12-27 16:08:43 +08:00
hyx
37238377d2 Merge remote-tracking branch 'origin/dev' into dev 2023-12-27 16:05:50 +08:00
hyx
b42bfded33 feat:CN-1522 插件管理页开发 2023-12-27 16:05:35 +08:00
刘洪洪
50e4112181 fix: 调整搜索组件输入过长会换行的样式,以及error提示的层级 2023-12-27 14:20:06 +08:00
hanyuxia
70ab0a46e6 feat:CN-1522 插件管理页开发(细节需要再调整),及代码整理 2023-12-26 18:24:05 +08:00
chenjinsong
cb6d0e4765 fix: 调整link测试用例 2023-12-26 15:40:37 +08:00
chenjinsong
15b90c11e9 CN-1519 fix: 链路配置更改;链路组件逻辑更改; 2023-12-25 18:35:14 +08:00
hyx
3f97c3e97f Merge remote-tracking branch 'origin/dev' into dev 2023-12-25 16:29:01 +08:00
hyx
e70fa5d569 fix: 下钻表格测试用例报错修改 2023-12-25 16:28:46 +08:00
刘洪洪
5ba4d8a1fe CN-1520 fix: 调整实体列表页接口请求方式 2023-12-25 16:27:45 +08:00
hyx
4ae22a1ea0 fix: 各模块流量曲线图测试用例修改 2023-12-25 14:47:48 +08:00
刘洪洪
e08a7a2434 fix: 修复jest单测获取codemirror依赖失败的问题 2023-12-25 14:32:22 +08:00
刘洪洪
2dbf5ee84e fix: 修复jest单测获取不到codemirror依赖的问题 2023-12-25 14:24:28 +08:00
刘洪洪
b7c5638c04 fix: 修复LinkTrafficLine单测用例报错的问题 2023-12-25 13:59:40 +08:00
刘洪洪
d1e8430e37 fix: 去除无效打印 2023-12-22 18:15:42 +08:00
刘洪洪
2028861b26 CN-1375 fix: 编写高级搜索器自动化测试用例 2023-12-22 17:59:44 +08:00
hanyuxia
21eafd4087 fix:CN-1518 Subscriber流量曲线图的交互逻辑优化(减少请求次数),及其他模块相关代码调整 2023-12-22 17:32:02 +08:00
hanyuxia
899b0e1e9d fix:CN-1518 Link Monitor流量曲线图的交互逻辑优化(减少请求次数) 2023-12-22 11:32:31 +08:00
hyx
eb7a9f875c fix: CN-1518 实体详情流量曲线图的交互逻辑优化(减少请求次数) 2023-12-21 15:59:04 +08:00
hyx
0f2f5ecdde Merge remote-tracking branch 'origin/dev' into dev 2023-12-20 20:40:01 +08:00
hyx
164089f99e fix: CN-1518 Npm流量曲线图的交互逻辑优化(减少请求次数) 2023-12-20 20:39:45 +08:00
刘洪洪
abab03eb12 fix: 1、搜索组件有关枚举的添加国际化;2、修复tag模式下切换key,value还保留上次选择的问题;3、修复切换语言环境,搜索参数包含其他语言导致不能识别转换的问题 2023-12-20 18:38:38 +08:00
hyx
19160c0da1 fix: CN-1518 DNS流量曲线图的交互逻辑优化(减少请求次数) 2023-12-20 10:41:43 +08:00
hanyuxia
56ad79bd0d fix:CN-1518 Network Overview流量曲线图的交互逻辑优化,减少请求次数 2023-12-19 13:44:14 +08:00
刘洪洪
a2a7bdcd14 fix: 调整showHint弹窗样式 2023-12-18 10:12:13 +08:00
刘洪洪
1d67584c9a fix: 调整搜索组件showHint样式和提示文本,隐藏/显示相关实体时联动左侧filter 2023-12-15 18:48:51 +08:00
刘洪洪
8b72a37489 fix: 修复domain in()和其他搜索条件一起时,识别in操作符失败的问题 2023-12-15 15:57:12 +08:00
hyx
e13c9afe78 fix: CN-1514 表结构变更后前端代码中字段更改(下钻表格);表格下钻后,切换顶部的下拉列表,表格报错; 2023-12-15 10:50:52 +08:00
刘洪洪
8001d66ca8 fix: 修复搜索高亮取值有时为空的问题 2023-12-14 16:52:09 +08:00
刘洪洪
d2cb42687e fix: 简化部分代码,添加注释,添加是否需要高亮指令noHighlight 2023-12-14 16:20:33 +08:00
刘洪洪
a3c8baea5c fix: 修复搜索组件tag模式下,in操作赋值错误的问题 2023-12-14 10:28:43 +08:00
刘洪洪
f5f857bcb4 CN-1479 fix: 搜索组件showHint提示文本修改 2023-12-13 17:05:16 +08:00
刘洪洪
3d1fbfa5fd fix: 删除无用文件,搜索组件text修改变量名 2023-12-12 17:29:19 +08:00
刘洪洪
3d5c69d87b CN-1479 fix: 搜索组件补全模糊搜索与tag模式关于枚举in操作的处理 2023-12-12 10:06:21 +08:00
chenjinsong
e1a26b60ae CN-1440 fix: policy的library增加鼠标悬浮提示事件 2023-12-11 17:48:38 +08:00
hyx
a7dfa33da2 暂时隐藏实体详情的性能事件 2023-12-11 14:08:20 +08:00
hyx
67189ade10 fix:1.撤销 subscriber top app 上下行数据等于0时转成<0.01 2023-12-11 11:34:12 +08:00
hyx
6c9a05f98d fix:1.subscriber top app 展示数据等于0时转成<0.01 2023-12-11 11:28:47 +08:00
hyx
720754bee2 fix:1.subscriber kpi及top app 展示数据等于0时转成<0.01 2023-12-09 11:24:21 +08:00
hyx
27c1d28870 CN-1510 feat:更改detection柱状图的tooltip交互方式 2023-12-09 10:16:41 +08:00
chenjinsong
535d2646b2 fix: policy接口参数变更 2023-12-08 16:23:06 +08:00
hyx
51d6c1d4be Merge remote-tracking branch 'origin/dev' into dev 2023-12-08 09:49:43 +08:00
hyx
742cf5d6f2 subscriber 基本信息中实体类型名称国际化 2023-12-08 09:49:21 +08:00
刘洪洪
a1ae084216 CN-1479 fix: 搜索组件text和tag模式交互添加枚举 2023-12-07 18:23:20 +08:00
hyx
bca51683e5 fix:实体详情基本信息部分底部空白过大调整 2023-12-07 18:14:38 +08:00
chenjinsong
da2ac78ea7 fix: 修复有时刷新后地图无法显示的问题 2023-12-07 17:22:50 +08:00
chenjinsong
c55a205003 fix: 修复有时刷新后地图无法显示的问题 2023-12-07 16:34:18 +08:00
hyx
f54ca30a58 1.CN-1506 Subscriber详情页基础信息图表开发;2.时间选择器组件选择具体日期时,显示省略号; 2023-12-07 16:25:14 +08:00
chenjinsong
ab30ddf49b fix: 更正subscriber地图标题 2023-12-07 11:39:48 +08:00
刘洪洪
8cd3f87c3e CN-1479 fix: 搜索组件添加showHint自动完成提示 2023-12-07 10:03:31 +08:00
chenjinsong
5106f23c2b fix: 修改subscriber地图组件url 2023-12-06 18:23:59 +08:00
hanyuxia
27e59586ed 暂时隐藏subscriber基本信息的Analysis 2023-12-06 17:52:36 +08:00
hanyuxia
da9ae6831e fix: 1.错误信息提示框将时间选择器组件遮住;2.subscriber kpi代码调整; 2023-12-06 17:30:45 +08:00
hanyuxia
2f315bf52c CN-1507 时间选择器增加左对齐的风格 2023-12-06 16:46:13 +08:00
刘洪洪
2b3f6e6b31 fix: 修复实体列表、检测界面的时间在刷新界面后显示异常的问题 2023-12-06 16:37:49 +08:00
chenjinsong
74d95f110e CN-1508 feat: subscriber流量曲线图开发 2023-12-06 16:16:12 +08:00
chenjinsong
2c12284415 CN-1484 fix: 调整error提示的位置 2023-12-01 18:08:38 +08:00
chenjinsong
60d06792b4 CN-1484 feat: 地图事件开发 2023-12-01 17:22:32 +08:00
chenjinsong
8bbebc3a8b CN-1484 feat: 静态地图 2023-11-30 17:02:44 +08:00
hyx
d43a9c8bba CN-1488 Subscriber详情页TAB组件开发 2023-11-30 09:16:45 +08:00
chenjinsong
d996769f7e fix: 修复测试用例报错问题 2023-11-27 11:23:31 +08:00
hanyuxia
a9e84fd714 CN-1483 network overview单位统一 2023-11-24 17:11:42 +08:00
hanyuxia
6450e8e050 CN-1481 Subscribe详情页KPI组件开发;CN-1482 Subscribe详情页top app组件开发 2023-11-24 14:39:33 +08:00
chenjinsong
f25805ea0a CN-1456 feat: 一些国际化 2023-11-22 16:48:17 +08:00
刘洪洪
5d42334074 fix: 恢复被误删的代码 2023-11-22 10:47:52 +08:00
刘洪洪
b30a8a8a25 fix: 实体列表恢复事件数量,并完善下拉详情和列表事件数量的一致性 2023-11-22 10:44:17 +08:00
刘洪洪
bfb1d93d95 fix: 修复npm--event的严重程度前色块标识丢失的问题 2023-11-21 18:33:22 +08:00
刘洪洪
0fa7aeebe6 fix: 修复实体详情tab因前面接口报错导致后面tab的数量显示为0的问题 2023-11-21 18:09:39 +08:00
hanyuxia
6e8566db75 fix: npm 下钻表格自定义配置metric时,界面混乱(名称过长导致) 2023-11-21 17:47:38 +08:00
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
231 changed files with 27055 additions and 7287 deletions

View File

@@ -28,7 +28,8 @@ module.exports = {
'node'
],
moduleNameMapper: {
'@/(.*)$': '<rootDir>/src/$1'
'@/(.*)$': '<rootDir>/src/$1',
'\\.(css|less|scss|sass)$': '<rootDir>/test/__mocks__/styleMock.js'
},
resetMocks: true
}

View File

@@ -23,6 +23,7 @@
"element-plus": "~1.0.2-beta.71",
"lib-flexible": "^0.3.2",
"lodash": "^4.17.21",
"maplibre-gl": "3.6.2",
"mockjs": "^1.1.0",
"moment-timezone": "^0.5.33",
"node-sass": "~4.14.0",

View File

@@ -1,5 +1,25 @@
const BASE_CONFIG = {
baseUrl: 'http://192.168.44.54:8091/',
version: '23.06',
baseUrl: 'http://192.168.44.54:8090/',
version: '23.12',
apiVersion: 'v1'
}
// 默认时间过滤条件,单位分钟. 0表示请求接口时不传时间参数
const DEFAULT_TIME_FILTER_RANGE = {
dashboard: 60, // 所有dashboard
entity: {
list: 60, // 实体列表
trafficLine: 60, // 实体详情--通用--流量曲线
subscriberKpi: 60, // 实体详情--subscriber--kpi
subscriberTopApp: 60, // 实体详情--subscriber--topApp
subscriberMap: 60, // 实体详情--subscriber--地图
informationAggregation: 0, // 实体详情--通用--信息聚合
relatedEntity: 60 * 24 * 7, // 实体详情--通用--相关实体
openPort: 60 * 24 * 7, // 实体详情--通用--开放端口
deviceInformation: 60 * 24 * 7, // 实体详情--subscriber--设备信息
accountInformation: 60 * 24 * 7, // 实体详情--subscriber--账户信息
securityEvent: 60 * 24 * 7, // 实体详情--通用--安全事件
performanceEvent: 60 * 24 * 7, // 实体详情--通用--性能事件
behaviorPattern: 60 * 24 * 7 // 实体详情--IP--行为模式
},
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 881 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -22,15 +22,39 @@
></el-input>
</el-form-item>
<el-form-item>
<el-button
<el-button :disabled="licenseStatus !== 0"
v-loading="loading"
type="primary"
class="login--input login--button"
:class="{'login-btn__license-error':licenseStatus !== 0}"
@click="login"
@keyup.enter="login"
style="font-size: 16px;"
>Login</el-button
>
>Login
</el-button>
</el-form-item>
<el-form-item v-if="licenseStatus !== 0">
<div class="license-error-msg">{{licenseStatusErrMsg}}</div>
<div class="license-file">
<button style="position: relative;" class="license__btn margin-r-20" @click.prevent="downloadFile" @keyup.enter="login">
<i class="cn-icon-download1 cn-icon margin-r-6"></i><span>Download c2v file</span>
</button>
<el-upload :action="`${baseUrl}sys/license/upload`"
ref="licenseUpload"
id="licenseUpload"
:multiple="false"
:show-file-list="false"
:accept="fileTypeLimit"
:file-list="fileList"
:auto-upload="false"
:on-change="fileChange"
:on-success="uploadSuccess"
:on-error="uploadError">
<button style="position: relative;" class="license__btn" @click.prevent="">
<i class="cn-icon-upload1 cn-icon margin-r-6"></i><span>Upload license</span>
</button>
</el-upload>
</div>
</el-form-item>
</el-form>
</div>
@@ -46,6 +70,7 @@ import { api } from '@/utils/api'
import dayjs from 'dayjs'
import _ from 'lodash'
import utc from 'dayjs/plugin/utc'
import { ref } from 'vue'
dayjs.extend(utc)
export default {
@@ -54,7 +79,12 @@ export default {
return {
loading: false,
username: '',
pin: ''
pin: '',
language: '',
licenseStatus: 1,
licenseStatusErrMsg: '',
downloadC2vUrl: api.downloadLicenseC2v,
supportID: ''
}
},
methods: {
@@ -68,6 +98,9 @@ export default {
} else {
return
}
if (this.licenseStatus !== 0) {
return
}
this.loading = true
axios.post(api.login, { username: this.username, pin: this.pin }).then(
res => {
@@ -75,6 +108,9 @@ export default {
if (!_.isEmpty(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)) {
localStorage.setItem(storageKey.theme, res.data.data.user.theme)
}
@@ -99,6 +135,72 @@ export default {
}
})
},
downloadFile () {
axios.get(this.downloadC2vUrl, { responseType: 'blob' }).then(res => {
const disposition = res.headers['content-disposition']
const fileName = decodeURI(disposition.split('filename=')[1])
if (window.navigator.msSaveOrOpenBlob) {
// 兼容ie11
const blobObject = new Blob([res.data])
window.navigator.msSaveOrOpenBlob(blobObject, fileName)
} else {
const url = URL.createObjectURL(new Blob([res.data]))
const a = document.createElement('a')
document.body.appendChild(a) // 此处增加了将创建的添加到body当中
a.href = url
a.download = fileName
a.target = '_blank'
a.click()
a.remove() // 将a标签移除
}
}, error => {
const $self = this
const reader = new FileReader()
reader.onload = function (event) {
const responseText = reader.result
const exception = JSON.parse(responseText)
if (exception.message) {
$self.$message.error(exception.message)
} else {
console.error(error)
}
}
reader.readAsText(error.response.data)
})
},
fileChange (file, fileList) {
if (file.status !== 'ready') return
if (!_.endsWith(file.name, '.xml')) {
this.fileList = []
this.$message.error('Only support '+ this.fileTypeLimit + ' files')
} else {
this.fileList = fileList.slice(-1)
this.$refs.licenseUpload.submit()
}
},
uploadSuccess (response) {
this.$message.success('Success')
this.licenseStatus = 0
},
uploadError (error) {
let errorMsg
if (error.message) {
errorMsg = JSON.parse(error.message).message
} else {
errorMsg = 'error'
}
this.licenseStatus = 1
this.$message.error('Upload failed: ' + errorMsg)
},
checkLicenseStatus () {
axios.get(api.licenseStatus).then(res => {
if (res.status === 200) {
this.licenseStatus = res.data.data.status
}
}).catch(e => {
this.licenseStatusErrMsg = this.errorMsgHandler(e)
})
},
queryAppearance () {
axios.get(api.appearance).then(res => {
if (res.status === 200) {
@@ -107,6 +209,7 @@ export default {
})
},
appearanceOut (data) {
this.language = data.lang || defaultLang
if (_.isEmpty(localStorage.getItem(storageKey.language))) {
localStorage.setItem(storageKey.language, data.lang || defaultLang)
}
@@ -123,13 +226,17 @@ export default {
localStorage.setItem(storageKey.sysLogo, data.system_logo)
}
},
mounted () {
async mounted () {
this.queryAppearance()
this.checkLicenseStatus()
},
setup (props) {
const { currentRoute } = useRouter()
return {
loginSuccessPath: currentRoute.value.query.redirect
loginSuccessPath: currentRoute.value.query.redirect,
baseUrl: BASE_CONFIG.baseUrl,
fileTypeLimit: '.xml',
fileList: ref([])
}
}
}
@@ -176,7 +283,7 @@ export default {
}
.inside {
width: 414px;
height: 524px;
height: fit-content;/*524*/
background: #0B325C;
border: 1px solid rgba(103,179,245,0.65);
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.38);
@@ -193,6 +300,7 @@ export default {
.title {
margin-top: 65px;
margin-bottom: 44px;
text-align: center;
}
.title > img {
@@ -203,6 +311,39 @@ export default {
justify-content: center;
flex-direction: column;
align-items: center;
.is-disabled {
background: #21B4ED;
color: #FFFFFF;
opacity: 0.6;
}
.license-error-msg {
color:#c73249;
display: flex;
align-items: flex-start;
justify-content: center;
height:40px;
}
.license-file {
display: flex;
flex-direction: row;
height: 100%;
align-items: center;
justify-content: space-between;
.license__btn {
height: 40px;
padding-left:10px;
padding-right:10px;
min-width: 74px;
color: white;
background-color: #21B4ED;
border: none;
border-radius: 4px;
outline: none;
font-size: 14px;
cursor: pointer;
transition: background-color linear .2s, color linear .1s;
}
}
}
:deep .el-form-item {
width: 334px;
@@ -215,9 +356,8 @@ export default {
width: 17px;
font-size: 17px;
}
.login__box .el-form-item:nth-of-type(3) {
margin-top: 25px;
margin-bottom: 65px;
.login__box .el-form-item:nth-child(3){
margin-bottom: 0px;
}
.login--button {
background: #21B4ED;
@@ -229,5 +369,12 @@ export default {
line-height: 22px;
width: 334px;
height: 52px;
margin-top: 25px;
margin-bottom:65px;
}
.login-btn__license-error {
margin-top: 25px;
margin-bottom: 10px;
height:40px;
}
</style>

View File

@@ -3,84 +3,92 @@
height: 100%;
.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 {
position: absolute;
display: flex;
top: 10px;
right: 10px;
align-items: center;
top: 1px;
right: 0;
.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 {
color: #A6AAAE;
font-size: 18px;
}
.el-icon-search {
color: #3976CB;
font-size: 22px;
}
&:hover {
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 {
height: 40px;
line-height: 40px;
margin-top: -9px;
.el-icon-error {
font-size: 17px;
color: #C4C4C4;
margin-right: 12px;
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 {
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 {
display: flex;
align-items: center;
height: 40px;
overflow: auto hidden;
border: 1px solid #CECECE;
border-radius: 2px;
padding-left: 10px;
padding-right: 80px;
background-color: white;
border: 1px solid #DEDEDE;
&::-webkit-scrollbar {
width: 8px;
@@ -104,6 +112,7 @@
border-radius: 1px;
cursor: pointer;
transition: all linear .1s;
margin-right: 30px;
&:hover {
background-color: white;
@@ -167,6 +176,9 @@
}
}
}
.entity__search .tag-search {
padding-left: 65px;
}
.el-popover.my-popper-class {
width: auto !important;

View File

@@ -13,11 +13,16 @@
color: #ccc;
}
}
.entity__search {
.CodeMirror {
padding-left: 60px;
}
}
/* PADDING */
.CodeMirror-lines {
padding: 11px 5px; /* Vertical padding around content */
padding: 9px 5px; /* Vertical padding around content */
}
.CodeMirror pre.CodeMirror-line,
.CodeMirror pre.CodeMirror-line-like {
@@ -170,14 +175,18 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
overflow: hidden;
margin-bottom: -50px; margin-right: -50px;
padding-bottom: 50px;
height: 100%;
height: 50px !important;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
z-index: 0;
}
.CodeMirror-scroll::-webkit-scrollbar {
width: 0;
}
.CodeMirror-sizer {
position: relative;
border-right: 50px solid transparent;
width: 3000px; // 避免搜索过长导致换行
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
@@ -351,3 +360,132 @@ div.CodeMirror-dragcursors {
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext { background: none; }
.CodeMirror-hint-active {
background-color: #eaf1f5;
color: #5a90b0;
}
.el-dropdown-menu__item.CodeMirror-hint {
line-height: 20px;
/*font-family: NotoSansSC-Regular;*/
}
.hint-clear {
color: #31739C !important;
}
.hint-title {
margin: 7px 0 3px 0 !important;
/* 禁止选中 样式 */
background: #fff !important;
font-family: NotoSansHans-Medium;
font-size: 12px;
color: #333333 !important;
letter-spacing: 0;
font-weight: 600 !important;
line-height: 12px;
}
.cm-s-eclipse span.cm-string-2 {
//color: #D85512;
}
.CodeMirror-lines {
/*height: 20px;*/
//padding: 3px 0;
}
.in-coder-panel {
margin-left: 2px;
}
.in-coder-panel pre {
height: auto !important;
}
.in-coder-panel .CodeMirror {
/*height: inherit;*/
}
.in-coder-panel input {
font-size: 12px !important;
display: block;
border-color: transparent !important;
padding: 0 0 0 8px !important;
}
.CodeMirror-hints {
font-family: NotoSansSC-Regular !important;
max-width: 800px;
}
.CodeMirror-line {
//font-family: Arial !important;
}
.CodeMirror-hint {
margin: 0;
padding: 0 !important;
line-height: 20px !important;
border-radius: 2px;
white-space: pre;
color: black;
cursor: pointer;
}
.cm-variable-2{
color: #164 !important;
}
.default-tips-header,.default-tips-title {
height: 24px;
line-height: 24px;
font-weight: 700;
color: #333333;
font-size: 14px;
margin: 6px 0;
font-family: NotoSansSC-Bold;
letter-spacing: 0;
}
.default-tips-header {
font-size: 15px;
}
.show-hint-tips__p {
word-break: keep-all;
margin: 0;
line-height: 24px;
color: #575757;
font-weight: 400;
}
.Hint {
padding: 0;
z-index: 2;
.hint__block {
display: flex;
flex-direction: row;
width: calc(100% - 41px);
min-height: 298px;
margin-top: 6px;
box-shadow: 0 2px 8px 0 rgba(0,0,0,.3);
z-index: 2;
.hint__block-filter {
width: 319px;
background: #fff;
border-right: 1px solid #DEDEDE;
padding: 12px;
z-index: 2;
}
.hint__block-helper {
width: calc(100% - 319px);
background: #fff;
z-index: 2;
}
}
}

View File

@@ -206,9 +206,20 @@
}
.my-date-picker {
z-index: 100004 !important;
.el-popper__arrow {
position: absolute;
top: 20px !important;
left: 642px !important; // element上样式设定是left添加right不生效
}
}
.my-date-picker__left {
.el-popper__arrow {
position: absolute;
top: 20px !important;
left: -6px !important;
}
.el-popper__arrow::before {
border: 1px solid #E7EAED !important;
}
}

View File

@@ -1,9 +1,8 @@
.error-component {
position: absolute;
//width: 100%;
//height: 100%;
left: 0;
top: 0;
z-index: 1000;
}
.error-block {
display: inline-block;

View File

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

View File

@@ -80,9 +80,17 @@
@import 'views/administration/AdministrationTabs';
@import 'views/administration/Appearance.scss';
@import 'views/administration/License.scss';
@import 'views/system/Plugin';
@import 'views/setting/knowledgeBase';
@import 'views/charts2/entityDetailLine';
@import 'views/charts2/EntityDetailSubscriberKpi.scss';
@import 'views/charts2/EntityDetailSubscriberTopApp.scss';
@import 'views/charts2/entityDetailSubscriberMap.scss';
@import 'views/charts2/entityDetailSubscriberLine.scss';
@import 'views/charts2/entityDetailTabs';
@import 'views/charts2/digitalCertificate';
@import 'views/charts2/entityDetailBasicInfo';

View File

@@ -0,0 +1,61 @@
.license{
height: 100%;
.license-form {
padding-top:40px;
padding-left:100px;
background-color: white;
position: relative;
display: flex;
flex-direction: column;
height: 100%;
.license-file {
display: flex;
flex-direction: row;
height: 100%;
margin-top: 3px;
.license__btn {
height: 30px;
min-width: 74px;
color: white;
background-color: #38ACD2;
border: none;
border-radius: 4px;
outline: none;
font-size: 14px;
cursor: pointer;
transition: background-color linear .2s, color linear .1s;
}
.license__btn:hover:not(.footer__btn--disabled) {
background-color: lighten(#38ACD2, 10%);
}
.license__btn--light {
background-color: #F5F6F7;
border: 1px solid $--border-color-primary;
color: #333;
}
.license__btn.license__btn--light:hover:not(.license__btn--disabled) {
background-color: white;
border-color: lighten(#38ACD2, 40%);
color: #38ACD2;
}
.license__btn--disabled {
opacity: .6;
cursor: default;
}
}
.el-form .el-form-item {
margin-bottom: 0;
padding:4px 0;
font-size: 14px!important;
.el-form-item__label {
color: #262626 !important;
font-weight: bold;
}
.el-form-item__content {
color: #262626 !important;
font-weight: 400;
}
}
}
}

View File

@@ -0,0 +1,94 @@
.subscriber-kpi {
height: 100%;
.subscriber-kpi-header {
height:34px;
padding-bottom:10px;
font-family: NotoSansHans-Medium;
font-size: 14px;
color: #353636;
font-weight: 500;
display:flex;
align-items: center;
justify-content: space-between;
.subscriber-kpi-title {
height:24px;
overflow: hidden;
}
}
.subscriber-kpi-body {
border: 1px solid #E2E5EC;
border-radius: 4px;
height:calc(100% - 34px);
.subscriber-kpi-content {
height: calc(100% - 36px);
padding: 20px 0 20px 20px;
display: flex;
flex-direction: column;
.panel-chart__no-data {
height: calc(100% - 46px);
}
.kpi-type {
display: flex;
flex-direction: column;
justify-content:space-between;
height: calc(100% - 65px);
.kpi-type-value {
display: flex;
flex-direction: column;
padding-bottom:20px;
.kpi-type-value-name {
line-height: 12px;
margin-bottom: 10px;
font-size: 14px;
color: #575757;
font-weight: 400;
}
.kpi-type-data {
display:flex;
flex-direction: row;
.kpi-type-value-number {
white-space: nowrap;
font-family: Helvetica-Bold;
font-size: 20px;
color: #353636;
font-weight: 700;
}
.data-trend {
display: flex;
width: 50%;
.data-total-trend {
display: flex;
align-items: center;
margin-left: 6px;
justify-content: center;
margin-top: 2px;
border-radius: 10px;
font-weight: 500;
font-size: 12px;
height: 20px;
padding: 0 5px;
}
.data-total-trend-black {
background-color: rgba(113,113,113,0.12);
color: #717171;
width: 36px;
}
.data-total-trend-green {
background-color: rgba(126,159,84,0.12);
color: #7E9F54;
}
.data-total-trend-red {
background-color: rgba(226,97,84,0.12);
color: #E26154;
.cn-icon-rise1{
color: #E44D3E;
}
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,113 @@
.subscriber-top-app {
height: 100%;
.subscriber-top-app-header {
height:34px;
padding-bottom:10px;
font-family: NotoSansHans-Medium;
font-size: 14px;
color: #353636;
font-weight: 500;
display:flex;
align-items: center;
justify-content: space-between;
.subscriber-top-app-title {
height:24px;
overflow: hidden;
}
}
.subscriber-top-app-body {
border: 1px solid #E2E5EC;
border-radius: 4px;
height:calc(100% - 34px);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 20px 20px 20px;
.panel-chart__no-data {
height: calc(100% - 46px);
}
.top-app-left {
height:100%;
display: flex;
flex-direction: column;
margin-right:15px;
.app-data {
display: flex;
flex-direction: row;
align-items: center;
font-size: 14px;
color: #353636;
font-weight: 400;
height:calc(100%/10);
.app-index {
text-align: right;
width:20px;
margin-right:15px;
}
.app-name {
width:80px;
margin-right:10px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.top-app-divider {
height:10px;
background: #717171;
margin-left:10px;
margin-right:8px;
}
.app-trend-right {
display: flex;
flex-direction: row;
align-items: center;
white-space: nowrap;
width:50px;
i {
margin-right:3px;
font-size:12px;
color: #717171;
}
}
.app-trend-left {
display: flex;
flex-direction: row;
align-items: center;
white-space: nowrap;
width:60px;
i {
margin-right:3px;
font-size:12px;
color: #717171;
}
}
.app-up {
font-size: 12px;
color: #717171;
letter-spacing: -0.2px;
text-align: center;
font-weight: 400;
}
.app-down {
font-size: 12px;
color: #717171;
letter-spacing: -0.2px;
text-align: center;
font-weight: 400;
}
}
}
.top-app-right {
height: 100%;
width:calc(100% - 248px);
position: relative;
.chart-content {
height: 100%;
width: 100%;
}
}
}
}

View File

@@ -0,0 +1,51 @@
.entity-detail-line--subscriber {
$blue: #046ECA;
$grey: #353636;
height:100%;
.el-tabs__content {
overflow: visible;
}
.cn-chart__tabs {
height:100%;
.tab-pane {
height:100%;
}
.el-tabs__header {
margin-bottom: 10px;
width: calc(100% - 272px);
}
.el-tabs__nav-wrap::after {
height: 1px;
background-color: transparent ;
}
.el-tabs__nav.is-top {
height: 33px;
.el-tabs__active-bar {
background-color: $blue;
}
.el-tabs__item {
padding: 0 10px;
height: 33px;
color: $grey;
font-size: 14px;
&.el-tabs__item.is-top.is-active {
color:$blue;
}
&:nth-child(2) {
padding-left: 0;
}
}
}
.el-tabs__content {
height: calc(100% - 40px);
border:none;
.el-table__body-wrapper {
height: calc(100% - 45px) !important;
}
}
}
}

View File

@@ -0,0 +1,70 @@
.subscriber-map {
height: 100%;
.subscriber-map-header {
height: 34px;
padding-bottom: 10px;
font-size: 14px;
color: #353636;
display: flex;
align-items: center;
justify-content: space-between;
.subscriber-map-title {
height: 24px;
overflow: hidden;
}
}
.subscriber-map-body {
position: relative;
border: 1px solid #E2E5EC;
border-radius: 4px;
height: calc(100% - 34px);
.subscriber-map {
height: 100%;
width: 100%;
.maplibregl-canvas:focus-visible {
outline: none;
}
}
.panel-chart__no-data {
height: calc(100% - 46px);
}
}
.subscriber-map-point-tooltip {
position: fixed;
background-color: white;
width: 200px;
border: 1px solid #C5C5C5;
box-shadow: -1px 1px 10px -1px rgba(205,205,205,0.85);
border-radius: 2px;
.subscriber-map-point-tooltip__time {
padding: 10px;
font-size: 12px;
color: #353636;
font-weight: bold;
}
.subscriber-map-point-tooltip__coordinates {
padding: 0 10px 10px;
.subscriber-map-point-tooltip__coordinate {
display: flex;
.coordinate__label {
width: 115px;
font-size: 12px;
color: #575757;
}
.coordinate__value {
font-size: 12px;
color: #353636;
font-weight: bold;
}
}
}
}
}

View File

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

View File

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

View File

@@ -52,11 +52,30 @@
display:flex;
flex-direction:column;
align-items: flex-start;
color: #778391;
font-weight: 400;
.data-column__span {
font-weight: bold;
color:#353636;
}
.unit__span {
color: #778391;
font-weight: 400;
}
}
.column-loading {
display: flex;
flex-direction: row;
position:relative;
.loading-icon {
position:absolute;
left: -20px;
transform: translateZ(0) scale(0.5);
width:20px;
height:20px;
margin-right:3px;
}
}
.tab-table {
border: 1px solid #E2E5EC;
border-radius: 4px 4px 0 0;

View File

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

View File

@@ -31,8 +31,12 @@
line-height: 16px;
margin-right: 10px;
.block-mode-icon1 {
font-size: 14px;
}
.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%;
display: flex;
justify-content: flex-end;
.el-button {
width: 80px !important;
height: 30px !important;
min-height: 30px !important;
line-height: 30px !important;
@@ -208,9 +213,25 @@
font-weight: 500;
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 {
padding: 0 11px !important;
}

View File

@@ -10,9 +10,9 @@
}
.detection-form-content {
height: 100%;
height: calc(100% - 92px);
overflow: scroll;
padding-bottom: 40px;
padding-bottom: 20px;
.detection-form-collapse {
margin-top: 20px;
@@ -101,6 +101,12 @@
justify-content: flex-start;
height: 24px;
line-height: 24px;
.policy-form-item {
.el-form-item__error {
width: 260px;
}
}
}
.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 {
@@ -137,4 +149,13 @@
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;
}
.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-size: 14px;
color: #717171;
@@ -34,6 +34,15 @@
.drawer-basic-id {
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;
}
.drawer-trigger-minutes {
color: #353636;
}
.detection-drawer-collapse {
background: #FFFFFF;
border: 1px solid rgba(226, 229, 236, 1);

View File

@@ -2,98 +2,124 @@
display: flex;
flex-direction: column;
width: 280px;
padding: 10px;
margin-right: 10px;
background-color: white;
margin-right: 12px;
overflow: auto;
z-index: 1;
border: 1px solid rgba(226, 229, 236, 1) !important;
border-radius: 4px !important;
.detection-filter {
display: flex;
flex-direction: column;
margin-bottom: 10px;
.filter__header {
display: flex;
flex: 0 0 32px;
align-items: center;
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;
color: #353636;
font-weight: 600;
}
.new-detection-filter-icon {
margin-left: 8px;
margin-bottom: 2px;
font-weight: bold !important;
}
}
.filter__body {
padding: 5px 0 0 15px;
.el-checkbox-group {
display: flex;
flex-direction: column;
.el-checkbox {
display: flex;
align-items: center;
padding: 5px 0;
margin-right: 5px;
.el-checkbox__label {
width: 100%;
}
.filter__checkbox-label {
display: flex;
justify-content: space-between;
align-items: center;
.severity-color-block {
width: 4px;
height: 15px;
border-radius: 2px;
}
}
&:last-of-type {
padding-bottom: 0;
}
}
}
}
.filter-case__header {
padding-left: 8px;
height: 36px;
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;
}
.new-detection-filter-title {
display: flex;
flex: 0 0 32px;
align-items: center;
padding-left: 27px;
background-color: #EFF2F5;
cursor: pointer;
.filter__header {
height: 46px;
line-height: 46px;
margin: 0 20px;
font-size: 14px;
color: #353636;
font-weight: 600;
margin: -10px;
margin-bottom: 10px;
font-weight: 500;
}
.filter__body {
width: calc(100% - 30px);
margin: 0 10px 0 20px;
max-height: 265px;
overflow-y: scroll;
overflow-x: hidden;
.filter__body-item {
height: 26px;
line-height: 26px;
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
.filter__body-item-left {
display: flex;
align-items: center;
font-size: 14px;
color: #353636;
font-weight: 400;
.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 {
height: 32px;
line-height: 32px;

View File

@@ -59,6 +59,7 @@
width: 5px;
height: 20px;
border-radius: 12px;
margin-top: 2px;
}
.cn-detection__row {
@@ -88,6 +89,7 @@
margin-left: 12px;
font-size: xx-small;
font-weight: bold;
margin-top: -1px;
}
.circle {
@@ -95,7 +97,7 @@
height: 10px;
border: 2px solid #da5656;
border-radius: 10px;
margin-top: 4px;
margin-top: 1px;
margin-right: 12px;
}
@@ -119,10 +121,12 @@
margin-right: 12px;
}
.detection-event-severity-block {
height: 20px;
line-height: 20px;
font-size: 12px;
color: #046EC9;
font-weight: 500;
padding: 2px 10px;
padding: 0 10px;
background: rgba(56,172,210,0.10);
border: 1px solid #ADC7DB;
box-shadow: 0 2px 4px 0 rgba(51,51,51,0.02);
@@ -145,7 +149,7 @@
flex-direction: row;
flex-wrap: wrap;
.basic-info__item {
.basic-info__item, .basic-info__item1 {
padding-right: 30px;
display: flex;
align-items: center;
@@ -168,6 +172,11 @@
color: #666;
}
}
.basic-info__item1 {
span: {
color: #666;
}
}
}
.show-detail {
@@ -262,3 +271,8 @@
color: #FFD82D !important;
}
}
.detection-list-icon {
margin-top: 1px;
font-size: 16px !important;
}

View File

@@ -37,7 +37,7 @@
flex-direction: row;
align-items: flex-start;
flex-wrap: nowrap;
padding: 2px 10px 2px 0;
padding: 6px 10px 6px 0;
font-size: 14px;
word-break: break-all;
@@ -45,6 +45,8 @@
padding-right: 20px;
min-width: 100px;
color: #6B717B;
line-height: 14px;
word-break: normal;
}
.row__charts {
@@ -60,12 +62,15 @@
font-weight: 400;
}
.row__content {
.row__content, .row__content1 {
display: flex;
//color: #3976CB;
color: #046ECA;
font-weight: 500;
font-size: 14px;
align-items: center;
flex-wrap: wrap;
line-height: 14px;
&.row__content--link {
font-style: italic;
@@ -87,6 +92,21 @@
font-style: italic;
color: #1890FF;
}
.row__content__icon {
font-size: 14px;
margin-right: 5px;
color: #1890FF;
}
.row__content__svg {
font-size: 14px;
margin-right: 7px;
}
}
.row__content1 {
display: block;
padding-right: 50px;
}
}
}
@@ -186,6 +206,7 @@
color: #046ECA;
margin-bottom: 10px;
font-weight: 500;
height: 36px;
}
.timeline__start-time {
font-size: 12px;
@@ -215,13 +236,20 @@
}
}
}
.row__tag {
.row__tag, .row__tag__level {
display: flex;
justify-content: center;
align-items: center;
padding: 0 4px;
//color: white;
}
.row__tag__level {
height: 16px;
font-size: 12px;
font-weight: 400;
color: #fff;
border-radius: 2px;
}
.performance-event-remark {
font-family: NotoSansSChineseRegular;

View File

@@ -1,4 +1,3 @@
.detection-table {
.el-table th > .cell, .el-table .cell {
padding-left: 0 !important;
@@ -32,7 +31,37 @@
height: 32px !important;
}
}
.policy-library-tip {
max-width: 180px;
padding: 4px;
.tip__header {
color: #353636;
font-weight: bold;
font-size: 14px;
}
.tip__tags {
display: flex;
margin-top: 8px;
.tip__tag {
margin-right: 10px;
padding: 2px 10px;
background-color: #EBF7FA;
color: #046ECA;
box-shadow: 0 2px 4px 0 rgba(51,51,51,0.02);
border-radius: 12px;
}
}
.tip__description {
margin-top: 14px;
color: #666;
&.tip__description--non {
color: #999;
}
}
}
.detection-tag-blue, .detection-tag-red, .detection-tag-gray, .detection-tag-status0, .detection-tag-status1 {
display: inline-block;
border-radius: 10px;
@@ -61,22 +90,19 @@
.detection-tag-status0 {
font-weight: 500;
font-family: NotoSansHans-Medium;
background: rgba(113, 113, 113, 0.12);
color: #717171;
padding: 0 12px;
padding: 0 10px;
}
.detection-tag-status1 {
font-weight: 500;
font-family: NotoSansHans-Medium;
background: rgba(126, 159, 84, 0.12);
color: #7E9F54;
padding: 0 8px;
padding: 0 10px;
}
.detection-table-library {
font-family: NotoSansSChineseRegular;
font-size: 12px;
color: #046ECA;
font-weight: 400;

View File

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

View File

@@ -26,7 +26,7 @@
justify-content: flex-start;
}
.explorer-top-tools, .explorer-detection-top-tools {
.explorer-top-tools, .explorer-detection-top-tools, .explorer-entity-top-tools {
display: flex;
justify-content: flex-end;
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;
justify-content: space-between;
}
@@ -87,6 +90,13 @@
font-size: 14px;
color: #353636;
font-weight: 400;
.entity-hide-entity {
margin-left: 20px;
.el-checkbox__label {
padding-left: 6px;
}
}
}
.explorer-container, .explorer-container-new {
display: flex;

View File

@@ -2,7 +2,7 @@
display: flex;
flex-direction: column;
width: 320px;
margin-right: 20px;
margin-right: 12px;
overflow: auto;
z-index: 1;
border: 1px solid rgba(226, 229, 236, 1) !important;
@@ -32,7 +32,6 @@
width: calc(100% - 30px);
margin: 0 10px 0 20px;
max-height: 265px;
min-height: 40px;
overflow-y: scroll;
overflow-x: hidden;

View File

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

View File

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

View File

@@ -43,7 +43,7 @@
align-content: center;
padding: 16px 0;
margin-bottom: 1px;
background-color: white;
//background-color: white;
border-radius: 0 4px 4px 0;
.cn-entity__icon {
@@ -79,11 +79,22 @@
.cn-entity__header-title {
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 {
display: flex;
flex-direction: column;
@@ -98,7 +109,7 @@
flex-direction: row;
flex-wrap: wrap;
.basic-info__item {
.basic-info__item, .basic-info__item1 {
padding-right: 30px;
.item__box {
@@ -161,6 +172,17 @@
color: #666;
}
}
.basic-info__item1 {
span: {
color: #666;
}
span:first-of-type {
color: #666;
}
.row-item-label {
color: #999 !important;
}
}
.row-item-label {
font-family: NotoSansSChineseRegular;
@@ -174,6 +196,26 @@
font-size: 14px;
color: #353636;
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;
&.explorer-search__input-case--question-mark-in-line {
flex-direction: row;
//flex-direction: row;
flex-direction: column;
padding: 0;
.explorer-search__input {
@@ -34,9 +35,8 @@
max-width: unset;
}
.explorer-search__input__border {
border: 1px #DEDEDE solid;
height: 43px;
.explorer-search__input--border .CodeMirror {
border: 1px solid #DEDEDE;
}
}
.search-symbol-inline {
@@ -53,14 +53,14 @@
max-width: 1000px;
height: 40px;
}
.explorer-search__foot {
.explorer-search__foot,.explorer-search__foot-list {
display: flex;
padding-top: 18px;
padding-top: 9px;
width: 100%;
max-width: 1000px;
position: relative;
justify-content: space-between;
font-weight: bold;
justify-content: flex-start;
//font-weight: bold;
.foot__item {
display: flex;
@@ -87,14 +87,15 @@
.search__history {
position: absolute;
top: 6px;
display: flex;
padding: 10px 0 0 0;
flex-direction: column;
width: 100%;
max-width: 1000px;
z-index: 2;
top: 47px;
border: 1px solid rgba(206,206,206,0.20);
//top: 47px;
border: 1px solid rgba(226,229,236,1);
border-radius: 2px;
background-color: white;
@@ -137,8 +138,32 @@
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 {
padding-left: 30px;
padding-left: 18px;
font-weight: normal;
font-size: 14px;
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;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,61 @@
.plugin-management .list-page {
.cn-table {
height: 100%;
.el-table {
height: 100%;
}
.el-table--group::after,.el-table--border::after, .el-table::before {
height: 0;
}
}
.cn-pagination {
display: none;
}
}
.plugin {
font-size: 12px;
color: #353636;
line-height: 14px;
font-weight: 400;
.type-tag {
display: inline-block;
padding: 0 10px;
background-color: #EBF7FA;
color: #046ECA;
box-shadow: 0 2px 4px 0 rgba(51,51,51,0.02);
border-radius: 12px;
margin-right:10px;
}
.plugin-name {
display: flex;
flex-direction: row;
justify-content: left;
align-items: center;
.icon-background {
display:flex;
justify-content: center;
align-items: center;
width:32px;
height:32px;
background: #ECECEC;
border-radius: 4px;
margin-right:6px;
.plugin-name-icon {
width:25px;
height:25px;
color:red;
display:flex;
justify-content: center;
align-items: center;
}
}
}
.two-line {
overflow: hidden; //超出的文本隐藏
text-overflow: ellipsis; //溢出用省略号显示
display: -webkit-box;
line-clamp:2 ;
-webkit-line-clamp: 2; // 超出多少行
-webkit-box-orient: vertical;
}
}

View File

@@ -1,8 +1,8 @@
@font-face {
font-family: "cn-icon"; /* Project id 2614877 */
src: url('iconfont.woff2?t=1693386443164') format('woff2'),
url('iconfont.woff?t=1693386443164') format('woff'),
url('iconfont.ttf?t=1693386443164') format('truetype');
src: url('iconfont.woff2?t=1706606024800') format('woff2'),
url('iconfont.woff?t=1706606024800') format('woff'),
url('iconfont.ttf?t=1706606024800') format('truetype');
}
.cn-icon {
@@ -13,6 +13,78 @@
-moz-osx-font-smoothing: grayscale;
}
.cn-icon-license:before {
content: "\e666";
}
.cn-icon-base-station:before {
content: "\e6cf";
}
.cn-icon-home:before {
content: "\e6d0";
}
.cn-icon-company:before {
content: "\e6d1";
}
.cn-icon-pedestrian:before {
content: "\e6d2";
}
.cn-icon-system:before {
content: "\e6cc";
}
.cn-icon-plugin:before {
content: "\e6cd";
}
.cn-icon-IMSI:before {
content: "\e812";
}
.cn-icon-APN:before {
content: "\e813";
}
.cn-icon-shoujihaoma:before {
content: "\e814";
}
.cn-icon-IMEI:before {
content: "\e811";
}
.cn-icon-trace-point:before {
content: "\e810";
}
.cn-icon-account-info:before {
content: "\e80e";
}
.cn-icon-device-info:before {
content: "\e80f";
}
.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 {
content: "\e6c7";
}
@@ -321,7 +393,7 @@
content: "\e7b4";
}
.cn-icon-a-GeneralSettings:before {
.cn-icon-general-setting:before {
content: "\e7b5";
}

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

@@ -5,16 +5,20 @@
@mouseleave="showCloseIcon = false"
>
<text-mode
test-id="text-mode"
v-if="searchMode === 'text'"
ref="textMode"
:column-list="columnList"
:str="str"
:show-list="showList"
:is-show-hint="showHint"
:unit-test-str="unitTestStr"
@changeMode="changeMode"
@search="search"
:show-close-icon="showCloseIcon"
></text-mode>
<tag-mode
test-id="tag-mode"
v-if="searchMode === 'tag'"
ref="tagMode"
:column-list="columnList"
@@ -47,6 +51,11 @@ export default {
showCloseIcon: false
}
},
provide () {
return {
myHighLight: !this.noHighlight
}
},
props: {
// 默认模式tag | text
defaultMode: String,
@@ -64,11 +73,22 @@ export default {
required: true
},
// 连接符列表
connectionList: Array
connectionList: Array,
showHint: {
type: Boolean,
default: false
},
noHighlight: {
type: Boolean,
default: false
}
},
emits: ['search'],
methods: {
search (parseData) {
if (this.isUnitTesting) {
this.unitTestParam = parseData
}
this.$emit('search', parseData)
},
changeMode (mode, { str, metaList }) {

View File

@@ -17,7 +17,7 @@
size="mini"
v-model="meta.column.label"
ref="columnSelect"
:placeholder="meta.column.label || ''"
:placeholder="meta.column.label || ' '"
@blur="columnBlur(meta, index)"
@change="(value) => selectColumn(value, meta)"
>
@@ -40,7 +40,58 @@
<div v-if="meta.operator.value === 'has'" class="condition__operator" style="color: #000C18">({{meta.column.label}},</div>
<!-- -->
<div class="condition__value">
<div v-if="meta.value.isEditing">
<!--点击=操作符时单个选择枚举值-->
<div v-if="meta.value.isEditing && meta.doc && meta.column.type!==columnType.array">
<el-select
allow-create
filterable
size="mini"
v-model="meta.value.value"
ref="columnValue"
:placeholder="meta.value.value || ' '"
@blur="valueBlur1(meta, index)"
@change="(value) => selectValue(value, meta)"
>
<el-option
v-for="(code, i) in meta.doc.data"
:key="i"
:label="code.code"
:value="code.code"
></el-option>
</el-select>
</div>
<!--点击 in 操作符时多个选择枚举值-->
<div v-if="meta.value.isEditing && meta.doc && meta.column.type===columnType.array">
<el-select
v-model="myCheckboxList"
ref="valuesSelect"
multiple
size="mini"
collapse-tags
collapse-tags-tooltip
:placeholder="meta.value.value || ' '"
@blur="valuesBlur(meta, index)"
@focus="valuesFocus(meta)"
@change="(value) => selectValues(value, meta)"
@visible-change="(value) => selectVisibleValues(value, meta)"
popper-class="my-select-class"
>
<template #label>
{{ meta.value.value }}
</template>
<template #default>
<el-option
v-for="item in meta.doc.data"
:key="item.code"
:label="item.code"
:value="item.code"
/>
</template>
</el-select>
</div>
<div v-if="meta.value.isEditing && !meta.doc">
<!--避免blur事件和keyup.enter重复执行-->
<el-input
ref="valueInput"
@@ -90,25 +141,25 @@
</template>
</div>
<div class="tag-search__add" @click="addCondition">{{$t('entities.advancedSearch.add')}}</div>
<div class="search__suffixes search__suffixes--tag-mode">
<div class="search__suffix" style="margin-right: 12px">
<div class="search__suffixes search__suffixes--tag-mode search__suffixes--tag-mode__block" :class="showList ? '' : 'entity-explorer-home'">
<span class="search__suffix">
<el-popover
popper-class="my-popper-class"
placement="top"
trigger="hover"
:content="$t('entity.switchToBasicSearch')"
:content="$t('overall.switchToText')"
>
<template #reference>
<i class="cn-icon cn-icon-search-normal" @click="changeMode"></i>
</template>
</el-popover>
</div>
<div v-show="metaList.length>0" class="search__suffix-close" @click="cleanMetaList">
</span>
<span v-show="metaList.length>0" class="search__suffix search__suffix-close" @click="cleanMetaList">
<i class="el-icon-error"></i>
</div>
<div class="search__suffix" :class="showList ? 'new-search__suffix' : 'entity-explorer-search'" @click="search">
</span>
<span test-id="tag-search" class="search__suffix" @click="search">
<i class="el-icon-search"></i>
</div>
</span>
</div>
</div>
</template>
@@ -119,23 +170,28 @@ import _ from 'lodash'
import { handleErrorTip } from '@/components/advancedSearch/meta/error'
import Parser, { stringInQuot } from '@/components/advancedSearch/meta/parser'
import { overwriteUrl, urlParamsHandler } from '@/utils/tools'
import { enumerateData } from '@/utils/static-data'
export default {
name: 'TagMode',
props: {
columnList: Array,
connectionList: Array,
convertMetaList: Array,
showList: Boolean
showList: Boolean,
unitTestStr: String
},
data () {
return {
condition,
connection,
metaList: [],
columnType,
myCheckboxList: [],
operatorList: [] // 操作符列表根据所选columnList的label来确定一般为=,INtags操作符为has
}
},
emits: ['changeMode', 'search'],
inject: ['myHighLight'],
methods: {
// 新增条件
addCondition (meta) {
@@ -202,6 +258,7 @@ export default {
if (this.isCustomized(value)) {
meta.column.type = columnType.fullText
meta.column.label = value
meta.column.isFullText = true
meta.resetOperator()
meta.resetValue()
} else {
@@ -217,19 +274,61 @@ export default {
if (!meta.operator.value) {
meta.operator.isEditing = true
meta.operator.show = true
} else {
// 切换column清除上次column选择的value包含枚举的则删除in操作符的让类型回归array
meta.value.value = ''
meta.value.label = ''
meta.value.isEditing = true
meta.value.show = true
const obj = enumerateData.find(d => d.name === meta.column.label)
if (obj) {
meta.doc = obj
if (this.$refs.valuesSelect) {
// 触发focus后select弹窗并没有生效
this.$refs.valuesSelect[0].focus(meta)
}
} else {
delete meta.doc
}
if (meta.operator.value.toLowerCase() === 'in') {
meta.column.type = columnType.array
}
}
}, 200)
}
},
selectValue (value, meta) {
const isWrapped = this.isSingleQuoteWrapping(value)
meta.value.value = isWrapped && meta.column.type === columnType.string ? value : `'${value}'`
meta.value.label = isWrapped && meta.column.type === columnType.string ? value : `'${value}'`
setTimeout(() => {
meta.column.isEditing = false
meta.value.isEditing = false
}, 100)
},
selectVisibleValues (value, meta) {
if (!value) {
meta.value.isEditing = false
this.myCheckboxList = []
}
},
selectValues (value, meta) {
if (value.length > 0) {
let str = ''
value.forEach(item => {
str += `'${item}',`
})
str = str.substring(0, str.length - 1)
str = `(${str})`
meta.value.value = value
meta.value.label = str
} else {
meta.value.value = ''
}
},
selectConnection (value, meta) {
meta.isEditing = false
},
columnClick (meta) {
meta.column.isEditing = true
this.$nextTick(() => {
this.$refs.columnSelect[this.$refs.columnSelect.length - 1].focus()
})
},
columnBlur (meta, index) {
setTimeout(() => {
const parser = new Parser(this.columnList)
@@ -247,26 +346,64 @@ export default {
this.operatorList = obj ? obj.doc.constraints.operator_functions.split(',') : ['=', 'IN']
if (meta.column && meta.column.type === 'fullText') {
meta.operator.value = '='
meta.column.show = false
meta.operator.show = false
meta.column.show = true
meta.column.isFullText = true
meta.operator.show = true
const label = JSON.parse(JSON.stringify(meta.column.label))
meta.column.label = parser.getEntityTypeByValue(meta.column.label)
meta.value.value = label
meta.value.label = label
// if (meta.column.label === 'domain') {
// meta.operator.value = 'like'
// meta.value.value = `%${this.delSingleQuote(label)}`
// meta.value.label = `${this.delSingleQuote(label)}`
// } else if (meta.column.label === 'app') {
// meta.operator.value = 'like'
// meta.value.value = `%${this.delSingleQuote(label)}%`
// meta.value.label = `${this.delSingleQuote(label)}`
// }
if (meta.column.label === 'domain') {
meta.operator.value = 'like'
meta.value.value = `%${this.delSingleQuote(label)}`
meta.value.label = `%${this.delSingleQuote(label)}`
} else if (meta.column.label === 'app') {
meta.operator.value = 'like'
meta.value.value = `%${this.delSingleQuote(label)}%`
meta.value.label = `%${this.delSingleQuote(label)}%`
}
meta.column.type = 'string'
}
}, 200)
},
valueBlur1 (meta) {
setTimeout(() => {
meta.value.isEditing = false
}, 200)
},
valuesBlur (meta) {
this.$nextTick(() => {
})
},
valuesFocus (meta) {
this.$nextTick(() => {
meta.value.isEditing = true
setTimeout(() => {
if (meta.value.value && this.myCheckboxList.length === 0) {
let valueArr = []
if (!_.isArray(meta.value.value)) {
let value = meta.value.value
if (value.indexOf('(') === 0 && value.indexOf(')') === value.length - 1) {
value = value.substring(1, value.length)
value = value.substring(0, value.length - 1)
}
valueArr = value.split(',')
valueArr.forEach((item, index) => {
if (item[0] === "'" && item[item.length - 1] === "'") {
item = item.substring(1, item.length - 1)
this.myCheckboxList.push(item)
}
})
} else if (_.isArray(meta.value.value)) {
meta.value.value.forEach(item => {
this.myCheckboxList.push(item)
})
}
}
}, 100)
})
},
connectionClick (meta) {
meta.isEditing = true
},
@@ -277,10 +414,13 @@ export default {
// 处理搜索值
meta.value.isEditing = true
meta.value.show = true
const obj = enumerateData.find(d => d.name === meta.column.label)
if (obj) {
meta.doc = obj
}
// 若是in或not incolumn的type要改成array否则是string
if (operator.toLowerCase().indexOf('in') > -1) {
meta.column.type = columnType.array
meta.value.value = []
} else if (['>', '<', '>=', '<='].indexOf(operator) > -1) {
meta.column.type = columnType.number
} else {
@@ -290,9 +430,16 @@ export default {
}
}
this.$nextTick(() => {
const selectList = this.$refs.valueInput
if (selectList && selectList.length > 0) {
this.$refs.valueInput[selectList.length - 1].focus() // 在for循环里生成的dom所以是数组
if (meta.doc) {
const selectList = this.$refs.columnValue
if (selectList && selectList.length > 0) {
this.$refs.columnValue[selectList.length - 1].focus() // 在for循环里生成的dom所以是数组
}
} else {
const selectList = this.$refs.valueInput
if (selectList && selectList.length > 0) {
this.$refs.valueInput[selectList.length - 1].focus() // 在for循环里生成的dom所以是数组
}
}
})
},
@@ -439,10 +586,26 @@ export default {
}
meta.value.isEditing = !meta.isCompleteCondition()
},
columnClick (meta) {
meta.column.isEditing = true
this.$nextTick(() => {
this.$refs.columnSelect[this.$refs.columnSelect.length - 1].focus()
})
},
valueClick (meta) {
meta.value.isEditing = true
const obj = enumerateData.find(d => d.name === meta.column.label)
if (obj) {
meta.doc = obj
}
this.$nextTick(() => {
this.$refs.valueInput[0].focus()
if (this.$refs.valueInput) {
this.$refs.valueInput[0].focus()
}
if (this.$refs.valuesSelect.length > 0) {
// 触发focus后select弹窗并没有生效
this.$refs.valuesSelect[0].focus(meta)
}
})
},
// 判断是否是用户自己添加的内容,用于判断是否是全局搜索
@@ -460,14 +623,20 @@ export default {
search () {
if (this.metaList.length > 0) {
const parser = new Parser(this.columnList)
const errorList = parser.validateMeta(this.metaList)
let errorList = parser.validateMeta(this.metaList)
// 测试的metaList并不是由new Meta()生成所以instanceof时meta并不在Meta原型链上导致报错故直接略过
if (this.isUnitTesting) {
errorList = []
}
const keywordList = this.myHighLight ? parser.getKeywordList(this.metaList) : [] // 搜索高亮的关键字
if (_.isEmpty(errorList)) {
const strObj = parser.handleMetaListToStr(this.metaList)
const str = strObj.str ? strObj.str : strObj
const str2 = strObj.str2 ? strObj.str2 : strObj
// str为将metaList转成字符串的值str2为地址栏展示的值
const key = parser.handleEntityTypeByStr(str)
this.$emit('search', { ...parser.parseStr(key), str: str2 })
let key = parser.handleEntityTypeByStr(str)
key = parser.conversionEnum(key)
this.$emit('search', { ...parser.parseStr(key), str: str2, keywordList: keywordList })
} else {
this.$message.error(handleErrorTip(errorList[0]))
}
@@ -555,14 +724,17 @@ export default {
const column = this.columnList.find(c => {
return c.label === param.column
})
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
if (metaIndex > 0) {
this.metaList.splice(metaIndex - 1, 2)
} else if (this.metaList.length === 1) {
this.metaList.splice(metaIndex, 1)
} else {
this.metaList.splice(metaIndex, 2)
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))
// 不是在首位则删除时顺带删除前一个indexand或or否则顺带删除后一个index
if (metaIndex > 0) {
this.metaList.splice(metaIndex - 1, 2)
} else if (this.metaList.length === 1) {
this.metaList.splice(metaIndex, 1)
} else {
this.metaList.splice(metaIndex, 2)
}
}
})
},
@@ -622,20 +794,25 @@ export default {
let { q } = this.$route.query
if (q && !this.convertMetaList) {
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) {
q = decodeURI(q)
} else {
const str1 = q.substring(q.indexOf('%'), q.indexOf('%') + 3)
if (q.indexOf('%') > 0 && (str1 !== '%20' || str1 === '%25')) {
if (q.indexOf('%') > 0 && (str1 === '%20' || str1 === '%25')) {
q = decodeURI(q)
}
}
this.metaList = parser.parseStr(q).metaList
}
this.emitter.on('advanced-search', function () {
vm.search()
})
if (!this.isUnitTesting) {
this.emitter.on('advanced-search', function () {
vm.search()
})
}
},
watch: {
convertMetaList: {
@@ -650,6 +827,7 @@ export default {
if (item.column && item.column.type === 'fullText') {
item.operator.value = '='
item.column.show = false
item.column.isFullText = true
item.operator.show = false
const label = JSON.parse(JSON.stringify(item.column.label))
item.column.label = parser.getEntityTypeByValue(item.column.label)

View File

@@ -1,35 +1,56 @@
<template>
<textarea
ref="textSearch"
></textarea>
<div class="search__suffixes search__suffixes--text-mode">
<div class="search__suffix">
<el-popover
popper-class="my-popper-class"
placement="top"
trigger="hover"
:content="$t('entity.switchToAdvancedSearch')"
>
<template #reference>
<i class="cn-icon cn-icon-filter margin-r-12" @click="changeMode"></i>
</template>
</el-popover>
</div>
<div v-show="isCloseIcon" class="search__suffix-close" @click="cleanParams">
<i class="el-icon-error"></i>
</div>
<div class="search__suffix" :class="showList ? 'new-search__suffix' : 'entity-explorer-search'" @click="search">
<i class="el-icon-search"></i>
<div @click="handleClick" v-ele-click-outside="handleBlur">
<textarea
style="text-indent: 65px;"
cols="40"
ref="textSearch"
></textarea>
<div class="search__suffixes search__suffixes--text-mode" :class="showList ? '' : 'entity-explorer-home'" style="padding-left: 1px;background: #fff;">
<!--切换texttag模式图标-->
<span class="search__suffix">
<el-popover
popper-class="my-popper-class"
placement="top"
trigger="hover"
:content="$t('overall.switchToTag')"
>
<template #reference>
<i test-id="text-change-mode" class="cn-icon cn-icon-filter" @click="changeMode"></i>
</template>
</el-popover>
</span>
<!--删除图标-->
<span v-show="isCloseIcon" class="search__suffix search__suffix-close" @click="cleanParams">
<i class="el-icon-error"></i>
</span>
<!--搜索图标-->
<span class="search__suffix" test-id="text-search" @click.stop="search">
<i class="el-icon-search"></i>
</span>
</div>
<!--showHint弹窗部分-->
<el-popover
placement="bottom"
width="100%"
ref="popoverRef"
:visible="hintVisible"
popper-class="search-show-hint-popover"
trigger="click">
<template #reference>
<div>
<hint v-if="hintVisible" :hintList="hintList"
@load="handleHintLoad"
@select="handleSelect"
:hintParams="hintParams"
:hintSearch="searchStr"></hint>
</div>
</template>
</el-popover>
</div>
</template>
<script>
import 'codemirror/theme/ambiance.css'
import 'codemirror/addon/hint/show-hint'
import 'codemirror/addon/hint/show-hint.css'
import 'codemirror/addon/display/placeholder'
import 'codemirror/mode/sql/sql'
import Parser, { stringInQuot, handleOperatorSpace } from '@/components/advancedSearch/meta/parser'
import CodeMirror from 'codemirror'
import { toRaw } from 'vue'
@@ -37,23 +58,65 @@ import _ from 'lodash'
import { columnType } from '@/components/advancedSearch/meta/meta'
import { handleErrorTip } from '@/components/advancedSearch/meta/error'
import { overwriteUrl, urlParamsHandler } from '@/utils/tools'
import Hint from '@/components/advancedSearch/showhint/Hint/Hint'
import { getDataset } from '@/components/advancedSearch/showhint/packages/getDataset'
import codeMirrorMixins from '@/components/advancedSearch/showhint/myCodeMirror.js'
export default {
name: 'TextMode',
mixins: [codeMirrorMixins],
props: {
columnList: Array,
str: String,
showList: Boolean,
showCloseIcon: Boolean
showCloseIcon: Boolean,
isShowHint: {
type: Boolean,
default: false
},
unitTestStr: String
},
data () {
return {
codeMirror: null,
isCloseIcon: this.showCloseIcon,
isEdit: false
isEdit: false,
hintVisible: false,
dataset: null,
CodeMirror,
myUnitTestStr: this.unitTestStr
}
},
emits: ['changeMode', 'search'],
inject: ['myHighLight'],
created () {
if (this.isShowHint && !this.isUnitTesting) {
this._initComponent()
}
},
provide () {
return {
getDataset: () => {
// provide() 写成方法之后,保证this的指向
return this.dataset || null
}
}
},
components: {
Hint
},
computed: {
searchStr () {
const { wholeTokenStr } = this.getWholeToken() || ''
if (['not in', 'not like', 'order by', 'group by'].includes(wholeTokenStr?.toLowerCase())) {
return wholeTokenStr
}
if (['operator', 'keyword', 'builtin'].includes(this.hintParams?.token?.type)) {
return this.hintParams?.token?.string
}
return this.hintParams.leftpart || this.hintSearch || ''
}
},
methods: {
cleanParams () {
toRaw(this.codeMirror).setValue('')
@@ -64,43 +127,74 @@ export default {
this.reloadUrl(routeQuery, 'cleanOldParams')
},
initCodeMirror () {
this.codeMirror = CodeMirror.fromTextArea(this.$refs.textSearch, {
mode: {
name: 'sql'
},
placeholder: 'Enter...',
let option = {
mode: 'sql',
placeholder: '',
lineNumbers: false
})
this.codeMirror.setOption('extraKeys', {
Enter: (cm) => {}
})
this.codeMirror.on('focus', () => {
if (this.codeMirror.getValue().trim() !== '') {
this.isEdit = true
this.isCloseIcon = true
}
if (this.isShowHint) {
option = {
keyMap: 'sublime',
tabSize: 2, // 缩进格式
// theme: 'eclipse', // 主题,对应主题库 JS 需要提前引入
line: true,
lineNumbers: false, // 显示行数
indentUnit: 4, // 缩进单位为4
styleActiveLine: true, // 当前行背景高亮
// mode: 'text/x-filter', // HMTL混合模式
mode: 'sql', // HMTL混合模式
foldGutter: true,
lint: true,
auto: 'auto', // 自动换行
autoCloseBrackets: true, // 自动闭合符号
matchBrackets: true, // 是否添加匹配括号高亮
spellcheck: true, // 启用拼写检查
autocorrect: true, // 启用自动更正
lineWrapping: true, // 滚动或换行以显示长行
// 提示配置
hintOptions: {
completeSingle: false, // 自动匹配唯一值
// 匹配 t_test_login.col_a 用. 来连接的
tables: {
filter_table: ['recv_time']
},
alignWithWord: false
}
}
})
this.codeMirror.on('blur', () => {
const timer = setTimeout(() => {
this.isEdit = false
this.isCloseIcon = false
clearTimeout(timer)
}, 200)
})
this.codeMirror.on('update', () => {
this.isEdit = true
this.isCloseIcon = true
})
}
this.codeMirror = CodeMirror.fromTextArea(this.$refs.textSearch, option)
if (this.codeMirror) {
this.codeMirror.setOption('extraKeys', {
Enter: (cm) => {}
})
this.setCodemirrorValue()
this.initEvent()
this.initHint()
}
},
search () {
const str = this.codeMirror.getValue().trim()
this.handleBlur()
let str
if (!this.isUnitTesting) {
str = this.codeMirror.getValue().trim()
} else {
str = this.myUnitTestStr
}
if (str) {
const parser = new Parser(this.columnList)
const keyInfo = parser.comparedEntityKey(parser.handleEntityTypeByStr(str))
const keyInfo = parser.comparedEntityKey(parser.handleEntityTypeByStr(str)) // 校验输入str字段是schema内的字段并将语句进行规范
const metaList = parser.parseStr(_.cloneDeep(str)).metaList
const keywordList = this.myHighLight ? parser.getKeywordList(metaList) : [] // 搜索高亮所需的关键字
if (keyInfo.isKey) {
const errorList = parser.validateStr(keyInfo.key)
const enumKey = parser.conversionEnum(keyInfo.key) // 检查是否包含枚举字段,包含的话进行替换
const errorList = parser.validateStr(enumKey) // 检查语句是否有错误
if (_.isEmpty(errorList)) {
this.$emit('search', { ...parser.parseStr(keyInfo.key), str: str })
// 补全模糊搜索
if (!this.isUnitTesting) {
toRaw(this.codeMirror).setValue(parser.handleEntityTypeByStr(str))
}
// 注参数str1.是用户搜索框的内容在补全模糊搜索后的内容2.部分参数是用户主观可见但格式不符合接口原则的如status='Active'接口需要status=0
this.$emit('search', { ...parser.parseStr(enumKey), str: parser.handleEntityTypeByStr(str), keywordList: keywordList })
} else {
this.$message.error(handleErrorTip(errorList[0]))
}
@@ -111,8 +205,9 @@ export default {
this.$emit('search', { q: '', str: '', metaList: [] })
}
},
focus () {
this.codeMirror.focus()
focus (e) {
toRaw(this.codeMirror).setValue(e.str)
// this.codeMirror.focus()
},
changeMode () {
const str = this.codeMirror.getValue().trim()
@@ -147,7 +242,12 @@ export default {
}
},
addParams (params) {
let current = this.codeMirror.getValue()
let current = ''
if (!this.isUnitTesting) {
current = this.codeMirror.getValue()
} else {
current = this.myUnitTestStr
}
params.forEach(param => {
const column = this.columnList.find(c => c.label === param.column)
if (param.operator === 'has') {
@@ -156,7 +256,11 @@ export default {
current = `${current ? current + ' AND ' : ''}${param.column}${handleOperatorSpace(param.operator)}${this.handleValue(param.value, column, param.operator)}`
}
})
toRaw(this.codeMirror).setValue(current.trim())
if (!this.isUnitTesting) {
toRaw(this.codeMirror).setValue(current.trim())
} else {
this.myUnitTestStr = current
}
},
removeParams (params) {
let current = this.codeMirror.getValue()
@@ -193,6 +297,174 @@ export default {
newUrl = urlParamsHandler(window.location.href, query, newParam, clean)
}
overwriteUrl(newUrl)
},
initEvent () {
this.codeMirror.on('focus', (coder) => {
if (this.codeMirror.getValue().trim() !== '') {
this.isEdit = true
this.isCloseIcon = true
}
if (this.isShowHint && this.$emit) {
this.$emit('focus', coder.getValue())
}
})
this.codeMirror.on('blur', (coder) => {
const timer = setTimeout(() => {
this.isEdit = false
this.isCloseIcon = false
if (this.isShowHint && this.$emit) {
this.$emit('blur', coder.getValue())
}
clearTimeout(timer)
}, 200)
})
this.codeMirror.on('update', () => {
this.isEdit = true
this.isCloseIcon = true
})
if (this.isShowHint) {
// 支持双向绑定
this.codeMirror.on('change', (coder) => {
if (this.$emit) {
this.$emit('input', coder.getValue())
}
})
this.codeMirror.on('startCompletion', () => {
// 展开自动提示的 事件回调
this.hintVisible = true
this.hintVm?.hintDeactive()
})
this.codeMirror.on('endCompletion', () => {
// 自动提示关闭
this.hintVisible = false
this.hintParams = {}
this.hintList = []
})
this.$emit('load', this.codeMirror)
}
},
_initComponent () {
getDataset(this, this.queryParams || {}, this.columnList).then((dataset, dataDisposeFun) => {
this.dataset = Object.freeze(dataset)
}).catch(err => {
console.error(err)
})
},
initHint () {
this.codeMirror.on('inputRead', () => {
setTimeout(() => {
this.codeMirror.showHint()
})
})
},
handleBlur () {
if (this.isShowHint) {
this.hintVisible = false
this.hintParams = {}
this.hintList = []
}
},
handleClick () {
if (this.isShowHint) {
this.hintVisible = true
this.codeMirror.showHint()
}
},
getWholeToken () {
// 获取 前一个token
const editor = this.hintParams.editor
const Pos = this.CodeMirror.Pos
const cur = this.hintParams.cur
const token = this.hintParams.token
if (!editor) {
return
}
const spaceToken = editor.getTokenAt(Pos(cur.line, token.start))
let preToken = ''
if (spaceToken && spaceToken?.string === ' ') {
preToken = editor.getTokenAt(Pos(cur.line, spaceToken.start))
}
const searchKey = `${preToken?.string} ${token?.string}`
return {
wholeTokenStr: searchKey,
spaceToken,
preToken,
token
}
},
handleHintLoad ({ vm }) {
this.hintVm = vm
},
handleSelect (item, index, hintList) {
if (index === 0) {
// 不可能选中0 第0项是标题, 选中0 说明没选中
this.hintParams?.editor?.closeHint()
this.$emit('query', CodeMirror)
return
}
const data = {
from: this.hintParams.from,
to: this.hintParams.to,
list: hintList
}
const { wholeTokenStr, preToken, token } = this.getWholeToken() || ''
let cur = null
cur = this.hintParams?.cur
// 上一个字段 是存在空格的关键字,整体删除上一个关键字
if (['not in', 'not like', 'order by', 'group by'].includes(wholeTokenStr?.toLowerCase())) {
this.hintParams?.editor?.replaceRange('', { line: cur.line, ch: preToken.start }, {
line: cur.line,
ch: token.end
})
}
this.completion && this.completion.pick(data, index)
},
setCodemirrorValue () {
// 如果地址栏包含参数q则将参数q回显到搜索栏内
let { q } = this.$route.query
if (this.str) {
toRaw(this.codeMirror).setValue(this.str)
}
if (q) {
if (q.indexOf('+') > -1) {
q = q.replace('+', ' ')
}
if (q.indexOf('%') === 0 || q.indexOf('%20') > -1 || q.indexOf('%25') > -1) {
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解析时不识别导致的语法错误
// 如地址栏输入116.178.222.171此时的q很长刷新界面时需要把q里的116.178.222.171拿出来进行搜索
if (q.indexOf('QUERY') > -1) {
const strList = q.split(' ')
if (strList.length > 0) {
// 此时strList[1]为ip_addr:116.178.222.171获取116.178.222.171
q = strList[1].slice(8)
}
}
if (this.codeMirror) {
toRaw(this.codeMirror).setValue(q)
}
} else {
this.isCloseIcon = false
}
const vm = this
this.emitter.on('advanced-search', function () {
vm.search()
})
}
},
watch: {
@@ -216,39 +488,23 @@ export default {
}
},
mounted () {
// 如果地址栏包含参数q则将参数q回显到搜索栏内
let { q } = this.$route.query
this.initCodeMirror()
if (this.str) {
toRaw(this.codeMirror).setValue(this.str)
}
if (q) {
if (q.indexOf('%') === 0 || q.indexOf('%20') > -1 || q.indexOf('%25') > -1) {
q = decodeURI(q)
} else {
const str1 = q.substring(q.indexOf('%'), q.indexOf('%') + 3)
if (q.indexOf('%') > 0 && (str1 !== '%20' || str1 === '%25')) {
q = decodeURI(q)
if (this.isShowHint) {
this.$nextTick(() => {
// dataset是避免数据未初始化完成注册失败ref是因为组件加载2次避免第二次时dom丢失导致数据挂载失败
if (this.dataset && this.$refs.textSearch) {
this.initShowHint()
this.initCodeMirror()
}
}
// 为避免地址栏任意输入导致全查询的q带QUERY解析时不识别导致的语法错误
// 如地址栏输入116.178.222.171此时的q很长刷新界面时需要把q里的116.178.222.171拿出来进行搜索
if (q.indexOf('QUERY') > -1) {
const strList = q.split(' ')
if (strList.length > 0) {
// 此时strList[1]为ip_addr:116.178.222.171获取116.178.222.171
q = strList[1].slice(8)
}
}
toRaw(this.codeMirror).setValue(q)
} else {
this.isCloseIcon = false
})
} else if (this.$refs.textSearch) {
this.initCodeMirror()
}
const vm = this
this.emitter.on('advanced-search', function () {
vm.search()
})
}
}
</script>
<style>
.el-popper.search-show-hint-popover {
visibility: hidden !important;
}
</style>

View File

@@ -4,6 +4,7 @@ import ParserError, { errorDesc, errorTypes } from '@/components/advancedSearch/
import _ from 'lodash'
import { ElMessage } from 'element-plus'
import i18n from '@/i18n'
import store from '@/store'
const strReg = {
// 需要不限制语言,正则过滤中英日俄语出错实现语言都通过。留个记录观察,后续校验
@@ -12,6 +13,14 @@ const strReg = {
value: /^[\da-zA-Z\u4E00-\u9FA5\u3040-\u309F\u0800-\u4e00\u0400-\u04FF\u2000-\u206F\s.'-_%]$/
}
const operatorList = ['=', ' in ', ' IN ', ' like ', ' LIKE ', 'HAS(', 'has(']
const enumList = ['status', 'eventType', 'severity']
// ipv4校验
const regexIPv4 = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
// ipv6校验
const regexIPv6 = /^(?:(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}:){1,7}:|(?:[0-9A-Fa-f]{1,4}:){1,6}:[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}:){1,5}(?::[0-9A-Fa-f]{1,4}){1,2}|(?:[0-9A-Fa-f]{1,4}:){1,4}(?::[0-9A-Fa-f]{1,4}){1,3}|(?:[0-9A-Fa-f]{1,4}:){1,3}(?::[0-9A-Fa-f]{1,4}){1,4}|(?:[0-9A-Fa-f]{1,4}:){1,2}(?::[0-9A-Fa-f]{1,4}){1,5}|[0-9A-Fa-f]{1,4}:(?:(?::[0-9A-Fa-f]{1,4}){1,6})|:(?:(?::[0-9A-Fa-f]{1,4}){1,7}|:)|fe80:(?::[0-9A-Fa-f]{0,4}){0,4}%\w+|::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:(?:2[0-4]|1\d|[1-9])?\d|25[0-5])\.(?:(?:2[0-4]|1\d|[1-9])?\d|25[0-5])\.(?:(?:2[0-4]|1\d|[1-9])?\d|25[0-5])\.(?:(?:2[0-4]|1\d|[1-9])?\d|25[0-5])|(?:[0-9A-Fa-f]{1,4}:){1,4}:192\.88\.99\.(\d{1,3})|(?:[0-9A-Fa-f]{1,4}:){1,4}:192\.0\.2\.(\d{1,3})|(?:[0-9A-Fa-f]{1,4}:){1,4}:(?:[0-9A-Fa-f]{1,4}:){0,1}192\.0\.0\.(\d{1,3})|ff00:(?::[0-9A-Fa-f]{0,4}){0,4}|(?:[0-9A-Fa-f]{1,4}:){1,4}:255\.255\.255\.255)$/
// domain校验
const regexDomain = /^(?=^.{3,255}$)(http(s)?:\/\/)?(www\.)?[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+(:\d+)*(\/\w+\.\w+)*$/
export default class Parser {
constructor (columnList) {
@@ -860,7 +869,7 @@ export default class Parser {
item.value.label = isWrapped ? `'${this.delSingleQuote(label)}'` : `${this.delSingleQuote(label)}`
item.value.label1 = isWrapped ? `'%${this.delSingleQuote(label)}%'` : `%${this.delSingleQuote(label)}%`
}
item.column.type = 'string'
item.column.type = columnType.string
}
})
// 长度为1时即模糊搜索例如搜索框值为1.1.1.1则直接返回1.1.1.1
@@ -1111,7 +1120,7 @@ export default class Parser {
if (key === 'has') {
returnObj.key += 'has(' + obj.label + item.substring(item.indexOf(','), item.length) + ' AND '
} else {
returnObj.key += obj.label + ' ' + item.substring(item.toLowerCase().indexOf(key.toLowerCase()), item.length) + ' AND '
returnObj.key += obj.label + ' ' + item.substring(obj.label.length, item.length) + ' AND '
}
} else if (returnObj.isKey) {
returnObj.key = '[' + key + ']'
@@ -1126,12 +1135,12 @@ export default class Parser {
}
return returnObj
} else if (q.indexOf(' LIKE ') > -1 || q.indexOf(' like ') > -1) {
} else if (q.toLowerCase().indexOf(' like ') > -1) {
return {
key: q,
isKey: true
}
} else if (q.indexOf(' IN ') > -1 || q.indexOf(' in ') > -1) {
} else if (q.toLowerCase().indexOf(' in ') > -1) {
return {
key: q,
isKey: true
@@ -1145,15 +1154,79 @@ export default class Parser {
return { key: '[' + key + ']', isKey: false }
}
}
} else if (q && (q.indexOf(' IN ') > -1 || q.indexOf(' in ') > -1 || q.indexOf(' LIKE ') > -1 || q.indexOf(' like ') > -1)) {
return {
key: q,
isKey: true
}
} else if (q && (q.indexOf('has(') > -1)) {
return {
key: q,
isKey: true
} else if (q && (q.toLowerCase().indexOf(' in ') > -1 || q.toLowerCase().indexOf(' like ') > -1 || q.toLowerCase().indexOf('has(') > -1)) {
const lowerQ = q.toLowerCase()
if (lowerQ.indexOf(' and ') > -1) {
if (this.checkStrIncludeAnd(q)) {
q = q.replace(/ and /g, ' AND ')
}
const arr = q.split(' AND ')
for (let i = 0; i < arr.length; i++) {
const item = arr[i].toLowerCase()
if (item.indexOf(' like ') > -1) {
const key = item.substring(0, item.indexOf(' like '))
const obj = this.columnList.find(t => t.label.toLowerCase() === key.toLowerCase())
if (!obj) {
return { key: '[' + key + ']', isKey: false }
}
} else if (item.indexOf(' in ') > -1) {
const key = q.substring(0, q.indexOf(' in '))
const obj = this.columnList.find(t => t.label.toLowerCase() === key.toLowerCase())
if (!obj) {
return { key: '[' + key + ']', isKey: false }
}
} else if (item.indexOf('has(') > -1) {
const key = item.substring(0, 4)
if (key === 'has(') {
const label = item.substring(4, item.indexOf(','))
if (label) {
const obj = this.columnList.find(t => t.label.toLowerCase() === label.toLowerCase())
if (!obj) {
return { key: '[' + key + ']', isKey: false }
}
} else {
return { key: 'in index ' + q.indexOf('has('), isKey: false }
}
} else {
return { key: 'in index ' + q.indexOf('has('), isKey: false }
}
}
}
return { key: q, isKey: true }
} else if (lowerQ.indexOf(' like ') > -1) {
const key = q.substring(0, q.indexOf(' like '))
const obj = this.columnList.find(t => t.label.toLowerCase() === key.toLowerCase())
if (obj) {
return { key: obj.label + q.substring(lowerQ.indexOf(' like '), q.length), isKey: true }
} else {
return { key: '[' + key + ']', isKey: false }
}
} else if (lowerQ.indexOf(' in ') > -1) {
const key = lowerQ.substring(0, lowerQ.indexOf(' in '))
const obj = this.columnList.find(t => t.label.toLowerCase() === key.toLowerCase())
if (obj) {
return { key: obj.label + q.substring(lowerQ.indexOf(' in '), q.length), isKey: true }
} else {
return { key: '[' + key + ']', isKey: false }
}
} else if (lowerQ.indexOf('has(') > -1) {
const key = lowerQ.substring(0, 4)
if (key === 'has(') {
const label = lowerQ.substring(4, lowerQ.indexOf(','))
if (label) {
const obj = this.columnList.find(t => t.label.toLowerCase() === label.toLowerCase())
if (obj) {
return { key: 'has(' + obj.label + q.substring(lowerQ.indexOf(','), lowerQ.length), isKey: true }
} else {
return { key: '[' + key + ']', isKey: false }
}
} else {
return { key: 'in index 5', isKey: false }
}
} else {
return { key: 'in index 5', isKey: false }
}
}
} else {
return {
@@ -1173,7 +1246,9 @@ export default class Parser {
const arr = []
// 如果出现this.columnList中的字段如IP\Domain\App\Country等则不进行模糊搜索将str返回出去
this.columnList.forEach(item => {
arr.push(item.label.toLowerCase())
// todo 取消了大小写校验,后续观察是否出现问题
// arr.push(item.label.toLowerCase())
arr.push(item.label)
})
// 因为手动输入时可能会输入and所以将操作符的AND转换为and统一处理
@@ -1183,7 +1258,9 @@ export default class Parser {
newStr = newStr.replace(new RegExp(arr[i], 'g'), arr[i])
}
// 检查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) {
// 将单引号包裹的and拿出来放到数组tempList里原来的单引号包裹内容用temp即'it is test keyword{键值}'代替
// 再将字符串用and转换为数组遍历数组发现值为temp的获取键值根据键值获取tempList的值组合起来
@@ -1194,7 +1271,7 @@ export default class Parser {
// 将单引号包裹的and内容集合起来
while ((match = regex.exec(newStr)) !== null) {
if (match[1].includes('and')) {
if (match[1].includes(' and ')) {
tempList.push(match[1])
}
}
@@ -1212,8 +1289,9 @@ export default class Parser {
if (item.indexOf('it is test keyword') > -1) {
const regex = /\d+/g
const result1 = item.match(regex)
noAndList[index] = noAndList[index].replace(result1[0], '')
noAndList[index] = noAndList[index].replace('it is test keyword', tempList[result1[0]])
result1.forEach((r, i) => {
noAndList[index] = noAndList[index].replace('it is test keyword' + r, tempList[result1[i]])
})
}
})
@@ -1234,7 +1312,8 @@ export default class Parser {
// 不区分大小写用this.columnList里的label
arr.forEach(item => {
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()) {
str = this.checkFormatByStr(str)
}
@@ -1242,6 +1321,10 @@ export default class Parser {
})
return str
} else if (!result) {
// 此处为不能识别的字段不能当成app处理
if (str.indexOf('=') > -1 || str.toLowerCase().indexOf(' in ') > -1 || str.toLowerCase().indexOf(' like ') > -1 || str.toLowerCase().indexOf('has(') > -1) {
return str
}
const regex = /^["']|["']$/
// 去除两侧引号,如'1.1.1.1',避免校验时被当作app
if (regex.test(str)) {
@@ -1268,15 +1351,9 @@ export default class Parser {
if (str[0] === '%' && str[str.length - 1] !== '%') {
str = str.substring(1, str.length)
}
// ipv4校验
const regexIPv4 = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
// ipv6校验
const regexIPv6 = /^(?:(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}:){1,7}:|(?:[0-9A-Fa-f]{1,4}:){1,6}:[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}:){1,5}(?::[0-9A-Fa-f]{1,4}){1,2}|(?:[0-9A-Fa-f]{1,4}:){1,4}(?::[0-9A-Fa-f]{1,4}){1,3}|(?:[0-9A-Fa-f]{1,4}:){1,3}(?::[0-9A-Fa-f]{1,4}){1,4}|(?:[0-9A-Fa-f]{1,4}:){1,2}(?::[0-9A-Fa-f]{1,4}){1,5}|[0-9A-Fa-f]{1,4}:(?:(?::[0-9A-Fa-f]{1,4}){1,6})|:(?:(?::[0-9A-Fa-f]{1,4}){1,7}|:)|fe80:(?::[0-9A-Fa-f]{0,4}){0,4}%\w+|::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:(?:2[0-4]|1\d|[1-9])?\d|25[0-5])\.(?:(?:2[0-4]|1\d|[1-9])?\d|25[0-5])\.(?:(?:2[0-4]|1\d|[1-9])?\d|25[0-5])\.(?:(?:2[0-4]|1\d|[1-9])?\d|25[0-5])|(?:[0-9A-Fa-f]{1,4}:){1,4}:192\.88\.99\.(\d{1,3})|(?:[0-9A-Fa-f]{1,4}:){1,4}:192\.0\.2\.(\d{1,3})|(?:[0-9A-Fa-f]{1,4}:){1,4}:(?:[0-9A-Fa-f]{1,4}:){0,1}192\.0\.0\.(\d{1,3})|ff00:(?::[0-9A-Fa-f]{0,4}){0,4}|(?:[0-9A-Fa-f]{1,4}:){1,4}:255\.255\.255\.255)$/
// domain校验
const reg = /^(?=^.{3,255}$)(http(s)?:\/\/)?(www\.)?[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+(:\d+)*(\/\w+\.\w+)*$/
if (regexIPv4.test(str) || regexIPv6.test(str)) {
const obj = this.columnList.find(t => t.label.toLowerCase() === 'ip')
const obj = this.columnList.find(t => t.label.toLowerCase() === 'ip' || t.label.toLowerCase() === 'ip.addr')
if (obj) {
return `${obj.label}='${str}'`
} else {
@@ -1285,9 +1362,9 @@ export default class Parser {
}
return str
}
} else if (reg.test(str)) {
} else if (regexDomain.test(str)) {
// 只写作domain即可schema字段更改几次避免后续再更改直接拿this.columnList的label进行替换
const obj = this.columnList.find(t => t.label.toLowerCase() === 'domain')
const obj = this.columnList.find(t => t.label.toLowerCase() === 'domain' || t.label.toLowerCase() === 'domain.name')
if (obj) {
return `${obj.label} LIKE '%${str}'`
} else {
@@ -1297,7 +1374,7 @@ export default class Parser {
return str
}
} else {
const obj = this.columnList.find(t => t.label.toLowerCase() === 'app')
const obj = this.columnList.find(t => t.label.toLowerCase() === 'app' || t.label.toLowerCase() === 'app.name')
if (obj) {
return `${obj.label} LIKE '%${str}%'`
} else {
@@ -1309,18 +1386,14 @@ export default class Parser {
}
}
/**
* 判断传过来值的实体类型仅限于ip、domain、app
*/
getEntityTypeByValue (str) {
if (str[0] === "'" && str[str.length - 1] === "'") {
str = str.substring(1, str.length)
str = str.substring(0, str.length - 1)
}
// ipv4校验
const regexIPv4 = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
// ipv6校验
const regexIPv6 = /^(?:(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}:){1,7}:|(?:[0-9A-Fa-f]{1,4}:){1,6}:[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}:){1,5}(?::[0-9A-Fa-f]{1,4}){1,2}|(?:[0-9A-Fa-f]{1,4}:){1,4}(?::[0-9A-Fa-f]{1,4}){1,3}|(?:[0-9A-Fa-f]{1,4}:){1,3}(?::[0-9A-Fa-f]{1,4}){1,4}|(?:[0-9A-Fa-f]{1,4}:){1,2}(?::[0-9A-Fa-f]{1,4}){1,5}|[0-9A-Fa-f]{1,4}:(?:(?::[0-9A-Fa-f]{1,4}){1,6})|:(?:(?::[0-9A-Fa-f]{1,4}){1,7}|:)|fe80:(?::[0-9A-Fa-f]{0,4}){0,4}%\w+|::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:(?:2[0-4]|1\d|[1-9])?\d|25[0-5])\.(?:(?:2[0-4]|1\d|[1-9])?\d|25[0-5])\.(?:(?:2[0-4]|1\d|[1-9])?\d|25[0-5])\.(?:(?:2[0-4]|1\d|[1-9])?\d|25[0-5])|(?:[0-9A-Fa-f]{1,4}:){1,4}:192\.88\.99\.(\d{1,3})|(?:[0-9A-Fa-f]{1,4}:){1,4}:192\.0\.2\.(\d{1,3})|(?:[0-9A-Fa-f]{1,4}:){1,4}:(?:[0-9A-Fa-f]{1,4}:){0,1}192\.0\.0\.(\d{1,3})|ff00:(?::[0-9A-Fa-f]{0,4}){0,4}|(?:[0-9A-Fa-f]{1,4}:){1,4}:255\.255\.255\.255)$/
// domain校验
const reg = /^(?=^.{3,255}$)(http(s)?:\/\/)?(www\.)?[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+(:\d+)*(\/\w+\.\w+)*$/
if (regexIPv4.test(str) || regexIPv6.test(str)) {
const obj = this.columnList.find(t => t.label.toLowerCase() === 'ip')
if (obj) {
@@ -1328,7 +1401,7 @@ export default class Parser {
} else {
return str
}
} else if (reg.test(str)) {
} else if (regexDomain.test(str)) {
const obj = this.columnList.find(t => t.label.toLowerCase() === 'domain')
if (obj) {
return obj.label
@@ -1410,7 +1483,7 @@ export default class Parser {
lastObj[i] = `has(${i},${commonObj[i]})`
} else {
// 单独存在的,直接保留
lastObj[i] = `${i} = '${commonObj[i]}'`
lastObj[i] = `${i}=${commonObj[i]}`
}
}
@@ -1471,6 +1544,78 @@ export default class Parser {
return this.columnList.find(t => t.label.toLowerCase() === label.toLowerCase())
}
/**
* 检测str是否包含枚举字段包含的话进行替换
* @param str
* @returns {string|*}
*/
conversionEnum (str) {
if (str) {
let enumFlag = false // 判断字符串是否包含枚举类型的key不包含则直接返回
enumList.forEach(item => {
if (str.toLocaleLowerCase().indexOf(item.toLocaleLowerCase()) > -1) {
enumFlag = true
}
})
if (enumFlag) {
let key = _.cloneDeep(str)
let searchList = [] // 将字符串按AND分割成单独的搜索条件
if (key.indexOf(' AND ') > -1) {
searchList = key.split(' AND ')
} else {
searchList = [key]
}
searchList.forEach((item, index) => {
const obj = this.columnList.find(d => item.indexOf(d.label) > -1)
if (obj && obj.doc.data) {
for (let i = 0; i < obj.doc.data.length; i++) {
const item1 = obj.doc.data[i]
if (item.indexOf(item1.code) > -1) {
searchList[index] = searchList[index].replace(new RegExp(item1.code, 'g'), item1.value)
// 匹配到code终止匹配
break
} else {
// 该操作是避免中文参数切换到英文环境时code经i18n转为英文匹配不到中文参数的情况
Object.keys(store.state.i18nObj).forEach(lang => {
const i18nCode = store.state.i18nObj[lang][item1.code1]
if (item.indexOf(i18nCode) > -1) {
searchList[index] = searchList[index].replace(new RegExp(i18nCode, 'g'), item1.value)
}
})
}
}
}
})
key = searchList.join(' AND ')
return key
} else {
return str
}
} else {
return str
}
}
/**
* 获取关键字列表,即高亮字段
* @param metaList
* @returns {*[]}
*/
getKeywordList (metaList) {
const keywordList = []
if (metaList && metaList.length > 0) {
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 || item.column.label })
}
})
}
return keywordList
}
}
// 使用单引号包裹

View File

@@ -0,0 +1,171 @@
<template>
<div class="HelperInfo">
<div class="tips-container">
<Renderer ref="renderVm" :renderProps="renderProps" :renderFun="helperDataFun"></Renderer>
</div>
</div>
</template>
<script>
import operatorTips from '@/components/advancedSearch/showhint/const/operatorTips'
import defaultTips from '@/components/advancedSearch/showhint/const/defaultTips.js'
import sqlTips from '@/components/advancedSearch/showhint/const/sqlTips.js'
import functionTips from '@/components/advancedSearch/showhint/const/functionTips.js'
import filterTips from '@/components/advancedSearch/showhint/const/filterTips.js'
import varTips from '@/components/advancedSearch/showhint/const/varTips.js'
import { fieldRender } from '@/components/advancedSearch/showhint/const/fieldTips.js'
import { EN, storageKey, ZH } from '@/utils/constants'
export default {
name: 'HelperInfo',
inject: ['getDataset'],
props: {
hintSearch: {},
hintParams: {}
},
data () {
return {
operatorTips,
sqlTips,
functionTips,
renderProps: {},
isShow: true
}
},
methods: {
refreshRender () {
this.$refs?.renderVm.$forceUpdate()
},
matchFields (searchKey) {
const dataset = this.getDataset()
const fieldInfo = dataset.getFieldInfo(searchKey)
if (!fieldInfo) {
return false
}
let operates = dataset.getOperates(fieldInfo.type, fieldInfo._matchItem)
operates = operates.map(item => item.text.toUpperCase())
let funs = dataset.getFunctions(fieldInfo.type, fieldInfo._matchItem)
funs = funs.map(item => item.text.toUpperCase())
let operatorReference = dataset.sourceData.operatorReference
let funcReference = dataset.sourceData.funcReference
operatorReference = operatorReference.filter(item => {
return operates.includes(item.label)
})
funcReference = funcReference.filter(item => {
return funs.includes(item.name)
})
this.renderProps = {
fieldInfo,
funcReference,
operatorReference,
funs,
operates
}
return fieldRender
}
},
computed: {
helperDataFun () {
let hintSearch = ''
if (this.hintSearch) {
hintSearch = JSON.parse(JSON.stringify(this.hintSearch))
const fields = this.getDataset().sourceData.fields
const obj = fields.find(d => d.label === hintSearch)
if (obj) {
hintSearch = obj.label
}
}
let searchKey = hintSearch.toUpperCase() || ''
searchKey = searchKey.trim()
// if (functionTips[searchKey]) {
// return functionTips[searchKey].description
// }
if (operatorTips[searchKey]) {
return operatorTips[searchKey].description
}
// if (sqlTips[searchKey]) {
// return sqlTips[searchKey].description
// }
if (filterTips[searchKey]) {
return filterTips[searchKey].description
}
// if (varTips[searchKey]) {
// return varTips[searchKey].description
// }
// 完整的匹配关键字
if (this.getDataset()) {
const fieldRender = this.matchFields(searchKey)
if (fieldRender) {
return fieldRender
}
}
const language = localStorage.getItem(storageKey.language) || EN
if (language === ZH) {
return defaultTips.zhDefault.description
}
return defaultTips.enDefault.description
}
}
}
</script>
<style scoped>
.HelperInfo {
min-width: 450px;
background-color: #fff;
height: 294px;
box-sizing: border-box;
overflow: auto;
z-index: 99;
}
.tips-container {
padding: 12px;
}
/deep/ ul li {
list-style: inside;
line-height: 20px;
}
/deep/ h3 {
margin: 8px 0;
line-height: 22px;
}
/deep/ p {
line-height: 22px;
word-break: break-all;
}
/deep/ code,
.code {
background: initial;
border: 1px solid #DEDEDE;
height: 24px;
line-height: 24px;
padding: 0 12px;
margin: 6px 0;
display: block;
}
/deep/ .sub-url {
padding-left: 18px;
}
/deep/ .sub-url li {
list-style: inside circle;
}
/deep/ i.ref-txt {
line-height: 20px;
color: #aaa;
}
</style>

View File

@@ -0,0 +1,35 @@
<!-- 提示信息 -->
<template>
<div class="Hint" @click.stop>
<div class="hint__block">
<div class="hint__block-filter">
<hint-info v-on="$listeners" :hintList="hintList" @select="onSelect"></hint-info>
</div>
<div class="hint__block-helper">
<helper-info :hintSearch="hintSearch"></helper-info>
</div>
</div>
</div>
</template>
<script>
import HelperInfo from './HelperInfo.vue'
import HintInfo from './HintInfo.vue'
export default {
name: 'Hint',
props: {
hintList: [],
hintSearch: {}
},
components: {
HintInfo,
HelperInfo
},
methods: {
onSelect (item, index, hintList) {
this.$emit('select', item, index, hintList)
}
}
}
</script>

View File

@@ -0,0 +1,127 @@
<template>
<div class="HintInfo">
<ul style="padding-left: 0;margin: -10px 0 0 0;min-width: calc(100% - 12px)">
<template v-for="(item,index) in hintList" :key="index">
<li :ref="'hint_'+index" class="relative-item CodeMirror-hint"
style="margin-bottom: 2px"
@click="handleSelect(item,index,hintList)"
:class="{'CodeMirror-hint-active':index === activeIndex,[item.className]:true}"
>{{ item.displayText }}</li>
</template>
</ul>
</div>
</template>
<script>
export default {
name: 'HintInfo',
props: {
hintList: {
type: Array,
default () {
return []
}
}
},
data () {
return {
active: true,
activeIndex: null
}
},
mounted () {
// this.handleFocus()
this.$emit('load', {
name: this.name,
label: this.name,
vm: this
})
},
methods: {
scrollToView (index = 0) {
// 移动到可视区域
const li = this.$refs['hint_' + index][0]
li && li.scrollIntoView(false)
},
handleDown () {
if (!this.active) {
this.hintActive()
return
}
let nextIndex = this.activeIndex + 1
let nextItem = this.hintList[nextIndex]
if (nextItem?.type === 'abstract') {
nextIndex++
nextItem = this.hintList[nextIndex]
}
if (nextItem?.type === 'abstract') {
nextIndex++
}
nextIndex >= this.hintList.length ? this.activeIndex = 1 : this.activeIndex = nextIndex
this.scrollToView(this.activeIndex)
},
handleUp () {
if (!this.active) {
this.hintActive()
return
}
let preIndex = this.activeIndex - 1
let preItem = this.hintList[preIndex]
if (preItem?.type === 'abstract') {
preIndex--
preItem = this.hintList[preIndex]
}
if (preItem?.type === 'abstract') {
preIndex--
}
preIndex > 0 ? this.activeIndex = preIndex : this.activeIndex = this.hintList.length - 1
this.scrollToView(this.activeIndex)
},
hintActive () {
this.active = true
this.activeIndex = 1
this.scrollToView(this.activeIndex)
},
hintDeactive () {
this.active = false
this.activeIndex = null
},
handleSelect (item, index) {
this.$emit('select', item, index, this.hintList)
},
triggerSelect () {
const index = this.activeIndex || 0
const item = this.hintList[index]
this.$emit('select', item, index, this.hintList)
}
}
}
</script>
<style scoped>
.HintInfo {
height: 280px;
overflow: auto;
background: #fff;
}
ul {
min-width: 300px;
height:auto;
width: fit-content;
box-sizing: border-box;
padding-bottom: 10px;
}
.el-dropdown-menu__item{
text-indent: 1em;
font-size: 12px !important;
font-family: NotoSansSChineseRegular;
color: #575757;
font-weight: 400;
}
.hint-clear{
text-indent: 1em;
}
</style>

View File

@@ -0,0 +1,16 @@
<script>
export default {
name: 'Renderer',
// abstract: true,
props: {
renderFun: {
type: Function,
require: true
},
renderProps: {}
},
render (h) {
return this.renderFun(h, this.renderProps)
}
}
</script>

View File

@@ -0,0 +1,22 @@
import sqlHint from './sql-hint.js'
import showHint from './show-hint.js'
import manualShowHint from './manual-show-hint'
import 'codemirror/addon/hint/show-hint.css'
export default function createHint (hitType = 'default', CodeMirror, {
dataset,
hinthook,
keywordshook,
callback,
keyboardUp,
keyboardDown,
keyboardEnter
}) {
hitType === 'default' ? showHint(CodeMirror) : manualShowHint(CodeMirror, {
cb: callback,
keyboardUp,
keyboardDown,
keyboardEnter
})
sqlHint(CodeMirror, { dataset, hinthook, keywordshook })
}

View File

@@ -0,0 +1,596 @@
export default function (CodeMirror,{
cb,
keyboardUp,
keyboardDown,
keyboardEnter
}) {
var HINT_ELEMENT_CLASS = "CodeMirror-hint";
var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active";
// This is the old interface, kept around for now to stay
// backwards-compatible.
CodeMirror.showHint = function (cm, getHints, options) {
if (!getHints) return cm.showHint(options);
if (options && options.async) getHints.async = true;
var newOpts = {hint: getHints};
if (options) for (var prop in options) newOpts[prop] = options[prop];
return cm.showHint(newOpts);
};
CodeMirror.defineExtension("showHint", function (options) {
options = parseOptions(this, this.getCursor("start"), options);
var selections = this.listSelections()
if (selections.length > 1) return;
// By default, don't allow completion when something is selected.
// A hint function can have a `supportsSelection` property to
// indicate that it can handle selections.
if (this.somethingSelected()) {
if (!options.hint.supportsSelection) return;
// Don't try with cross-line selections
for (var i = 0; i < selections.length; i++)
if (selections[i].head.line != selections[i].anchor.line) return;
}
if (this.state.completionActive) this.state.completionActive.close();
var completion = this.state.completionActive = new Completion(this, options);
if (!completion.options.hint) return;
CodeMirror.signal(this, "startCompletion", this);
completion.update(true);
cb && cb({completion})
});
CodeMirror.defineExtension("closeHint", function () {
if (this.state.completionActive) this.state.completionActive.close()
})
function Completion(cm, options) {
this.cm = cm;
this.options = options;
this.widget = null;
this.debounce = 0;
this.tick = 0;
this.startPos = this.cm.getCursor("start");
this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length;
if (this.options.updateOnCursorActivity) {
var self = this;
cm.on("cursorActivity", this.activityFunc = function () {
self.cursorActivity();
});
}
}
var requestAnimationFrame = window.requestAnimationFrame || function (fn) {
return setTimeout(fn, 1000 / 60);
};
var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;
Completion.prototype = {
close: function () {
if (!this.active()) return;
this.cm.state.completionActive = null;
this.tick = null;
if (this.options.updateOnCursorActivity) {
this.cm.off("cursorActivity", this.activityFunc);
}
if (this.widget && this.data) CodeMirror.signal(this.data, "close");
if (this.widget) this.widget.close();
CodeMirror.signal(this.cm, "endCompletion", this.cm);
},
active: function () {
return this.cm.state.completionActive == this;
},
pick: function (data, i) {
var completion = data.list[i], self = this;
this.cm.operation(function () {
if (completion.hint)
completion.hint(self.cm, data, completion);
else
self.cm.replaceRange(getText(completion), completion.from || data.from,
completion.to || data.to, "complete");
CodeMirror.signal(data, "pick", completion);
self.cm.scrollIntoView();
});
if (this.options.closeOnPick) {
this.close();
}
},
cursorActivity: function () {
if (this.debounce) {
cancelAnimationFrame(this.debounce);
this.debounce = 0;
}
var identStart = this.startPos;
if (this.data) {
identStart = this.data.from;
}
var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line);
if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch ||
pos.ch < identStart.ch || this.cm.somethingSelected() ||
(!pos.ch || this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) {
this.close();
} else {
var self = this;
this.debounce = requestAnimationFrame(function () {
self.update();
});
if (this.widget) this.widget.disable();
}
},
update: function (first) {
if (this.tick == null) return
var self = this, myTick = ++this.tick
fetchHints(this.options.hint, this.cm, this.options, function (data) {
if (self.tick == myTick) self.finishUpdate(data, first)
})
},
finishUpdate: function (data, first) {
if (this.data) CodeMirror.signal(this.data, "update");
var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle);
if (this.widget) this.widget.close();
this.data = data;
if (data && data.list.length) {
if (picked && data.list.length == 1) {
this.pick(data, 0);
} else {
this.widget = new Widget(this, data);
CodeMirror.signal(data, "shown");
}
}
}
};
function parseOptions(cm, pos, options) {
var editor = cm.options.hintOptions;
var out = {};
for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
if (editor) for (var prop in editor)
if (editor[prop] !== undefined) out[prop] = editor[prop];
if (options) for (var prop in options)
if (options[prop] !== undefined) out[prop] = options[prop];
if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos)
return out;
}
function getText(completion) {
if (typeof completion == "string") return completion;
else return completion.text;
}
function buildKeyMap(completion, handle) {
var baseMap = {
Up: function () {
handle.moveFocus(-1);
},
Down: function () {
handle.moveFocus(1);
},
PageUp: function () {
handle.moveFocus(-handle.menuSize() + 1, true);
},
PageDown: function () {
handle.moveFocus(handle.menuSize() - 1, true);
},
Home: function () {
handle.setFocus(0);
},
End: function () {
handle.setFocus(handle.length - 1);
},
// Enter: handle.pick,
// Tab: handle.pick,
Tab: keyboardEnter,
Enter: keyboardEnter,
Esc: handle.close
};
var mac = /Mac/.test(navigator.platform);
if (mac) {
baseMap["Ctrl-P"] = function () {
handle.moveFocus(-1);
};
baseMap["Ctrl-N"] = function () {
handle.moveFocus(1);
};
}
var custom = completion.options.customKeys;
var ourMap = custom ? {} : baseMap;
function addBinding(key, val) {
var bound;
if (typeof val != "string")
bound = function (cm) {
return val(cm, handle);
};
// This mechanism is deprecated
else if (baseMap.hasOwnProperty(val))
bound = baseMap[val];
else
bound = val;
ourMap[key] = bound;
}
if (custom)
for (var key in custom) if (custom.hasOwnProperty(key))
addBinding(key, custom[key]);
var extra = completion.options.extraKeys;
if (extra)
for (var key in extra) if (extra.hasOwnProperty(key))
addBinding(key, extra[key]);
return ourMap;
}
function getHintElement(hintsElement, el) {
while (el && el != hintsElement) {
if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el;
el = el.parentNode;
}
}
function Widget(completion, data) {
this.id = "cm-complete-" + Math.floor(Math.random(1e6))
this.completion = completion;
this.data = data;
this.picked = false;
var widget = this, cm = completion.cm;
var ownerDocument = cm.getInputField().ownerDocument;
var parentWindow = ownerDocument.defaultView || ownerDocument.parentWindow;
var hints = this.hints = ownerDocument.createElement("ul");
// $(hints).append(`
// <h1>lalallalla</h1>
// `)
hints.setAttribute("role", "listbox")
hints.setAttribute("aria-expanded", "true")
hints.id = this.id
var theme = completion.cm.options.theme;
hints.className = "CodeMirror-hints " + theme;
this.selectedHint = data.selectedHint || 0;
var completions = data.list;
for (var i = 0; i < completions.length; ++i) {
var elt = hints.appendChild(ownerDocument.createElement("li")), cur = completions[i];
var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS);
if (cur.className != null) className = cur.className + " " + className;
elt.className = className;
if (i == this.selectedHint) elt.setAttribute("aria-selected", "true")
elt.id = this.id + "-" + i
elt.setAttribute("role", "option")
if (cur.render) cur.render(elt, data, cur);
else elt.appendChild(ownerDocument.createTextNode(cur.displayText || getText(cur)));
elt.hintId = i;
}
var container = completion.options.container || ownerDocument.body;
var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null);
var left = pos.left, top = pos.bottom, below = true;
var offsetLeft = 0, offsetTop = 0;
if (container !== ownerDocument.body) {
// We offset the cursor position because left and top are relative to the offsetParent's top left corner.
var isContainerPositioned = ['absolute', 'relative', 'fixed'].indexOf(parentWindow.getComputedStyle(container).position) !== -1;
var offsetParent = isContainerPositioned ? container : container.offsetParent;
var offsetParentPosition = offsetParent.getBoundingClientRect();
var bodyPosition = ownerDocument.body.getBoundingClientRect();
offsetLeft = (offsetParentPosition.left - bodyPosition.left - offsetParent.scrollLeft);
offsetTop = (offsetParentPosition.top - bodyPosition.top - offsetParent.scrollTop);
}
hints.style.left = (left - offsetLeft) + "px";
hints.style.top = (top - offsetTop) + "px";
// todo 隐藏codemirror自带的提示
hints.style.display = 'none'
// If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
var winW = parentWindow.innerWidth || Math.max(ownerDocument.body.offsetWidth, ownerDocument.documentElement.offsetWidth);
var winH = parentWindow.innerHeight || Math.max(ownerDocument.body.offsetHeight, ownerDocument.documentElement.offsetHeight);
//不用默认的DOM 下拉提示
// container.appendChild(hints);
hints.remove()
cm.getInputField().setAttribute("aria-autocomplete", "list")
cm.getInputField().setAttribute("aria-owns", this.id)
cm.getInputField().setAttribute("aria-activedescendant", this.id + "-" + this.selectedHint)
var box = completion.options.moveOnOverlap ? hints.getBoundingClientRect() : new DOMRect();
var scrolls = completion.options.paddingForScrollbar ? hints.scrollHeight > hints.clientHeight + 1 : false;
// Compute in the timeout to avoid reflow on init
var startScroll;
setTimeout(function () {
startScroll = cm.getScrollInfo();
});
var overlapY = box.bottom - winH;
if (overlapY > 0) {
var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);
if (curTop - height > 0) { // Fits above cursor
hints.style.top = (top = pos.top - height - offsetTop) + "px";
below = false;
} else if (height > winH) {
hints.style.height = (winH - 5) + "px";
hints.style.top = (top = pos.bottom - box.top - offsetTop) + "px";
var cursor = cm.getCursor();
if (data.from.ch != cursor.ch) {
pos = cm.cursorCoords(cursor);
hints.style.left = (left = pos.left - offsetLeft) + "px";
box = hints.getBoundingClientRect();
}
}
}
var overlapX = box.right - winW;
if (scrolls) overlapX += cm.display.nativeBarWidth;
if (overlapX > 0) {
if (box.right - box.left > winW) {
hints.style.width = (winW - 5) + "px";
overlapX -= (box.right - box.left) - winW;
}
hints.style.left = (left = Math.max(pos.left - overlapX - offsetLeft, 0)) + "px";
}
if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling)
node.style.paddingRight = cm.display.nativeBarWidth + "px"
// debugger
cm.addKeyMap(this.keyMap = buildKeyMap(completion, {
moveFocus: function (n, avoidWrap) {
n === -1 ? keyboardUp && keyboardUp() : keyboardDown && keyboardDown()
widget.changeActive(widget.selectedHint + n, avoidWrap);
},
setFocus: function (n) {
widget.changeActive(n);
},
menuSize: function () {
return widget.screenAmount();
},
length: completions.length,
close: function () {
completion.close();
},
pick: function () {
widget.pick();
},
data: data
}));
if (completion.options.closeOnUnfocus) {
var closingOnBlur;
cm.on("blur", this.onBlur = function () {
closingOnBlur = setTimeout(function () {
completion.close();
}, 100);
});
cm.on("focus", this.onFocus = function () {
clearTimeout(closingOnBlur);
});
}
cm.on("scroll", this.onScroll = function () {
var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
if (!startScroll) startScroll = cm.getScrollInfo();
var newTop = top + startScroll.top - curScroll.top;
var point = newTop - (parentWindow.pageYOffset || (ownerDocument.documentElement || ownerDocument.body).scrollTop);
if (!below) point += hints.offsetHeight;
if (point <= editor.top || point >= editor.bottom) return completion.close();
hints.style.top = newTop + "px";
hints.style.left = (left + startScroll.left - curScroll.left) + "px";
});
CodeMirror.on(hints, "dblclick", function (e) {
var t = getHintElement(hints, e.target || e.srcElement);
if (t && t.hintId != null) {
widget.changeActive(t.hintId);
widget.pick();
}
});
CodeMirror.on(hints, "click", function (e) {
var t = getHintElement(hints, e.target || e.srcElement);
if (t && t.hintId != null) {
widget.changeActive(t.hintId);
if (completion.options.completeOnSingleClick) widget.pick();
}
});
CodeMirror.on(hints, "mousedown", function () {
setTimeout(function () {
cm.focus();
}, 20);
});
// The first hint doesn't need to be scrolled to on init
var selectedHintRange = this.getSelectedHintRange();
if (selectedHintRange.from !== 0 || selectedHintRange.to !== 0) {
this.scrollToActive();
}
CodeMirror.signal(data, "select", completions[this.selectedHint], hints.childNodes[this.selectedHint]);
return true;
}
Widget.prototype = {
close: function () {
if (this.completion.widget != this) return;
this.completion.widget = null;
if (this.hints.parentNode) this.hints.parentNode.removeChild(this.hints);
this.completion.cm.removeKeyMap(this.keyMap);
var input = this.completion.cm.getInputField()
input.removeAttribute("aria-activedescendant")
input.removeAttribute("aria-owns")
var cm = this.completion.cm;
if (this.completion.options.closeOnUnfocus) {
cm.off("blur", this.onBlur);
cm.off("focus", this.onFocus);
}
cm.off("scroll", this.onScroll);
},
disable: function () {
this.completion.cm.removeKeyMap(this.keyMap);
var widget = this;
this.keyMap = {
Enter: function () {
widget.picked = true;
}
};
this.completion.cm.addKeyMap(this.keyMap);
},
pick: function () {
this.completion.pick(this.data, this.selectedHint);
},
changeActive: function (i, avoidWrap) {
if (i >= this.data.list.length)
i = avoidWrap ? this.data.list.length - 1 : 0;
else if (i < 0)
i = avoidWrap ? 0 : this.data.list.length - 1;
if (this.selectedHint == i) return;
var node = this.hints.childNodes[this.selectedHint];
if (node) {
node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, "");
node.removeAttribute("aria-selected")
}
node = this.hints.childNodes[this.selectedHint = i];
node.className += " " + ACTIVE_HINT_ELEMENT_CLASS;
node.setAttribute("aria-selected", "true")
this.completion.cm.getInputField().setAttribute("aria-activedescendant", node.id)
this.scrollToActive()
CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node);
},
scrollToActive: function () {
var selectedHintRange = this.getSelectedHintRange();
var node1 = this.hints.childNodes[selectedHintRange.from];
var node2 = this.hints.childNodes[selectedHintRange.to];
var firstNode = this.hints.firstChild;
if (node1.offsetTop < this.hints.scrollTop)
this.hints.scrollTop = node1.offsetTop - firstNode.offsetTop;
else if (node2.offsetTop + node2.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)
this.hints.scrollTop = node2.offsetTop + node2.offsetHeight - this.hints.clientHeight + firstNode.offsetTop;
},
screenAmount: function () {
return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;
},
getSelectedHintRange: function () {
var margin = this.completion.options.scrollMargin || 0;
return {
from: Math.max(0, this.selectedHint - margin),
to: Math.min(this.data.list.length - 1, this.selectedHint + margin),
};
}
};
function applicableHelpers(cm, helpers) {
if (!cm.somethingSelected()) return helpers
var result = []
for (var i = 0; i < helpers.length; i++)
if (helpers[i].supportsSelection) result.push(helpers[i])
return result
}
function fetchHints(hint, cm, options, callback) {
if (hint.async) {
hint(cm, callback, options)
} else {
var result = hint(cm, options)
if (result && result.then) result.then(callback)
else callback(result)
}
}
function resolveAutoHints(cm, pos) {
var helpers = cm.getHelpers(pos, "hint"), words
if (helpers.length) {
var resolved = function (cm, callback, options) {
var app = applicableHelpers(cm, helpers);
function run(i) {
if (i == app.length) return callback(null)
fetchHints(app[i], cm, options, function (result) {
if (result && result.list.length > 0) callback(result)
else run(i + 1)
})
}
run(0)
}
resolved.async = true
resolved.supportsSelection = true
return resolved
} else if (words = cm.getHelper(cm.getCursor(), "hintWords")) {
return function (cm) {
return CodeMirror.hint.fromList(cm, {words: words})
}
} else if (CodeMirror.hint.anyword) {
return function (cm, options) {
return CodeMirror.hint.anyword(cm, options)
}
} else {
return function () {
}
}
}
CodeMirror.registerHelper("hint", "auto", {
resolve: resolveAutoHints
});
CodeMirror.registerHelper("hint", "fromList", function (cm, options) {
var cur = cm.getCursor(), token = cm.getTokenAt(cur)
var term, from = CodeMirror.Pos(cur.line, token.start), to = cur
if (token.start < cur.ch && /\w/.test(token.string.charAt(cur.ch - token.start - 1))) {
term = token.string.substr(0, cur.ch - token.start)
} else {
term = ""
from = cur
}
var found = [];
for (var i = 0; i < options.words.length; i++) {
var word = options.words[i];
if (word.slice(0, term.length) == term)
found.push(word);
}
if (found.length) return {list: found, from: from, to: to};
});
CodeMirror.commands.autocomplete = CodeMirror.showHint;
var defaultOptions = {
hint: CodeMirror.hint.auto,
completeSingle: true,
alignWithWord: true,
closeCharacters: /[\s()\[\]{};:>,]/,
closeOnPick: false,
closeOnUnfocus: false, //阻止提示信息 失焦关闭
// closeOnUnfocus: false,
updateOnCursorActivity: true,
completeOnSingleClick: true,
container: null,
customKeys: null,
extraKeys: null,
paddingForScrollbar: true,
moveOnOverlap: true,
};
CodeMirror.defineOption("hintOptions", null);
}

View File

@@ -0,0 +1,227 @@
//正则 向前去找关键字
/* 用于匹配关系数据 */
function matchOperator(CodeMirror, hintParams = {}) {
var editor = hintParams.editor;
var Pos = CodeMirror.Pos
var cur = hintParams.cur;
var token = hintParams.token;
if (!editor) {
return
}
var start = token.start;
var cont = true;
var leftTokenGroup = [];
while (cont) {
start = token.start;
leftTokenGroup.unshift(token);
token = editor.getTokenAt(Pos(cur.line, token.start));
cont = !(token.string.match(/^[ ]*$/) || start === 0); //只用空格做终止条件
}
var cursorLeftString = editor.getRange(Pos(cur.line, leftTokenGroup[0].start), Pos(cur.line, leftTokenGroup[leftTokenGroup.length - 1].end))
//判断是不是满足 运算符 表达式的正则
//test 如果是ig 会改变正则指针: https://my.oschina.net/jamesview/blog/5460753
var reg = /^(.*?)(=|!=|>|<|>=|<=)([^ ]*?)$/;
if (reg.test(cursorLeftString)) {
var execArr = reg.exec(cursorLeftString) || []
return {
leftTokenGroup,
cursorLeftString,
label: execArr[1],
sign: execArr[2],
value: execArr[3],
}
}
return null
}
function matchCommon(CodeMirror, hintParams = {}) {
//通用情况 QUANTILE(expr,level) 左括号右侧第一个就是 关键字
var editor = hintParams.editor;
var Pos = CodeMirror.Pos
var cur = hintParams.cur;
var token = hintParams.token;
if (!editor) {
return
}
var start = token.start;
var cont = true;
var leftTokenGroup = [];
while (cont) {
start = token.start;
leftTokenGroup.unshift(token);
token = editor.getTokenAt(Pos(cur.line, token.start));
cont = !(token.string.match(/^[ (]*$/) || start === 0); //括号或者空格为终止条件
//括号补上
if (token.string === '(') {
leftTokenGroup.unshift(token);
}
}
var cursorLeftString = editor.getRange(Pos(cur.line, leftTokenGroup[0].start), Pos(cur.line, leftTokenGroup[leftTokenGroup.length - 1].end))
//判断是不是满足 运算符 表达式的正则
var reg = /^\((.*?),([^ ]*)$/;
if (reg.test(cursorLeftString)) {
var execArr = reg.exec(cursorLeftString) || []
return {
leftTokenGroup,
cursorLeftString,
label: execArr[1],
sign: 'unknown', //没必要判断
value: execArr[2],
}
}
return null
}
function matchIn(CodeMirror, hintParams = {}) {
//in 的情况比较特殊
//通用情况 expr not in (values) expr in (values)
var editor = hintParams.editor;
var Pos = CodeMirror.Pos
var cur = hintParams.cur;
var token = hintParams.token;
if (!editor) {
return
}
var start = token.start;
var cont = true;
var leftTokenGroup = [];
//找到左括号
while (cont) {
start = token.start;
leftTokenGroup.unshift(token);
token = editor.getTokenAt(Pos(cur.line, token.start));
cont = !(token.string.match(/^[ (]*$/) || start === 0); //括号或者空格为终止条件
//左括号补上
if (token.string === '(') {
leftTokenGroup.unshift(token);
}
}
//左括号继续向右找
cont = true
while (cont) {
start = token.start;
leftTokenGroup.unshift(token);
token = editor.getTokenAt(Pos(cur.line, token.start));
cont = !(!token.string.match(/^(in|not| )/g) || start === 0); //括号或者空格为终止条件
//string-2
if (token.type === 'string-2') {
leftTokenGroup.unshift(token);
}
}
var cursorLeftString = editor.getRange(Pos(cur.line, leftTokenGroup[0].start), Pos(cur.line, leftTokenGroup[leftTokenGroup.length - 1].end))
//判断是不是满足 运算符 表达式的正则
var reg = /^(.*?)[ ]+((?:not[ ]+)?in)[ ]*\(([^ ]*?)$/i;
if (reg.test(cursorLeftString)) {
var execArr = reg.exec(cursorLeftString) || []
return {
leftTokenGroup,
cursorLeftString,
label: execArr[1],
sign: execArr[2],
value: execArr[3],
}
}
return null
}
function matchLike(CodeMirror, hintParams = {}) {
//like 的情况比较特殊 expr like value , expr not like value
var editor = hintParams.editor;
var Pos = CodeMirror.Pos
var cur = hintParams.cur;
var token = hintParams.token;
if (!editor) {
return
}
var start = token.start;
var cont = true;
var leftTokenGroup = [];
//找到左括号
while (cont) {
start = token.start;
leftTokenGroup.unshift(token);
token = editor.getTokenAt(Pos(cur.line, token.start));
cont = !(token.string.match(/^[ ]*$/) || start === 0); //括号或者空格为终止条件
}
//左括号继续向右找
cont = true
while (cont) {
start = token.start;
leftTokenGroup.unshift(token);
token = editor.getTokenAt(Pos(cur.line, token.start));
cont = !(!token.string.match(/^(like|not| )/g) || start === 0); //括号或者空格为终止条件
//string-2
if (token.type === 'string-2') {
leftTokenGroup.unshift(token);
}
}
var cursorLeftString = editor.getRange(Pos(cur.line, leftTokenGroup[0].start), Pos(cur.line, leftTokenGroup[leftTokenGroup.length - 1].end))
//判断是不是满足 运算符 表达式的正则
var reg = /^(.*?)[ ]+((?:not[ ]+)?like)[ ]*([^ ]*?)$/i;
if (reg.test(cursorLeftString)) {
var execArr = reg.exec(cursorLeftString) || []
return {
leftTokenGroup,
cursorLeftString,
label: execArr[1],
sign: execArr[2],
value: execArr[3],
}
}
return null
}
export const matchMain = (CodeMirror, params, manualParams = {}) => {
var matchRes = null
//匹配 运算符表达式
matchRes = matchOperator(CodeMirror, manualParams)
if (matchRes) {
return matchRes
}
//匹配 in表达式
matchRes = matchIn(CodeMirror, manualParams)
if (matchRes) {
return matchRes
}
//匹配 like表达式
matchRes = matchLike(CodeMirror, manualParams)
if (matchRes) {
return matchRes
}
//这里缺少一个对 count(distinct expr) 模式的匹配, 感觉大数据涉及存在缺陷, 先暂时不写
//匹配 其他表达式 (expr,value1,value2....) 模式的匹配
matchRes = matchCommon(CodeMirror, manualParams)
if (matchRes) {
// 说明这是一个 运算符表达式
return matchRes
}
return matchRes
}

View File

@@ -0,0 +1,591 @@
export default function showHint(CodeMirror) {
"use strict";
var HINT_ELEMENT_CLASS = "CodeMirror-hint";
var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active";
// This is the old interface, kept around for now to stay
// backwards-compatible.
CodeMirror.showHint = function (cm, getHints, options) {
if (!getHints) return cm.showHint(options);
if (options && options.async) getHints.async = true;
var newOpts = {hint: getHints};
if (options) for (var prop in options) newOpts[prop] = options[prop];
return cm.showHint(newOpts);
};
CodeMirror.defineExtension("showHint", function (options) {
options = parseOptions(this, this.getCursor("start"), options);
var selections = this.listSelections()
if (selections.length > 1) return;
// By default, don't allow completion when something is selected.
// A hint function can have a `supportsSelection` property to
// indicate that it can handle selections.
if (this.somethingSelected()) {
if (!options.hint.supportsSelection) return;
// Don't try with cross-line selections
for (var i = 0; i < selections.length; i++)
if (selections[i].head.line != selections[i].anchor.line) return;
}
if (this.state.completionActive) this.state.completionActive.close();
var completion = this.state.completionActive = new Completion(this, options);
if (!completion.options.hint) return;
CodeMirror.signal(this, "startCompletion", this);
completion.update(true);
});
CodeMirror.defineExtension("closeHint", function () {
if (this.state.completionActive) this.state.completionActive.close()
})
function Completion(cm, options) {
this.cm = cm;
this.options = options;
this.widget = null;
this.debounce = 0;
this.tick = 0;
this.startPos = this.cm.getCursor("start");
this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length;
if (this.options.updateOnCursorActivity) {
var self = this;
cm.on("cursorActivity", this.activityFunc = function () {
self.cursorActivity();
});
}
}
var requestAnimationFrame = window.requestAnimationFrame || function (fn) {
return setTimeout(fn, 1000 / 60);
};
var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;
Completion.prototype = {
close: function () {
if (!this.active()) return;
this.cm.state.completionActive = null;
this.tick = null;
if (this.options.updateOnCursorActivity) {
this.cm.off("cursorActivity", this.activityFunc);
}
if (this.widget && this.data) CodeMirror.signal(this.data, "close");
if (this.widget) this.widget.close();
CodeMirror.signal(this.cm, "endCompletion", this.cm);
},
active: function () {
return this.cm.state.completionActive == this;
},
pick: function (data, i) {
var completion = data.list[i], self = this;
this.cm.operation(function () {
if (completion.hint)
completion.hint(self.cm, data, completion);
else
self.cm.replaceRange(getText(completion), completion.from || data.from,
completion.to || data.to, "complete");
CodeMirror.signal(data, "pick", completion);
self.cm.scrollIntoView();
});
if (this.options.closeOnPick) {
this.close();
}
},
cursorActivity: function () {
if (this.debounce) {
cancelAnimationFrame(this.debounce);
this.debounce = 0;
}
var identStart = this.startPos;
if (this.data) {
identStart = this.data.from;
}
var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line);
if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch ||
pos.ch < identStart.ch || this.cm.somethingSelected() ||
(!pos.ch || this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) {
this.close();
} else {
var self = this;
this.debounce = requestAnimationFrame(function () {
self.update();
});
if (this.widget) this.widget.disable();
}
},
update: function (first) {
if (this.tick == null) return
var self = this, myTick = ++this.tick
fetchHints(this.options.hint, this.cm, this.options, function (data) {
if (self.tick == myTick) self.finishUpdate(data, first)
})
},
finishUpdate: function (data, first) {
if (this.data) CodeMirror.signal(this.data, "update");
var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle);
if (this.widget) this.widget.close();
this.data = data;
if (data && data.list.length) {
if (picked && data.list.length == 1) {
this.pick(data, 0);
} else {
this.widget = new Widget(this, data);
CodeMirror.signal(data, "shown");
}
}
}
};
function parseOptions(cm, pos, options) {
var editor = cm.options.hintOptions;
var out = {};
for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
if (editor) for (var prop in editor)
if (editor[prop] !== undefined) out[prop] = editor[prop];
if (options) for (var prop in options)
if (options[prop] !== undefined) out[prop] = options[prop];
if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos)
return out;
}
function getText(completion) {
if (typeof completion == "string") return completion;
else return completion.text;
}
function buildKeyMap(completion, handle) {
var baseMap = {
Up: function () {
handle.moveFocus(-1);
},
Down: function () {
handle.moveFocus(1);
},
PageUp: function () {
handle.moveFocus(-handle.menuSize() + 1, true);
},
PageDown: function () {
handle.moveFocus(handle.menuSize() - 1, true);
},
Home: function () {
handle.setFocus(0);
},
End: function () {
handle.setFocus(handle.length - 1);
},
Enter: handle.pick,
Tab: handle.pick,
Esc: handle.close
};
var mac = /Mac/.test(navigator.platform);
if (mac) {
baseMap["Ctrl-P"] = function () {
handle.moveFocus(-1);
};
baseMap["Ctrl-N"] = function () {
handle.moveFocus(1);
};
}
var custom = completion.options.customKeys;
var ourMap = custom ? {} : baseMap;
function addBinding(key, val) {
var bound;
if (typeof val != "string")
bound = function (cm) {
return val(cm, handle);
};
// This mechanism is deprecated
else if (baseMap.hasOwnProperty(val))
bound = baseMap[val];
else
bound = val;
ourMap[key] = bound;
}
if (custom)
for (var key in custom) if (custom.hasOwnProperty(key))
addBinding(key, custom[key]);
var extra = completion.options.extraKeys;
if (extra)
for (var key in extra) if (extra.hasOwnProperty(key))
addBinding(key, extra[key]);
return ourMap;
}
function getHintElement(hintsElement, el) {
while (el && el != hintsElement) {
if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el;
el = el.parentNode;
}
}
function Widget(completion, data) {
this.id = "cm-complete-" + Math.floor(Math.random(1e6))
this.completion = completion;
this.data = data;
this.picked = false;
var widget = this, cm = completion.cm;
var ownerDocument = cm.getInputField().ownerDocument;
var parentWindow = ownerDocument.defaultView || ownerDocument.parentWindow;
var hints = this.hints = ownerDocument.createElement("ul");
// $(hints).append(`
// <h1>lalallalla</h1>
// `)
hints.setAttribute("role", "listbox")
hints.setAttribute("aria-expanded", "true")
hints.id = this.id
var theme = completion.cm.options.theme;
hints.className = "CodeMirror-hints " + theme;
this.selectedHint = data.selectedHint || 0;
var completions = data.list;
for (var i = 0; i < completions.length; ++i) {
var elt = hints.appendChild(ownerDocument.createElement("li")), cur = completions[i];
var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS);
if (cur.className != null) className = cur.className + " " + className;
elt.className = className;
if (i == this.selectedHint) elt.setAttribute("aria-selected", "true")
elt.id = this.id + "-" + i
elt.setAttribute("role", "option")
if (cur.render) cur.render(elt, data, cur);
else elt.appendChild(ownerDocument.createTextNode(cur.displayText || getText(cur)));
elt.hintId = i;
}
var container = completion.options.container || ownerDocument.body;
var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null);
var left = pos.left, top = pos.bottom, below = true;
var offsetLeft = 0, offsetTop = 0;
if (container !== ownerDocument.body) {
// We offset the cursor position because left and top are relative to the offsetParent's top left corner.
var isContainerPositioned = ['absolute', 'relative', 'fixed'].indexOf(parentWindow.getComputedStyle(container).position) !== -1;
var offsetParent = isContainerPositioned ? container : container.offsetParent;
var offsetParentPosition = offsetParent.getBoundingClientRect();
var bodyPosition = ownerDocument.body.getBoundingClientRect();
offsetLeft = (offsetParentPosition.left - bodyPosition.left - offsetParent.scrollLeft);
offsetTop = (offsetParentPosition.top - bodyPosition.top - offsetParent.scrollTop);
}
hints.style.left = (left - offsetLeft) + "px";
hints.style.top = (top - offsetTop) + "px";
// todo 隐藏codemirror自带的提示
hints.style.display = 'none'
// If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
var winW = parentWindow.innerWidth || Math.max(ownerDocument.body.offsetWidth, ownerDocument.documentElement.offsetWidth);
var winH = parentWindow.innerHeight || Math.max(ownerDocument.body.offsetHeight, ownerDocument.documentElement.offsetHeight);
//在这里 添加的DOM 元素 -- 该方案 实现复杂算了吧
container.appendChild(hints);
// debugger
// $(container).append(
// `
// <div>
// <h1>hahah</h1>
// ${ hints }
// </div>
// `
// )
cm.getInputField().setAttribute("aria-autocomplete", "list")
cm.getInputField().setAttribute("aria-owns", this.id)
cm.getInputField().setAttribute("aria-activedescendant", this.id + "-" + this.selectedHint)
var box = completion.options.moveOnOverlap ? hints.getBoundingClientRect() : new DOMRect();
var scrolls = completion.options.paddingForScrollbar ? hints.scrollHeight > hints.clientHeight + 1 : false;
// Compute in the timeout to avoid reflow on init
var startScroll;
setTimeout(function () {
startScroll = cm.getScrollInfo();
});
var overlapY = box.bottom - winH;
if (overlapY > 0) {
var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);
if (curTop - height > 0) { // Fits above cursor
hints.style.top = (top = pos.top - height - offsetTop) + "px";
below = false;
} else if (height > winH) {
hints.style.height = (winH - 5) + "px";
hints.style.top = (top = pos.bottom - box.top - offsetTop) + "px";
var cursor = cm.getCursor();
if (data.from.ch != cursor.ch) {
pos = cm.cursorCoords(cursor);
hints.style.left = (left = pos.left - offsetLeft) + "px";
box = hints.getBoundingClientRect();
}
}
}
var overlapX = box.right - winW;
if (scrolls) overlapX += cm.display.nativeBarWidth;
if (overlapX > 0) {
if (box.right - box.left > winW) {
hints.style.width = (winW - 5) + "px";
overlapX -= (box.right - box.left) - winW;
}
hints.style.left = (left = Math.max(pos.left - overlapX - offsetLeft, 0)) + "px";
}
if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling)
node.style.paddingRight = cm.display.nativeBarWidth + "px"
// debugger
cm.addKeyMap(this.keyMap = buildKeyMap(completion, {
moveFocus: function (n, avoidWrap) {
widget.changeActive(widget.selectedHint + n, avoidWrap);
},
setFocus: function (n) {
widget.changeActive(n);
},
menuSize: function () {
return widget.screenAmount();
},
length: completions.length,
close: function () {
completion.close();
},
pick: function () {
widget.pick();
},
data: data
}));
if (completion.options.closeOnUnfocus) {
var closingOnBlur;
cm.on("blur", this.onBlur = function () {
closingOnBlur = setTimeout(function () {
completion.close();
}, 100);
});
cm.on("focus", this.onFocus = function () {
clearTimeout(closingOnBlur);
});
}
cm.on("scroll", this.onScroll = function () {
var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
if (!startScroll) startScroll = cm.getScrollInfo();
var newTop = top + startScroll.top - curScroll.top;
var point = newTop - (parentWindow.pageYOffset || (ownerDocument.documentElement || ownerDocument.body).scrollTop);
if (!below) point += hints.offsetHeight;
if (point <= editor.top || point >= editor.bottom) return completion.close();
hints.style.top = newTop + "px";
hints.style.left = (left + startScroll.left - curScroll.left) + "px";
});
CodeMirror.on(hints, "dblclick", function (e) {
var t = getHintElement(hints, e.target || e.srcElement);
if (t && t.hintId != null) {
widget.changeActive(t.hintId);
widget.pick();
}
});
CodeMirror.on(hints, "click", function (e) {
var t = getHintElement(hints, e.target || e.srcElement);
if (t && t.hintId != null) {
widget.changeActive(t.hintId);
if (completion.options.completeOnSingleClick) widget.pick();
}
});
CodeMirror.on(hints, "mousedown", function () {
setTimeout(function () {
cm.focus();
}, 20);
});
// The first hint doesn't need to be scrolled to on init
var selectedHintRange = this.getSelectedHintRange();
if (selectedHintRange.from !== 0 || selectedHintRange.to !== 0) {
this.scrollToActive();
}
CodeMirror.signal(data, "select", completions[this.selectedHint], hints.childNodes[this.selectedHint]);
return true;
}
Widget.prototype = {
close: function () {
if (this.completion.widget != this) return;
this.completion.widget = null;
if (this.hints.parentNode) this.hints.parentNode.removeChild(this.hints);
this.completion.cm.removeKeyMap(this.keyMap);
var input = this.completion.cm.getInputField()
input.removeAttribute("aria-activedescendant")
input.removeAttribute("aria-owns")
var cm = this.completion.cm;
if (this.completion.options.closeOnUnfocus) {
cm.off("blur", this.onBlur);
cm.off("focus", this.onFocus);
}
cm.off("scroll", this.onScroll);
},
disable: function () {
this.completion.cm.removeKeyMap(this.keyMap);
var widget = this;
this.keyMap = {
Enter: function () {
widget.picked = true;
}
};
this.completion.cm.addKeyMap(this.keyMap);
},
pick: function () {
this.completion.pick(this.data, this.selectedHint);
},
changeActive: function (i, avoidWrap) {
if (i >= this.data.list.length)
i = avoidWrap ? this.data.list.length - 1 : 0;
else if (i < 0)
i = avoidWrap ? 0 : this.data.list.length - 1;
if (this.selectedHint == i) return;
var node = this.hints.childNodes[this.selectedHint];
if (node) {
node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, "");
node.removeAttribute("aria-selected")
}
node = this.hints.childNodes[this.selectedHint = i];
node.className += " " + ACTIVE_HINT_ELEMENT_CLASS;
node.setAttribute("aria-selected", "true")
this.completion.cm.getInputField().setAttribute("aria-activedescendant", node.id)
this.scrollToActive()
CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node);
},
scrollToActive: function () {
var selectedHintRange = this.getSelectedHintRange();
var node1 = this.hints.childNodes[selectedHintRange.from];
var node2 = this.hints.childNodes[selectedHintRange.to];
var firstNode = this.hints.firstChild;
if (node1.offsetTop < this.hints.scrollTop)
this.hints.scrollTop = node1.offsetTop - firstNode.offsetTop;
else if (node2.offsetTop + node2.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)
this.hints.scrollTop = node2.offsetTop + node2.offsetHeight - this.hints.clientHeight + firstNode.offsetTop;
},
screenAmount: function () {
return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;
},
getSelectedHintRange: function () {
var margin = this.completion.options.scrollMargin || 0;
return {
from: Math.max(0, this.selectedHint - margin),
to: Math.min(this.data.list.length - 1, this.selectedHint + margin),
};
}
};
function applicableHelpers(cm, helpers) {
if (!cm.somethingSelected()) return helpers
var result = []
for (var i = 0; i < helpers.length; i++)
if (helpers[i].supportsSelection) result.push(helpers[i])
return result
}
function fetchHints(hint, cm, options, callback) {
if (hint.async) {
hint(cm, callback, options)
} else {
var result = hint(cm, options)
if (result && result.then) result.then(callback)
else callback(result)
}
}
function resolveAutoHints(cm, pos) {
var helpers = cm.getHelpers(pos, "hint"), words
if (helpers.length) {
var resolved = function (cm, callback, options) {
var app = applicableHelpers(cm, helpers);
function run(i) {
if (i == app.length) return callback(null)
fetchHints(app[i], cm, options, function (result) {
if (result && result.list.length > 0) callback(result)
else run(i + 1)
})
}
run(0)
}
resolved.async = true
resolved.supportsSelection = true
return resolved
} else if (words = cm.getHelper(cm.getCursor(), "hintWords")) {
return function (cm) {
return CodeMirror.hint.fromList(cm, {words: words})
}
} else if (CodeMirror.hint.anyword) {
return function (cm, options) {
return CodeMirror.hint.anyword(cm, options)
}
} else {
return function () {
}
}
}
CodeMirror.registerHelper("hint", "auto", {
resolve: resolveAutoHints
});
CodeMirror.registerHelper("hint", "fromList", function (cm, options) {
var cur = cm.getCursor(), token = cm.getTokenAt(cur)
var term, from = CodeMirror.Pos(cur.line, token.start), to = cur
if (token.start < cur.ch && /\w/.test(token.string.charAt(cur.ch - token.start - 1))) {
term = token.string.substr(0, cur.ch - token.start)
} else {
term = ""
from = cur
}
var found = [];
for (var i = 0; i < options.words.length; i++) {
var word = options.words[i];
if (word.slice(0, term.length) == term)
found.push(word);
}
if (found.length) return {list: found, from: from, to: to};
});
CodeMirror.commands.autocomplete = CodeMirror.showHint;
var defaultOptions = {
hint: CodeMirror.hint.auto,
completeSingle: true,
alignWithWord: true,
closeCharacters: /[\s()\[\]{};:>,]/,
closeOnPick: true,
closeOnUnfocus: true,
updateOnCursorActivity: true,
completeOnSingleClick: true,
container: null,
customKeys: null,
extraKeys: null,
paddingForScrollbar: true,
moveOnOverlap: true,
};
CodeMirror.defineOption("hintOptions", null);
}

View File

@@ -0,0 +1,372 @@
import {cloneDeep} from 'lodash'
import {matchMain} from './matchRelatedInfo'
export default function (CodeMirror,
{
dataset,
hinthook, //生成提示的hook 拦截
keywordshook //关键字hook 在关键字里面
}
) {
// "use strict";
var tables;
var defaultTable;
var keywords;
var identifierQuote;
var CONS = {
QUERY_DIV: ";",
ALIAS_KEYWORD: "AS"
};
var Pos = CodeMirror.Pos, cmpPos = CodeMirror.cmpPos;
function isArray(val) {
return Object.prototype.toString.call(val) == "[object Array]"
}
function getKeywords(editor) {
var mode = editor.doc.modeOption;
if (mode === "sql") mode = "text/x-sql";
keywords = CodeMirror.resolveMode(mode).keywords;
if (keywordshook) {
keywords = keywordshook(keywords, CodeMirror.resolveMode(mode)) || keywords
}
return keywords
}
function getIdentifierQuote(editor) {
var mode = editor.doc.modeOption;
if (mode === "sql") mode = "text/x-sql";
return CodeMirror.resolveMode(mode).identifierQuote || "`";
}
function getText(item) {
return typeof item == "string" ? item : item.text;
}
function wrapTable(name, value) {
if (isArray(value)) value = {columns: value}
if (!value.text) value.text = name
return value
}
function parseTables(input) {
//table 名称变大写 统一变成对象格式 columns text
var result = {}
if (isArray(input)) {
for (var i = input.length - 1; i >= 0; i--) {
var item = input[i]
result[getText(item).toUpperCase()] = wrapTable(getText(item), item)
}
} else if (input) {
for (var name in input)
result[name.toUpperCase()] = wrapTable(name, input[name])
}
return result
}
function getTable(name) {
return tables[name.toUpperCase()]
}
function shallowClone(object) {
var result = {};
for (var key in object) if (object.hasOwnProperty(key))
result[key] = object[key];
return result;
}
function match(string, word) {
var len = string.length;
var sub = getText(word).substr(0, len);
return string.toUpperCase() === sub.toUpperCase();
}
function addMatches(result, search, wordlist, formatter) {
if (isArray(wordlist)) {
for (var i = 0; i < wordlist.length; i++)
if (match(search, wordlist[i])) result.push(formatter(wordlist[i]))
} else {
for (var word in wordlist) if (wordlist.hasOwnProperty(word)) {
var val = wordlist[word]
if (!val || val === true)
val = word
else
val = val.displayText ? {text: val.text, displayText: val.displayText} : val.text
if (match(search, val)) result.push(formatter(val))
}
}
}
function cleanName(name) {
// Get rid name from identifierQuote and preceding dot(.)
if (name.charAt(0) == ".") {
name = name.substr(1);
}
// replace duplicated identifierQuotes with single identifierQuotes
// and remove single identifierQuotes
var nameParts = name.split(identifierQuote + identifierQuote);
for (var i = 0; i < nameParts.length; i++)
nameParts[i] = nameParts[i].replace(new RegExp(identifierQuote, "g"), "");
return nameParts.join(identifierQuote);
}
function insertIdentifierQuotes(name) {
var nameParts = getText(name).split(".");
for (var i = 0; i < nameParts.length; i++)
nameParts[i] = identifierQuote +
// duplicate identifierQuotes
nameParts[i].replace(new RegExp(identifierQuote, "g"), identifierQuote + identifierQuote) +
identifierQuote;
var escaped = nameParts.join(".");
if (typeof name == "string") return escaped;
name = shallowClone(name);
name.text = escaped;
return name;
}
function getLeftpart(cur, token, result, editor) {
var nameParts = [];
var start = token.start;
var cont = true;
while (cont) {
//全是空格 或者 是操作符 就接着往前找
cont = ((token.type === 'operator' || token.string.match(/^[ ]*$/)) && start !== 0);
start = token.start;
nameParts.unshift(token.string);
token = editor.getTokenAt(Pos(cur.line, token.start));
if (token.type === 'operator') {
cont = true;
token = editor.getTokenAt(Pos(cur.line, token.start));
}
}
return nameParts[0]
}
function isRightpart(cur, token, result, editor) {
token = editor.getTokenAt(Pos(cur.line, token.start));
return token.type === 'operator'
}
function nameCompletion(cur, token, result, editor) {
// Try to complete table, column names and return start position of completion
var useIdentifierQuotes = false;
var nameParts = [];
var start = token.start;
var cont = true;
while (cont) {
cont = (token.string.charAt(0) == ".");
useIdentifierQuotes = useIdentifierQuotes || (token.string.charAt(0) == identifierQuote);
start = token.start;
nameParts.unshift(cleanName(token.string));
token = editor.getTokenAt(Pos(cur.line, token.start));
if (token.string == ".") {
cont = true;
token = editor.getTokenAt(Pos(cur.line, token.start));
}
}
// Try to complete table names
var string = nameParts.join(".");
addMatches(result, string, tables, function (w) {
return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
});
// Try to complete columns from defaultTable
addMatches(result, string, defaultTable, function (w) {
return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
});
// Try to complete columns
string = nameParts.pop();
var table = nameParts.join(".");
var alias = false;
var aliasTable = table;
// Check if table is available. If not, find table by Alias
if (!getTable(table)) {
var oldTable = table;
table = findTableByAlias(table, editor);
if (table !== oldTable) alias = true;
}
var columns = getTable(table);
if (columns && columns.columns)
columns = columns.columns;
if (columns) {
addMatches(result, string, columns, function (w) {
var tableInsert = table;
if (alias == true) tableInsert = aliasTable;
if (typeof w == "string") {
w = tableInsert + "." + w;
} else {
w = shallowClone(w);
w.text = tableInsert + "." + w.text;
}
return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
});
}
return start;
}
function eachWord(lineText, f) {
var words = lineText.split(/\s+/)
for (var i = 0; i < words.length; i++)
if (words[i]) f(words[i].replace(/[`,;]/g, ''))
}
function findTableByAlias(alias, editor) {
var doc = editor.doc;
var fullQuery = doc.getValue();
var aliasUpperCase = alias.toUpperCase();
var previousWord = "";
var table = "";
var separator = [];
var validRange = {
start: Pos(0, 0),
end: Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).length)
};
//add separator
var indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV);
while (indexOfSeparator != -1) {
separator.push(doc.posFromIndex(indexOfSeparator));
indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV, indexOfSeparator + 1);
}
separator.unshift(Pos(0, 0));
separator.push(Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).text.length));
//find valid range
var prevItem = null;
var current = editor.getCursor()
for (var i = 0; i < separator.length; i++) {
if ((prevItem == null || cmpPos(current, prevItem) > 0) && cmpPos(current, separator[i]) <= 0) {
validRange = {start: prevItem, end: separator[i]};
break;
}
prevItem = separator[i];
}
if (validRange.start) {
var query = doc.getRange(validRange.start, validRange.end, false);
for (var i = 0; i < query.length; i++) {
var lineText = query[i];
eachWord(lineText, function (word) {
var wordUpperCase = word.toUpperCase();
if (wordUpperCase === aliasUpperCase && getTable(previousWord))
table = previousWord;
if (wordUpperCase !== CONS.ALIAS_KEYWORD)
previousWord = word;
});
if (table) break;
}
}
return table;
}
CodeMirror.registerHelper("hint", "sql", function (editor, options) {
tables = parseTables(options && options.tables);
var defaultTableName = options && options.defaultTable; //默认table 名称
var disableKeywords = options && options.disableKeywords; //禁用的keyword
defaultTable = defaultTableName && getTable(defaultTableName);
keywords = getKeywords(editor); //获取 定义defineMIME 时候的关键字参数
identifierQuote = getIdentifierQuote(editor); //获取 引用标识符
if (defaultTableName && !defaultTable)
defaultTable = findTableByAlias(defaultTableName, editor);
defaultTable = defaultTable || [];
if (defaultTable.columns)
defaultTable = defaultTable.columns;
var cur = editor.getCursor(); //line 当前行 ch 索引 sticky ??
var result = [];
var token = editor.getTokenAt(cur), start, end, search;
if (token.end > cur.ch) {
token.end = cur.ch;
token.string = token.string.slice(0, cur.ch - token.start);
}
//start end search 赋值
// todo 此处允许字符串包含 .
// if (token.string.match(/^[.`"'\w@][\w$#]*/g)) {
if (token.string.match(/^[.`"'\w@][\w#]*/g)) {
search = token.string;
start = token.start;
end = token.end;
} else {
start = end = cur.ch;
search = "";
}
//对引用标识符 . 的使用,关联table 列
if (search.charAt(0) == "." || search.charAt(0) == identifierQuote) {
start = nameCompletion(cur, token, result, editor);
} else {
var objectOrClass = function (w, className) {
if (typeof w === "object") {
w.className = className;
} else {
w = {text: w, className: className};
}
return w;
};
addMatches(result, search, defaultTable, function (w) {
return objectOrClass(w, "CodeMirror-hint-table CodeMirror-hint-default-table");
});
addMatches(
result,
search,
tables, function (w) {
return objectOrClass(w, "CodeMirror-hint-table");
}
);
if (!disableKeywords)
addMatches(result, search, keywords, function (w) {
return objectOrClass(w.toUpperCase(), "CodeMirror-hint-keyword");
});
}
//写入一个 钩子,在这里决定提示的内容
if (hinthook) {
var params = {
search, //搜索 关键字
keywords, //关键字列表
};
if (token.type === 'operator') {
params.leftpart = getLeftpart(cur, token, result, editor) || ''
}
if (isRightpart(cur, token, result, editor)) {
params.rightpart = token.string
}
/* 之后的参数 是为manualHinthook 预备的 */
var manualParams = {
search, //搜索 关键字
// keywords, //关键字列表 没啥用
from: Pos(cur.line, start),
to: Pos(cur.line, end),
leftpart: getLeftpart(cur, token, result, editor) || '',
rightpart: params.rightpart,
list: cloneDeep(result),
editor,
token,
cur
}
var refField = matchMain(CodeMirror, params, manualParams);
manualParams.refField = refField
manualParams.leftpart = refField?.label || manualParams.leftpart;
params.leftpart = refField?.label || params.leftpart;
result = hinthook(result, params, manualParams) || result
}
return {list: result, from: Pos(cur.line, start), to: Pos(cur.line, end)};
});
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,70 @@
export default {
enDefault: {
description () {
return (<div className='default-tips'>
<div class='default-tips-header'>How To Search</div>
<p class='show-hint-tips__p'> You can write queries to retrieve entities, including their basic information, activity levels, network performance, threat events, relationships with other entities, and so on. A query has three basic parts: fields, operators, and values.</p>
<code>[Field + operator + value] keyword [operator(Field)]</code>
<ul style="padding: 0">
<li className='show-hint-tips__p'>Field - Fields are different types of traffic attributes in the system.
Fields include ip, domain, app, and so on.
</li>
<li className='show-hint-tips__p'>Operator - Operators are the foundation of the query. They relate the field
to the value and build a query condition. Common operators include =, IN, Like, and so on.
</li>
<li className='show-hint-tips__p'>Value - Values are the actual data in the query.</li>
<p className='show-hint-tips__p'>Use the percent(%) wildcard substitutes for one or more characters in a
string. Such as: </p>
<code>domain like '%google.com'</code>
<p className='show-hint-tips__p'>Strings containing spaces must be enclosed in single quotes ('). Such as:</p>
<code>ip=192.168.10.53</code>
<code>ip.country='United States'</code>
<li className='show-hint-tips__p'>Keyword - Keywords are specific words in the query. You can specify the AND
and OR to create more complex query conditions.
</li>
<p className='show-hint-tips__p'>Currently only support AND.</p>
</ul>
<p class='show-hint-tips__p'>There are two input modes, which can be switched by clicking the button on the right side of the input box.</p>
<div class='default-tips-title'> 1. Text Mode</div>
<p class='show-hint-tips__p'>In text mode, you will write your query with the help of input suggestions.</p>
<div class='default-tips-title'> 2. Tag Mode </div>
<p class='show-hint-tips__p'>In tag mode, you will be guided through preset steps to write a query.</p>
</div>)
}
},
zhDefault: {
description () {
return (<div className='default-tips'>
<div class='default-tips-header'>如何搜索</div>
<p class='show-hint-tips__p'>您可以编写查询来检索实体。查询具有三个基本部分:字段、运算符和值。</p>
<code>[字段 + 运算符 + 值] 关键字 [运算符(字段)]</code>
<ul style="padding: 0">
<li className='show-hint-tips__p'>字段 - 字段是系统中不同类型的属性。字段包括 ip、domain、app 等。</li>
<li className='show-hint-tips__p'>运算符 - 运算符是查询的基础。他们将字段与值相关联并构建查询条件。常见的运算符包括
=、IN、Like 等。
</li>
<li className='show-hint-tips__p'>值 - 值是查询中的实际数据。</li>
<p className='show-hint-tips__p'>使用百分号(%)通配符替换字符串中的一个或多个字符。例如:</p>
<code>domain like '%google.com'</code>
<p className='show-hint-tips__p'>包含空格的字符串必须用单引号(')括住例如</p>
<code>ip=192.168.10.53</code>
<code>ip.country='United States'</code>
<li className='show-hint-tips__p'>关键字 - 关键字是查询中的特定单词您可以指定 AND OR
来创建更复杂的查询条件
</li>
<p className='show-hint-tips__p'>暂时只支持AND</p>
</ul>
<p class='show-hint-tips__p'>有两种输入模式通过点击输入框右侧的按钮进行切换</p>
<div class='default-tips-title'> 1. 文本模式</div>
<p class='show-hint-tips__p'>文本模式中您将在输入建议的帮助下编写查询语句</p>
<div class='default-tips-title'> 2. 标签模式 </div>
<p class='show-hint-tips__p'>标签模式中您将在引导下按预设好的步骤编写查询</p>
</div>)
}
}
}

View File

@@ -0,0 +1,26 @@
import i18n from '@/i18n'
export function fieldRender (h, { fieldInfo = {}, funcReference = [], operatorReference = [], operates = [], funs = [] }) {
return (<div className='field-tips'>
<div class='default-tips-header'>{fieldInfo.label}</div>
<p class='show-hint-tips__p'> {i18n.global.t('overall.name')}: {fieldInfo.name}</p>
<p class='show-hint-tips__p'> {i18n.global.t('overall.type')}: {typeof fieldInfo.type === 'object' ? fieldInfo.type.type : fieldInfo.type} </p>
<p class='show-hint-tips__p'> {i18n.global.t('overall.operators')}: {operates.join(',')} </p>
{/* <p> Function: {funs.join(',')} </p> */}
<p> {fieldInfo.options && (
'Options: ' + fieldInfo.options.map(item => {
return `${item.displayText}(${item.text})`
}).join(',')
)} </p>
<div class='default-tips-header'>{i18n.global.t('overall.enableOperators')}</div>
{/* 提示超过一屏幕 明显不合理 --- 换成简单形式 */}
<ul style="padding-left: 0;margin: 12px 0;">
{operatorReference.map(item => {
return <li>
<span>{item.name}</span>
<code> {item.function} </code>
</li>
})}
</ul>
</div>)
}

View File

@@ -0,0 +1,109 @@
import i18n from '@/i18n'
import { EN, storageKey, ZH } from '@/utils/constants'
export const helpInfo = [
{
operator: 'AND',
zhDescribe: '搜索包含所有搜索字段的实体。',
enDescribe: 'Search entities that contain all the search fields. ',
example: [
"ip = '192.168.10.53' AND domain = 'google.com'",
"domain = 'google.com' AND app = 'google'"
]
}
// {
// // operator: '= , !=',
// operator: ' = ',
// describe: 'Search logs that do EQUALS or NOT EQUALS the search value. ',
// example: [
// "Client IP = '192.168.10.53' "
// // 'Server Port != 443'
// ]
// },
// {
// operator: '> , < , >= , <=',
// describe: 'Search logs greater than and equal or less than and equal a value , or whtin a range. This operator cant be used with string fields. ',
// example: [
// 'Bytes Sent >= 751',
// 'Bytes Sent >= 751 and Bytes Sent <= 1024'
// ]
// },
// {
// operator: ' LIKE ',
// // operator: 'LIKE , NOT LIKE',
// describe: 'You can use wildcard searches for value that contain or not contain search fields. Using percent (%) wildcard substitutes for one or more characters in a string. Using underscore (_) wildcard substitutes for exactly one character in a string. ',
// example: [
// "SSL.SNI LIKE '%google.com' ",
// "Client IP LIKE '192.168.10.1_'"
// // "SSL.SNI NOT LIKE '%google.com'",
// // "Client IP NOT LIKE '192.168%'"
// ]
// },
// {
// operator: ' IN ',
// // operator: 'IN , NOT IN',
// describe: 'Specify or exclude multiple values for search fields. IN IN condition you can use when you need to use multiple OR condition. ',
// example: [
// "l7 Protocol IN ('HTTP', 'HTTPS')"
// // 'Server Port NOT IN (443,80)'
// ]
// },
// {
// operator: 'EMPTY , NOT EMPTY',
// describe: 'Specify or exclude empty value for search fields. A string or array is considered non-empty if it contains at least one byte, even if this is a space or a null byte. ',
// example: [
// 'NOT EMPTY(SSL.SNI)',
// 'EMPTY(Application Label)'
// ]
// },
// {
// operator: 'HAS',
// describe: 'Search logs that has element value for array type. Example:',
// example: [
// 'HAS(FQDN Category, music)'
// ]
// }
// {
// operator: 'bitAnd',
// describe: 'A bitwise And(&) is a binary operation that compares each bit of the first operand to the corresponding bit of the second operand. Both expressions must have integral types. Examples:',
// example: [
// 'bitAnd(Flags, Asymmetric|Download) = Asymmetric|Download',
// 'bitAnd(Flags, Asymmetric|Download) >0'
// ]
// }
]
const renderData = [
helpInfo[0]
]
export const filterList = renderData
function main () {
const sqlTips = {}
const language = localStorage.getItem(storageKey.language) || EN
renderData.forEach((item, index) => {
const data = item // 这是个闭包
sqlTips[item.operator] = {
name: item.operator,
// syntax: item.syntax,
type: 'filter',
description () {
return (<div className='filter-tips'>
<div class='default-tips-header'>{data.operator}</div>
<div class='default-tips-title'> {i18n.global.t('overall.remark')}: </div>
<p class='show-hint-tips__p'> {language === ZH ? data.zhDescribe : data.enDescribe}</p>
<div class='default-tips-title'>{i18n.global.t('overall.examples')}:</div>
<ul style="padding-left: 0;">
{item.example.map(v => {
return <li>
<code>{v}</code>
</li>
})}
</ul>
</div>)
}
}
})
return sqlTips
}
const filterTips = main()
export default filterTips

View File

@@ -0,0 +1,341 @@
var renderData = [
{
name: 'COUNT',
syntax: 'count(expr)',
description: 'Aggregate function is used to count the number of rows',
example: [
{
purpose: 'Total count of all logs :',
code: 'count(*)'
},
{
purpose: 'Counts the occurrences of a Client IP :',
code: 'count(client_ip)'
}
],
details () {
// 支持jsx 嵌套写法,万一测试要关键字加重呢
return <div>
You can use COUNT function by count(*), count(1) or count(field). But there are something difference:
<ul>
<li>count(*) and count(1) will count all the rows in the table, including NULL values.</li>
<li>count(field) will count all the rows in the specified field while excluding NULL values.</li>
</ul>
</div>
}
},
{
name: 'COUNT_DISTINCT',
syntax: 'count(distinct expr)',
description: 'Aggregate function is used to count only distinct(unique) rows in the specified field',
example: [
{
purpose: 'Counts the number of different Client IP :',
code: 'count(distinct client_ip)'
},
{
purpose: `Counts the number of different "Server IP" and "Server port" :`,
code: 'count(distinct server_ip, server_port)'
}
],
details () {
// 支持jsx 嵌套写法,万一测试要关键字加重呢
return <div>The COUNT DISTINCT function returns the number of unique values in the field or multiple fields.
System will uses an adaptive sampling algorithm to perform fast count distinct operations.</div>
}
},
{
name: 'AVG',
syntax: 'avg(expr)',
description: 'Aggregate function is used to calculate the arithmetic mean in the specified field. EXPR must be Integer,Float or Decimal and returned value as Float.',
example: [
{
purpose: `Calculates the average(mean) "Byte sent (sent_bytes)" field:`,
code: 'avg(sent_bytes)'
},
{
purpose: `Calculates the average(mean) "Bytes" , rounded to 2 decimal points:`,
code: 'round(avg(sent_bytes+received_bytes),2)'
}
],
details () {
// 支持jsx 嵌套写法,万一测试要关键字加重呢
return <div>You can use ROUND(expr[,decimal_places]) or FLOOR(expr[,decimal_places]) function that rounds or
floors a value to a specified number of decimal places.</div>
}
},
{
name: 'SUM',
syntax: 'sum(expr)',
description: 'Aggregate function is used to sum of the values of the specified field. EXPR must be Integer,Float or Decimal.',
example: [
{
purpose: `The sum of the "Byte sent (sent_bytes)" field:`,
code: 'sum(sent_bytes)'
},
{
purpose: `The sum of the "sent_bytes" and "received_bytes" fields , and rename as "Bytes ":`,
code: 'sum(sent_bytes+received_bytes) as Bytes'
}
],
details () {
// 支持jsx 嵌套写法,万一测试要关键字加重呢
return <div>You can rename the field using the AS keyword.</div>
}
},
{
name: 'MAX',
syntax: 'max(expr)',
description: 'Aggregate function is used to return the maximum value of the specified field.',
example: [
{
purpose: `Returns the maximum value of the "Byte sent (sent_bytes)" field:`,
code: 'max(sent_bytes)'
}
],
details () {
// 支持jsx 嵌套写法,万一测试要关键字加重呢
return <div>The <b>MAX</b> aggregate function can also be used with the DateTime data type, where it will sort the
DateTime values and return the last value from the sorted logs.</div>
}
},
{
name: 'MIN',
syntax: 'min(expr)',
description: 'Aggregate function is used to return the minimum value of the specified field.',
example: [
{
purpose: `Returns the minimum value of the "Byte sent (sent_bytes)" field:`,
code: 'min(sent_bytes)'
}
],
details () {
// 支持jsx 嵌套写法,万一测试要关键字加重呢
return <div>The MIN aggregate function can also be used with the DateTime data type, where it will sort the
DateTime values and return the minimum value from the sorted logs.</div>
}
},
{
name: 'TIME_FLOOR_WITH_FILL',
syntax: 'TIME_FLOOR_WITH_FILL(<timestamp_expr>, <period>[,<fill>])',
description: 'Rounds down a timestamp, returning it as a new timestamp,optionally from some reference fill, and fills time gaps and impute missing values.',
example: [
{
purpose: `Round the recv_time down to a 5 minutes increment and fill time gaps and impute zero value.`,
code: 'TIME_FLOOR_WITH_FILL(recv_time,\'PT5M\',\'zero\')'
}
],
details () {
// 支持jsx 嵌套写法,万一测试要关键字加重呢
return <div>
<p>The TIME_FLOOR_WITH_FILL function as Timeseries granularity is used for time-based grouping.</p>
<ul>
<li> timestamp_expr - Unix Timestamp field</li>
<li>period - can be any ISO8601 period, like P3M (quarters) or PT12H (half-days)</li>
<li>
<span>fill - optionnal. Includes none, null, zero, previous, next value.</span>
<ul class="sub-url">
<li>none: empty string ""</li>
<li>null"NULL" expression</li>
<li>zerozero "0"</li>
<li>previousprevious value</li>
<li>nextnext value</li>
</ul>
</li>
</ul>
</div>
}
},
{
name: 'UNIX_TIMESTAMP',
syntax: `UNIX_TIMESTAMP(date)`,
description: `Returns a Unix timestamp the value of the argument as seconds since '1970-01-01 00:00:00' UTC.`,
example: [
{
purpose: `Specify a datetime string "2019-06-06 19:11:12", calculate the Unix timestamp:`,
code: 'UNIX_TIMESTAMP(\'2019-06-06 19:11:12\')'
},
{
purpose: `Specify a ISO8601 datetime string with time zone information "2019-10-12T14:20:50+08:00", calculate the Unix timestamp:`,
code: 'UNIX_TIMESTAMP(\'2019-10-12T14:20:50+08:00\')'
},
{
purpose: `Specify a ISO8601 datetime string with UTC+0 time zone information "2019-10-12T14:20:50Z", calculate the Unix timestamp:`,
code: 'UNIX_TIMESTAMP(\'2019-10-12T14:20:50Z\')'
},
],
details () {
// 支持jsx 嵌套写法,万一测试要关键字加重呢
return <div>
<p>The date argument may be a DATE, DATETIME or TIMESTAMP string, or a number in YYMMDD, YYMMDDhhmmss, YYYYMMDD,
or YYYYMMDDhhmmss format.</p>
<ul>
<li> Standard datetime string(UTC+0) : UNIX_TIMESTAMP('2019-06-06 19:11:12')</li>
<li>ISO8601 datetime stringUNIX_TIMESTAMP('2019-10-12T14:20:50Z') or
UNIX_TIMESTAMP('2019-10-12T14:20:50+08:00')
</li>
<li>Date: UNIX_TIMESTAMP(DATE('2019-06-06 19:11:12'))</li>
</ul>
</div>
}
},
{
name: 'FROM_UNIXTIME',
syntax: `FROM_UNIXTIME(unix_timestamp)`,
description: `Returns a representation of unix_timestamp as a datetime or character string value. The value returned is expressed using the UTC+0 time zone.`,
example: [
{
purpose: `Specify a Unix Timestamp "1570881546", calculate the datetime string:`,
code: 'FROM_UNIXTIME(1570881546)'
},
],
details () {
// 支持jsx 嵌套写法,万一测试要关键字加重呢
return <div>The unix_timestamp is an internal timestamp value representing seconds since '1970-01-01 00:00:00'
UTC.</div>
}
},
{
name: 'DATE_FORMAT',
syntax: 'DATE_FORMAT(date, format)',
description: `Formats the date value according to the format string.`,
example: [
{
purpose: `Specify a Unix Timestamp "1570881546", calculate the datetime string with format "%Y-%m-%d %H:%i:%s":`,
code: 'DATE_FORMAT(FROM_UNIXTIME(1570881546), \'%Y-%m-%d %H:%i:%s\')'
}
],
details () {
// 支持jsx 嵌套写法,万一测试要关键字加重呢
return <div>
<p>The DATE_FORMAT function accepts two parameters as given below :</p>
<ul>
<li>date Specified date to be formatted.</li>
<li>
<span>format Specified format. This list of formats used in this function are listed below:</span>
<ul class="sub-url">
<li>%Y - Year, numeric, four digits</li>
<li>%y - Year, numeric (two digits)</li>
<li>%M - Month name (January..December)</li>
<li>%m - Month, numeric (00..12)</li>
<li>%D - Day of the month with English suffix (0th, 1st, 2nd, 3rd, )</li>
<li>%d - Day of the month, numeric (00..31)</li>
<li>%H - Hour (00..23)</li>
<li>%h - Hour (01..12)</li>
<li>%i - Minutes, numeric (00..59)</li>
<li>%s - Seconds (00..59)</li>
<li>%w - Day of the week (0=Sunday..6=Saturday)</li>
</ul>
</li>
</ul>
</div>
}
},
{
name: 'CONVERT_TZ',
syntax: `CONVERT_TZ(dt, from_tz, to_tz)`,
description: `Converts a datetime value dt from the time zone given by from_tz to the time zone given by to_tz and returns the resulting value.`,
example: [
{
purpose: `Specify a datetime string "2021-11-11 00:00:00", converted from GMT(Greenwich Mean Time) to Asia/Shanghai time zone:`,
code: 'CONVERT_TZ(\'2021-11-11 00:00:00\',\'GMT\',\'Asia/Shanghai\')'
},
{
purpose: `Specify a Unix timestamp "1636588800", converted from GMT(Greenwich Mean Time) to Asia/Shanghai time zone:`,
code: 'CONVERT_TZ(FROM_UNIXTIME(1636588800),\'GMT\',\'Asia/Shanghai\')'
},
{
purpose: `Specify a Unix timestamp "1636588800", converted from Europe/London to America/New_York time zone:`,
code: 'CONVERT_TZ(DATE_FORMAT(FROM_UNIXTIME(1636588800), \'%Y-%m-%d %H:%i:%s\'),\'Europe/London\',\'America/New_York\')'
},
],
details () {
// 支持jsx 嵌套写法,万一测试要关键字加重呢
return <div>
<p>The CONVERT_TZ function accepts a three-parameter:</p>
<ul>
<li>dt - The given DateTime which we want to convert.</li>
<li>from_tz - The time zone from which we want to convert DateTime.</li>
<li>to_tz - The time zone in which we want to convert DateTime.</li>
</ul>
</div>
}
},
{
name: 'MEDIAN',
syntax: `MEDIAN(<expr>)`,
description: `Aggregate function is used to calculate median value. expr must be Integer, Float or Decimal.`,
example: [
{
purpose: `Calculates the median "TCP Handshake Latency (tcp_handshake_latency_ms)" field:`,
code: 'MEDIAN(tcp_handshake_latency_ms)'
}
],
details () {
// 支持jsx 嵌套写法,万一测试要关键字加重呢
return <div>In Traffic logs analysis, the function can be useful in calculating the median of certain numbers,
e.g. median SSL Handshake Latency or TCP Handshake Latency.</div>
}
},
{
name: 'QUANTILE',
syntax: `QUANTILE(<expr>[, <level>])`,
description: `Aggregate function is used to calculate an approximate quantile of a numeric data sequence.`,
example: [
{
purpose: `Calculates the 90th percentile "TCP Handshake Latency (tcp_handshake_latency_ms)" field:`,
code: 'QUANTILE(tcp_handshake_latency_ms, 0.9)'
}
],
details () {
// 支持jsx 嵌套写法,万一测试要关键字加重呢
return <div>
<p>The QUANTILE function accepts a two-parameter:</p>
<ul>
<li>expr - The column values resulting in integer, Flot or Decimal.</li>
<li>level - Level of quantile. Optional parameter. Constant floating-point number from 0 to 1. We recommend
using a level value in the range of [0.01, 0.99]. Default value is 0.5. At level=0.5 the function calculates
MEDIAN.
</li>
</ul>
</div>
}
},
]
function main () {
var functionTips = {}
renderData.forEach((item, index) => {
var data = item // 这是个闭包
functionTips[item.name] = {
name: item.name,
syntax: item.syntax,
type: 'Function',
description () {
return (<div className="function-tips">
<h2>{data.name}</h2>
<h3>Syntax:<span>{data.syntax}</span></h3>
<h3> Description: </h3>
<p> {data.description}</p>
<h3>Examples:</h3>
<ul>
{item.example.map(v => {
return <li>
<span>{v.purpose}</span>
<code>{v.code}</code>
</li>
})}
</ul>
<h3> Details: </h3>
{Object.prototype.toString.call(data.details) === '[object Function]' ?
<renderer renderFun={data.details}></renderer> : <p>{data.details} </p>}
</div>)
}
}
})
return functionTips
}
export const functionList = renderData
var functionTips = main()
export default functionTips

View File

@@ -0,0 +1,111 @@
import i18n from '@/i18n'
import { EN, storageKey, ZH } from '@/utils/constants'
const renderData = [
{
name: 'EQUALS',
syntax: '=',
primaryKey: '=',
zhDescription: '搜索指定字段的值与指定值完全匹配的实体。',
enDescription: 'Search for entities where the value of a specified field exactly matches a specified value.',
example: [
{
zhPurpose: '通过192.168.10.53查询ip实体:',
enPurpose: 'Search entity of ip by 192.168.10.53:',
code: 'ip=\'192.168.10.53\''
},
{
zhPurpose: '通过google.com查询domain实体',
enPurpose: 'Search entity of domain by google.com',
code: 'domain=\'google.com\''
}
]
},
{
name: 'IN',
syntax: 'in',
primaryKey: 'IN',
zhDescription: '搜索指定字段的值是多个指定值之一的实体。',
enDescription: 'Search for entities where the value of a specified field is one of multiple specified values.',
example: [
{
zhPurpose: '通过192.168.10.53 或 192.168.10.54查询ip实体',
enPurpose: 'Search entity by 192.168.10.53 or 192.168.10.54',
code: 'ip in (\'192.168.10.53\', \'192.168.10.54\')'
},
{
zhPurpose: '通过appName1 或 appName2查询app实体:',
enPurpose: 'Search entity by appName1 or appName2:',
code: 'app in (\'appName1\', \'appName2\')'
}
]
},
{
name: 'LIKE',
syntax: 'like',
primaryKey: 'LIKE',
zhDescription: '搜索使用通配符搜索包含搜索字段的值的实体。此运算符与字符串字段一起使用。',
enDescription: 'Search for entities where use wildcard searches for value that contain search fields. This operator is used with array fields.',
example: [
{
zhPurpose: '通过google.com查询domain实体:',
enPurpose: 'Search entity of domain by google.com:',
code: 'domain like \'%google.com\''
},
{
zhPurpose: '通过appName查询app实体:',
enPurpose: 'Search entity of app by appName:',
code: 'app like \'%appName%\''
}
]
},
{
name: 'HAS',
syntax: 'has',
primaryKey: 'HAS',
zhDescription: '搜索指定字段的值是指定值之一的实体。此运算符与数组字段一起使用。',
enDescription: 'Search for entities where the values of a specified field is one of specified value. This operator is used with array fields.',
example: [
{
zhPurpose: '通过ADNS查询tag实体:',
enPurpose: 'Search entity of tag by ADNS:',
code: 'has(tag,\'ADNS\')'
}
]
}
]
export const operatorList = renderData
function main () {
const operatorTips = {}
const language = localStorage.getItem(storageKey.language) || EN
renderData.forEach((item, index) => {
const data = item // 这是个闭包
operatorTips[item.primaryKey] = {
name: item.name,
syntax: item.syntax,
type: 'Operators',
description () {
return (<div className="operator-tips">
<div class='default-tips-header'>{data.name}</div>
<div class='default-tips-title'>{i18n.global.t('overall.syntax')}: <span>{data.syntax}</span></div>
<div class='default-tips-title'>{i18n.global.t('overall.remark')}: </div>
<p class='show-hint-tips__p'> {language === ZH ? data.zhDescription : data.enDescription}</p>
<div class='default-tips-title'>{i18n.global.t('overall.examples')}: </div>
<ul style="padding-left: 0;">
{item.example.map(v => {
return <li>
<span>{language === ZH ? v.zhPurpose : v.enPurpose}</span>
<code>{v.code}</code>
</li>
})}
</ul>
</div>)
}
}
})
return operatorTips
}
const operatorTips = main()
export default operatorTips

View File

@@ -0,0 +1,97 @@
var renderData = [
{
name: 'FROM',
syntax: `FROM [db.]table |$log_type`,
description: {
title: `The table name of logs. If you type $log_type, the variable value is the current table name of logs.`
}
},
{
name: 'SELECT',
syntax: `Optional. The selected columns(also known as dimensions and metrics).`,
description: {
title: '可选,获取列',
list: [
`aggregate_function(field) - Aggregate functions, default is count.`,
`as field - Use as to specify a aliases for a field or expression.`
]
}
},
{
name: 'GROUP BY',
syntax: `GROUP BY <field-list>`,
description: {
title: 'Aggregate data. GROUP BY clause switches the SELECT query into an aggregation mode',
list: [
`The list of fields known as "grouping key", while each individual expression be referred to as a "key expression".`,
`All the expressions in the SELECT, HAVING and ORDER BY , must be "key expression" or on aggregate functions.`,
`The result of aggregating SELECT query will return unique values of "grouping key" in log type.`
]
}
},
{
name: 'HAVING',
syntax: `HAVING <expression-list>`,
description: {
title: `Optional. HAVING clause filtering the aggregation results retrieved by GROUP BY. It is difference is that WHERE is performed before aggregation, while HAVING is performed after it.
Note: HAVING can't be performed if GROUP BY is not performed.`
}
},
{
name: 'LIMIT',
syntax: `LIMIT [n, ]m`,
description: {
title: `Select the m rows from the aggregate results after skipping the first n rows. Default is 10 rows.`
}
},
{
name: 'ORDER BY',
syntax: `ORDER BY <sort-field> [ASC|DESC]`,
description: {
title: `Sort all of the results by the specified fields.`
}
},
{
name: 'WHERE',
syntax: `where $filter [and <expression-list>]`,
description: {
title: `Filter the data.`,
list: [
`$filter - Default global filter clause. Include Time period, Vsys ID, and other expressions, etc`,
`and <expression-list> - filter clauses`
]
}
}
]
export const sqlList = renderData
function main () {
var sqlTips = {}
renderData.forEach((item, index) => {
var data = item // 这是个闭包
sqlTips[item.name] = {
name: item.name,
syntax: item.syntax,
type: 'sql',
description () {
return (<div className="sql-tips">
<h2>{data.name}</h2>
<h3>Syntax: <span>{data.syntax}</span></h3>
<h3> Description: </h3>
<p> {data.description.title}</p>
{
data.description.list && <ul>
{data.description.list.map(v => {
return <li>{v} </li>
})}
</ul>
}
</div>)
}
}
})
return sqlTips
}
var sqlTips = main()
export default sqlTips

View File

@@ -0,0 +1,48 @@
var renderData = [
{
name: '$LOG_TYPE',
description: 'A variable is a symbolic representation of data that enables you to access a value without having to enter it manually wherever you need it. You can use $ to reference variables throughout Advanced Search. ',
details () {
// 支持jsx 嵌套写法,万一测试要关键字加重呢
return <div>
$log_type: The table name of logs.
</div>
}
},
{
name: '$FILTER',
description: 'A variable is a symbolic representation of data that enables you to access a value without having to enter it manually wherever you need it. You can use $ to reference variables throughout Advanced Search. ',
details () {
// 支持jsx 嵌套写法,万一测试要关键字加重呢
return <div>
$filter: The default filter clauses. such as time period, Vsys ID, and other default expressions, etc.
</div>
}
}
]
function main () {
const varTips = {}
renderData.forEach((item, index) => {
const data = item // 这是个闭包
varTips[item.name] = {
name: item.name,
type: 'Variables',
description () {
return (<div className="var-tips">
<h2>{data.name}</h2>
<h3> Description: </h3>
<p> {data.description}</p>
<h3> Details: </h3>
{Object.prototype.toString.call(data.details) === '[object Function]' ?
<renderer renderFun={data.details}></renderer> : <p>{data.details} </p>}
</div>)
}
}
})
return varTips
}
export const varList = renderData
const varTips = main()
export default varTips

View File

@@ -0,0 +1,129 @@
import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/eclipse.css'
import 'codemirror/mode/sql/sql'
import 'codemirror/theme/ambiance.css'
// import 'codemirror/addon/hint/show-hint.js'
import 'codemirror/addon/hint/show-hint.css'
import 'codemirror/addon/display/placeholder'
import 'codemirror/addon/hint/anyword-hint' // 关键字
import 'codemirror/addon/comment/comment'
import 'codemirror/keymap/sublime'
import 'codemirror/addon/edit/closebrackets'
import CodeMirror from 'codemirror'
import createMode from '@/components/advancedSearch/showhint/TextSearch/sql'
import createHint from '@/components/advancedSearch/showhint/TextSearch/createHint'
import { functionList } from '@/components/advancedSearch/showhint/const/functionTips'
import { cloneDeep } from 'lodash'
const codeMirrorMixins = {
data () {
return {
CodeMirror,
initData: {},
hintSearch: '',
hintList: [],
hintParams: {},
completion: null
}
},
methods: {
initShowHint () {
const dataset = this.dataset
createMode(CodeMirror, {
dataset,
modehook: this.modehook
})
const hintHook = this.manualHinthook
const vm = this
createHint('manual', CodeMirror, {
dataset,
hinthook: hintHook,
callback (completion, defaultOptions) {
vm.completion = completion.completion
},
keyboardUp () {
// 键盘向上
vm.hintVm?.handleUp()
},
keyboardDown () {
// 键盘向下
vm.hintVm?.handleDown()
},
keyboardEnter () {
if (!vm.hintVisible) {
return
}
// 回车 选中值
vm.hintVm?.triggerSelect()
}
})
},
manualHinthook (result, params, manualParams) {
this.hintParams = manualParams
this.hintSearch = manualParams.search
this.hintList = []
const list = this.hinthook(result, params)
this.hintList = cloneDeep(list)
return list
},
hinthook (result, { search, keywords, leftpart, rightpart }) {
if (leftpart) {
const options = this.matchOptions(leftpart)
if (options) {
return options
}
}
result = this.dataset.getHintList(search, null, null, this.value)
return result
},
matchOptions (leftpart) {
// 用于匹配下拉选
const fieldInfo = this.dataset.getFieldInfo(leftpart)
if (fieldInfo && fieldInfo.options) {
const options = []
options.push(
{
type: 'abstract',
text: '',
displayText: 'Options',
className: 'divider hint-title'
}
)
const fieldOptions = cloneDeep(fieldInfo.options)
// eslint-disable-next-line no-return-assign
fieldOptions.forEach(item => item.displayText = `${item.displayText}(${item.text})`)
options.push(...fieldOptions)
return options
}
return null
},
modehook (CodeMirror, set, { client, keywords, builtin, hookVar }) {
// 自定义 过滤器类型
const clientWords = this.dataset.getClientwords()
let clientWordsStr = ''
clientWordsStr = clientWords.join(' ')
const dateFuns = functionList.map(v => v.name.toLowerCase())
CodeMirror.defineMIME('text/x-filter', {
name: 'sql',
client: set(clientWordsStr + ' ' + client), // 客户端解析指令
builtin: set(builtin + ' like in not empty notempty has bitand'),
keywords: set(keywords + ' and or ' + dateFuns.join(' ')),
atoms: set('false true null unknown'),
operatorChars: /^[*+\-%<>!=&|^]/,
dateSQL: set('date time timestamp '),
support: set('ODBCdotTable doubleQuote binaryNumber hexNumber'), // 支持的数据编码 ODBC double 二进制 16进制
hooks: {
$: hookVar
}
})
}
}
}
export default codeMirrorMixins

View File

@@ -0,0 +1,288 @@
// 改js 用户获取初始化的 SchemaData
import { Scheme } from './service/Scheme'
import i18n from '@/i18n'
export class Dataset {
constructor ({ operatesList, filtersList, operatesDic, funcDic, operatorManual, fields, doc }) {
this.sourceData = {}
this.sourceData = this.keepSourcedata(operatesList, funcDic, filtersList, operatesDic, operatorManual, fields, doc)
// 存储格式化的数据
this.hintData = {}
this.hintData.operatesList = this.formatOperates(operatesList)
this.hintData.filtersList = this.formatFilters(filtersList)
}
getHintList (keyword, sqlKeywordsOptions = null, callback, completeFilter) {
// 获取提示列表
const operatesList = this.matchFilter(sqlKeywordsOptions || this.hintData.operatesList || [], keyword)
const filtersList = this.matchFilter(this.hintData.filtersList || [], keyword)
const hintList = [...operatesList, ...filtersList]
callback && callback(operatesList, filtersList, hintList)
return hintList
}
getClientwords () {
// 获取高亮的 可查询的 字段
const filtersList = this.sourceData.filtersList || []
const clientwords = []
filtersList.forEach((item) => {
// 可以在这里增加 过滤逻辑
// todo client由name改为label
// clientwords.push(item.name)
clientwords.push(item.label)
})
return clientwords
}
matchFilter (list, keyword) {
// 用于 匹配过滤器
const results = list.filter((item) => {
if (item.type === 'abstract') {
return true
}
let displayTextLow = item.displayText + '' ? (item.displayText + '').toLowerCase() : item.displayText
let keywordLow = keyword + '' ? (keyword + '').toLowerCase() : keyword
const reg = /[ '"]/ig
displayTextLow = (displayTextLow + '').replace(reg, '').toLowerCase()
keywordLow = (keywordLow + '').replace(reg, '').toLowerCase()
return displayTextLow && displayTextLow.indexOf(keywordLow) !== -1
})
const hasEnable = results.some((item) => {
return item.type !== 'abstract'
})
return hasEnable ? results : []
}
formatFilters (list) {
// 格式化 过滤器
if (!(list && list.length > 0)) {
return
}
const tempTitle = {
type: 'abstract',
text: '',
// displayText: "Filter",
displayText: i18n.global.t('overall.fields'),
className: 'divider hint-title',
hint (cm, callback, options) {
}
}
const results = list.map((item) => {
return {
// text: item.name,
// displayText: `${item.label}(${item.name})`,
text: item.label,
displayText: `${item.label}`,
className: 'filter-item el-dropdown-menu__item relative-item'
}
})
results.unshift(tempTitle)
return results
}
formatOperates (list) {
// 格式化 操作符
if (!(list && list.length > 0)) {
return
}
const tempTitle = {
type: 'abstract',
text: '',
// displayText: "Operator",
displayText: i18n.global.t('overall.keyword'),
className: 'divider hint-title'
// hint(cm, callback, options) {}
}
const results = list.map((item) => {
return {
text: item.name,
displayText: item.name,
className: 'operates-item el-dropdown-menu__item relative-item'
// hint(cm, callback, options) {}
}
})
results.unshift(tempTitle)
return results
}
keepSourcedata (operatesList, funcDic, filtersList, operatesDic, operatorManual, fields, doc) {
// 初始化原始数据 方法(存放的是 全量原始数据)
const sourceData = {}
// 支持的逻辑运算符
sourceData.operatesList = operatesList || []
// 过滤器列表
sourceData.filtersList = filtersList || []
sourceData.operatesDic = operatesDic || []
sourceData.operatorReference = doc?.functions?.operator || []
sourceData.operatorManual = operatorManual || []
// 添加全量数据
sourceData.fields = fields || []
sourceData.doc = doc || {}
// 存储function 的原始变量
sourceData.funcDic = funcDic || []
const aggregation = doc?.functions?.aggregation || []
const dateFuns = doc?.functions?.date || []
sourceData.funcReference = [...dateFuns, ...aggregation]
return sourceData
}
matchHightlight (keyWord) {
// 匹配高亮
// var reg = new RegExp(keyWord, 'i')
let matchFlag = false
// reg.test(val)
matchFlag = this.sourceData.operatesList.some((item) => {
return keyWord === item.name
})
if (matchFlag) {
return 'keyword'
}
if (matchFlag) {
return 'variable-2'
}
matchFlag = this.sourceData.filtersList.some((item) => {
return keyWord === item.name
})
if (matchFlag) {
return 'comment'
}
matchFlag = this.sourceData.specialCharacters.some((item) => {
return keyWord === item.name
})
if (matchFlag) {
return 'atom'
}
}
getOperates (type, item) {
// 获取 当前类型支持的操作符
let operatorFunctions = ''
if (item && item.doc && item.doc.constraints && item.doc.constraints.operator_functions) {
operatorFunctions = item.doc.constraints.operator_functions
} else {
const functions = this.sourceData.operatesDic.find(item => {
return item.type === type
})
// eslint-disable-next-line no-mixed-operators
operatorFunctions = functions && functions.functions || ''
}
const funList = operatorFunctions.split(',')
return funList.map((item) => {
return {
text: item,
displayText: item,
className: 'filter-item el-dropdown-menu__item relative-item'
}
})
}
getFunctions (type, item) {
// 获取 当前类型支持的操作符
let functionsInfo = ''
// 这里肯定有问题
if (item && item.doc && item.doc.constraints && item.doc.constraints.aggregation_functions) {
functionsInfo = item.doc.constraints.aggregation_functions
} else {
const functions = this.sourceData.funcDic.find(item => {
return item.type === type
})
functionsInfo = (functions && functions.functions) || ''
}
const funList = functionsInfo.split(',')
let result = []
result = funList.map((item) => {
return {
text: item,
displayText: item,
className: 'filter-item el-dropdown-menu__item relative-item'
}
})
return result
}
getFieldInfo (keywords) {
// 获取字段的相关信息 1.下拉数据是需要获取的 2.支持的运算符 是不是需要呢 ???
if (!keywords || !keywords.toLowerCase) {
return
}
keywords = (keywords.trim && keywords.trim()) || keywords
const fieldInfo = {}
const matchItem = this.sourceData.filtersList.find((item) => {
// const itemName = item.name && item.name.toLowerCase()
// 左侧面板的options值即枚举的值
const itemName = item.label && item.label.toLowerCase()
return keywords.toLowerCase() === itemName
})
if (!matchItem) {
return null
}
// 存在下拉值 候选项
if (matchItem && matchItem.doc && matchItem.doc.data) {
fieldInfo.options = matchItem.doc.data.map(item => {
return {
text: matchItem.type === 'string' ? `'${item.code}'` : item.code,
displayText: item.value,
className: 'filter-item el-dropdown-menu__item relative-item'
}
})
fieldInfo.operateType = 'select'
}
// 这是一个 远程下拉值
if (matchItem && matchItem.doc && matchItem.doc.dict_location) {
fieldInfo.operateType = 'remoteSelect'
}
fieldInfo._matchItem = matchItem
fieldInfo.name = matchItem.name
fieldInfo.type = matchItem.type
fieldInfo.label = matchItem.label
return fieldInfo
}
getFieldList () {
// 获取字段列表
const fieldList = (this.sourceData && this.sourceData.filtersList) || []
return fieldList
}
getOperatorItem (code) {
const operators = this.sourceData.operatorManual || []
const matchItem = operators.find(item => {
return item.name === code.trim()
})
if (matchItem && !matchItem.label) {
matchItem.label = matchItem.name
}
return matchItem || null
}
dispose () {
this.sourceData = null
this.hintData.operatesList = null
this.hintData.filtersList = null
this.hintData = null
}
}
// 获取数据集
export function getDataset (component, params, list) {
return new Promise((resolve, reject) => {
const schemeInstance = new Scheme(component, params, list)
schemeInstance.getFormatedData((schemeData) => {
const dataset = new Dataset(schemeData)
resolve(dataset, () => {
schemeInstance.dispose()
dataset.dispose()
})
})
})
}

View File

@@ -0,0 +1,94 @@
import {dataTemplate, fieldTemplate} from './template'
export class DeviceTag {
constructor(context, params) {
//先从缓存获取数据
this.queryparams = params
this.context = context
}
filterQueryData(list) {
return list
}
getDataFromRemote() {
//从 远程,也就是请求接口获取数据
return this.context.$get('/deviceTag', this.queryparams).then((res) => {
this.context.initDataReadyCb && this.context.initDataReadyCb(res.data || null); //组件 请求 文件成功回调
if (res.code === 200) {
var data = res.data && res.data.list;
return data
} else {
// this.context.$message({
// message: '请求 DeviceTag数据失败',
// type: 'error',
// showClose: true
// });
}
return null
}).catch((err) => {
console.error(err)
})
}
formatData(data) {
//格式化 获取的数据
const resultData = {
operatesList: [
{
"name": "AND",
"function": "A AND B",
type: "abstract",
label: "AND"
}
],
filtersList: data ? this.filterQueryData(data.fields || []) : [],
//操作符仓库 用于记录 类型 和 操作符之间的映射关系
operatesDic: data ? data.doc.schema_query.references.operator || [] : [],
//operator 记录运算符的label 和 value 映射 以及操作符的使用方法
operatorManual: data ? data.doc.functions.operator || [] : []
}
return resultData
}
organizeData(data=[]) {
var fields = []
var res = JSON.parse(JSON.stringify(dataTemplate));
if (!data ) {
return
}
data.forEach((item, index) => {
var fieldItem = JSON.parse(JSON.stringify(fieldTemplate));
fieldItem.name = item.tagValue
fieldItem.type = "Array"
fieldItem.label = item.tagName
fieldItem.doc.data = (item.subTags || []).map(tagItem => {
return {
code: `'${tagItem.tagValue}'`, //匹配SQL 的时候,这个Code 要加 ''
value: tagItem.tagName,
type: tagItem.tagType
}
})
fields.push(fieldItem)
})
res.fields = fields
return res
}
getFormatedData(callback) {
this.getDataFromRemote().then(data => {
//组织数据 模拟scama
var organizedData = this.organizeData(data);
//格式化数据
this.data = this.formatData(organizedData)
//获取scameData的时候 查询映射字段
callback && callback(this.data)
})
}
dispose(){
this.context=null
this.queryparams=null
this.data=null
}
}

View File

@@ -0,0 +1,150 @@
export class Scheme {
constructor (context, params, list) {
// 先从缓存获取数据
this.queryparams = params
this.context = context
this.columnList = list
this.schemeData = null
this.myCacheData = {
doc: {
functions: {
aggregation: [],
date: [],
operator: [
{
name: '=',
label: '=',
function: 'expr = value'
},
{
name: 'has',
label: 'HAS',
function: 'has(expr, value)'
},
{
name: 'in',
label: 'IN',
function: 'expr in (values)'
},
{
name: 'like',
label: 'LIKE',
function: 'expr like value'
}
]
},
schema_query: {
references: {
aggregation: [
{
type: 'int',
functions: ''
},
{
type: 'string',
functions: ''
},
{
type: 'array',
functions: ''
}
],
operator: [
{
type: 'int',
functions: '=,in,like,has'
},
{
type: 'string',
functions: '=,in,like,has'
},
{
type: 'array',
functions: '=,in,like,has'
}
]
}
}
}
}
}
filterQueryData (list) {
// 仅仅过滤掉 无用的数据 allow_query 为false 的数据 visibility==hidden/disabled DoS日志补充 start_time
const denyList = ['recv_time', 'start_time']
let result
result = list.filter((item, index) => {
// return (item.doc && item.doc.allow_query === 'true') && !denyList.includes(item.name)
// 在前端禁止搜索字段中 或者 接口不允许搜索得字段 过滤掉
return !(item.doc && item.doc.allow_query === 'false') && !denyList.includes(item.name) && !(item.doc && (item.doc.visibility == 'disabled' || item.doc.visibility == 'hidden'))
})
return result || []
}
formatSchemaData (data) {
// 格式化 获取的数据
const formatedData = {
operatesList: [
{
name: 'AND',
function: 'A AND B',
type: 'abstract',
label: 'AND'
}
// {
// name: 'OR',
// function: 'A OR B',
// type: 'abstract',
// label: 'OR'
// }
],
filtersList: data ? this.filterQueryData(data.fields || []) : [],
// 操作符仓库 用于记录 类型 和 操作符之间的映射关系
operatesDic: data ? (data.doc.schema_query && data.doc.schema_query.references.operator) || [] : [],
// 操作符仓库 用于记录 类型 和 聚合函数之间的映射关系
funcDic: data ? (data.doc.schema_query && data.doc.schema_query.references.aggregation) || [] : [],
// operator 记录运算符的label 和 value 映射 以及操作符的使用方法
operatorManual: data ? data.doc.functions.operator || [] : [],
// 全部附属信息 这些是需要的
doc: data.doc,
// 全部字段信息 fields
fields: data.fields
}
// date 和 timestamp 支持的方法 需要特殊处理 ,补充事件函数处理
const dateFuns = (data?.doc?.functions?.date || []).map(item => {
return item.name
})
formatedData.funcDic.forEach(item => {
if (['date', 'timestamp'].includes(item.type)) {
// eslint-disable-next-line no-unused-expressions
dateFuns.length > 0 ? item.functions = item.functions + ',' + dateFuns.join(',') : ''
}
})
return formatedData
}
async getFormatedData (callback) {
const cacheData = this.myCacheData
cacheData.fields = this.columnList
if (this.columnList) {
this.schemeData = this.formatSchemaData(cacheData)
// this.getRemoteOptions()
callback && callback(this.schemeData)
return
}
callback && callback(this.schemeData)
return this.schemeData
}
dispose () {
this.context = null
this.schemeData = null
this.queryparams = null
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,156 @@
export const dataTemplate = {
"doc": {
"functions": {
"aggregation": [
{
"name": "COUNT",
"function": "count(expr)",
"label": "COUNT"
}, {
"name": "COUNT_DISTINCT",
"function": "count(distinct expr)",
"label": "COUNT_DISTINCT"
}, {
"name": "AVG",
"function": "avg(expr)",
"label": "AVG"
}, {
"name": "SUM",
"function": "sum(expr)",
"label": "SUM"
}, {
"name": "MAX",
"function": "max(expr)",
"label": "MAX"
}, {
"name": "MIN",
"function": "min(expr)",
"label": "MIN"
}],
"operator": [
{
"name": "=",
"function": "expr = value",
"label": "="
}, {
"name": "!=",
"function": "expr != value",
"label": "!="
}, {
"name": ">",
"function": "expr > value",
"label": ">"
}, {
"name": "<",
"function": "expr < value",
"label": "<"
}, {
"name": ">=",
"function": "expr >= value",
"label": ">="
}, {
"name": "<=",
"function": "expr <= value",
"label": "<="
}, {
"name": "has",
"function": "has(expr, value)",
"label": "HAS"
}, {
"name": "in",
"function": "expr in (values)",
"label": "IN"
}, {
"name": "not in",
"function": "expr not in (values)",
"label": "NOT IN"
}, {
"name": "like",
"function": "expr like value",
"label": "LIKE"
}, {
"name": "not like",
"function": "expr not like value",
"label": "NOT LIKE"
}, {
"name": "notEmpty",
"function": "notEmpty(expr)",
"label": "NOT EMPTY"
}, {
"name": "empty",
"function": "empty(expr)",
"label": "EMPTY"
}]
},
"schema_query": {
"references": {
"aggregation": [
{
"type": "int",
"functions": "COUNT,COUNT_DISTINCT,AVG,SUM,MAX,MIN"
}, {
"type": "long",
"functions": "COUNT,COUNT_DISTINCT,AVG,SUM,MAX,MIN"
}, {
"type": "float",
"functions": "COUNT,COUNT_DISTINCT,AVG,SUM,MAX,MIN"
}, {
"type": "double",
"functions": "COUNT,COUNT_DISTINCT,AVG,SUM,MAX,MIN"
}, {
"type": "string",
"functions": "COUNT,COUNT_DISTINCT"
}, {
"type": "date",
"functions": "COUNT,COUNT_DISTINCT,MAX,MIN"
}, {
"type": "timestamp",
"functions": "COUNT,COUNT_DISTINCT,MAX,MIN"
}
],
"operator": [
{
"type": "int",
"functions": "=,!=,>,<,>=,<=,in,not in"
}, {
"type": "long",
"functions": "=,!=,>,<,>=,<=,in,not in"
}, {
"type": "float",
"functions": "=,!=,>,<,>=,<="
}, {
"type": "double",
"functions": "=,!=,>,<,>=,<="
}, {
"type": "string",
"functions": "=,!=,in,not in,like,not like,notEmpty,empty"
}, {
"type": "date",
"functions": "=,!=,>,<,>=,<="
}, {
"type": "timestamp",
"functions": "=,!=,>,<,>=,<="
}, {
"type": "array",
"functions": "has"
}]
}
}
},
"fields": []
}
export const fieldTemplate = {
"name": "",
"label": "",
"doc": {
"allow_query": "true",
"visibility": null,
"constraints": {
"type": "tag",
"operator_functions": "in,not in"
},
"data": []
},
"type": "Array"
}

View File

@@ -1,35 +1,39 @@
<template>
<div class="pagination" >
<el-pagination
ref="page"
@size-change="size"
@prev-click="prev"
@next-click="next"
@current-change="current"
:current-page="pageObj.pageNo"
:page-sizes="pageSizes?pageSizes:[20, 50, 100]"
:page-size="Number(pageObj.pageSize)"
:layout="layout"
:total="pageObj.total"
v-bind="bind"
>
<el-select v-model="pageSize" :placeholder="pageSize+$t('pageSize')" size="mini"
:popper-append-to-body="appendToBody" class="pagination-size-select" @change="size"
:popper-class="popClass" @visible-change="popperVisible">
<el-option v-for="(item, index) in pageSizes" :key="index" :label="item.label" :value="item.value"></el-option>
</el-select>
<el-config-provider :locale="locale">
<el-pagination
ref="page"
@size-change="size"
@prev-click="prev"
@next-click="next"
@current-change="current"
:current-page="pageObj.pageNo"
:page-sizes="pageSizes?pageSizes:[20, 50, 100]"
:page-size="Number(pageObj.pageSize)"
:layout="layout"
:total="pageObj.total"
v-bind="bind"
>
<el-select v-model="pageSize" :placeholder="pageSize+$t('pageSize')" size="mini"
:popper-append-to-body="appendToBody" class="pagination-size-select" @change="size"
:popper-class="popClass" @visible-change="popperVisible">
<el-option v-for="(item, index) in pageSizes" :key="index" :label="item.label" :value="item.value"></el-option>
</el-select>
</el-pagination>
</el-pagination>
</el-config-provider>
</div>
</template>
<script>
import { defaultPageSize, storageKey } from '@/utils/constants'
import { defaultPageSize, storageKey, ZH, EN } from '@/utils/constants'
import { urlParamsHandler, overwriteUrl } from '@/utils/tools'
import { ref } from 'vue'
import { useRoute } from 'vue-router'
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 {
name: 'pagination',
@@ -58,24 +62,19 @@ export default {
*/
setup (props) {
const { query } = useRoute()
// pageSize取值顺序1.url2.缓存3.pageObj4.默认值
const urlPageSize = parseInt(query.pageSize)
const cachePageSize = parseInt(localStorage.getItem(storageKey.pageSize + '-' + localStorage.getItem(storageKey.username) + '-' + props.tableId))
const pageObjPageSize = props.pageObj.pageSize
const pageSize = ref(urlPageSize || cachePageSize || pageObjPageSize || defaultPageSize)
const pageSize = ref(defaultPageSize)
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 {
pageSize,
currentPageNo
currentPageNo,
locale
}
},
mounted () {
if (this.postPageSizes && this.postPageSizes.length > 0) {
this.resetPageSizes()
}
this.currentPageNo = parseInt(this.currentPageNo)
this.current(this.currentPageNo)
},
data () {
return {
// pageSize: defaultPageSize,
@@ -158,7 +157,6 @@ export default {
size (val) {
// eslint-disable-next-line vue/no-mutating-props
// this.pageObj.pageNo = 1
this.$emit('scrollbarToTop')
this.$emit('pageSize', val)
this.backgroundColor()
@@ -175,12 +173,11 @@ export default {
wrap.scrollTop = 0
}
})
this.$emit('scrollbarToTop')
})
},
resetPageSizes: function () {
if (this.postPageSizes) {
this.pageSizes = this.postPageSizes.map(item => {
this.pageSizes = this.postPageSizes.map((item) => {
return {
label: item + this.$t('pageSize'),
value: item
@@ -196,6 +193,20 @@ export default {
// }
}
},
mounted () {
if (this.postPageSizes && this.postPageSizes.length > 0) {
this.pageSize = this.postPageSizes[0]
this.resetPageSizes()
} else {
const pageSize = localStorage.getItem(storageKey.pageSize + '-' + localStorage.getItem(storageKey.username) + '-' + this.tableId)
if (pageSize != 'undefined' && pageSize != null) {
this.pageSize = parseInt(pageSize)
}
}
this.currentPageNo = parseInt(this.currentPageNo)
this.current(this.currentPageNo)
},
watch: {
postPageSizes: {
immediate: true,

View File

@@ -2,10 +2,10 @@
<div v-ele-click-outside="changeDropdown" style="position: relative;" class="date-range-box">
<div @click="showDropdown" class="date-range-text" :class="myClass" :style="style">
<div class="calendar-popover-text"><i class="cn-icon cn-icon-Data"></i></div>
<div class="calendar-popover-text" style="display: flex" v-if="isCustom">
<div class="calendar-popover-text">{{ dateFormatByAppearance(getMillisecond(startTime)) }}</div>
<div class="calendar-popover-text"> -</div>
<div class="calendar-popover-text">{{ dateFormatByAppearance(getMillisecond(endTime)) }}</div>
<div class="calendar-popover-text" style="display: flex" v-if="isCustom" :title="`${dateFormatByAppearance(getMillisecond(startTime))} -${dateFormatByAppearance(getMillisecond(endTime))}`">
<div class="calendar-popover-text" :style="showPosition === 'left' ? 'width: 90px;text-overflow: ellipsis;overflow: hidden;' : ''" >{{ dateFormatByAppearance(getMillisecond(startTime)) }}</div>
<div class="calendar-popover-text" :style="showPosition === 'left' ? 'display:none;' : ''"> -</div>
<div class="calendar-popover-text" :style="showPosition === 'left' ? 'display:none;' : ''">{{ dateFormatByAppearance(getMillisecond(endTime)) }}</div>
</div>
<div class="calendar-popover-text" v-else>
{{ showDetail }}
@@ -14,32 +14,36 @@
<i class="cn-icon cn-icon-dropdown" :class="dropdownFlag ? 'cn-icon-up' : ''"></i>
</div>
</div>
<transition name="el-zoom-in-top" style="z-index: 4;">
<div v-if="dropdownFlag" class="date-range-panel">
<transition name="el-zoom-in-top" style="z-index: 100004;">
<div v-if="dropdownFlag" class="date-range-panel" :style="showPosition === 'left' ? leftStyle : rightStyle">
<el-row class="date-range-panel-top" style="position: relative">
<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>
<el-date-picker
v-model="newDateValue"
ref="newDatePicker"
popper-class="my-date-picker"
style="position: absolute;top: -53px;left: -536px;"
:clearable="false"
:default-time="defaultTime"
:unlink-panels="true"
type="datetimerange"
@change="timeArrChange"
/>
<div class="content-title">From</div>
<div class="date-range-title" style="padding-left: 0">{{$t('dateTime.absoluteTimeRange')}}</div>
<el-config-provider :locale="locale">
<el-date-picker
v-model="newDateValue"
:key="keyValue"
ref="newDatePicker"
popper-class="my-date-picker"
:style="showPosition === 'left' ? datePickerLeftStyle : datePickerRightStyle"
:clearable="false"
:default-time="defaultTime"
:unlink-panels="true"
type="datetimerange"
@blur="datePickerVisibleChange"
@change="timeArrChange"
/>
</el-config-provider>
<div class="content-title">{{$t('dateTime.from')}}</div>
<div @click="myDatePickerShow" tabindex="1" class="content-input">
{{ dateFormatByAppearance(getMillisecond(myStartTime)) }}
</div>
<div class="content-title">To</div>
<div class="content-title">{{$t('dateTime.to')}}</div>
<div @click="myDatePickerShow" tabindex="2" class="content-input">
{{ dateFormatByAppearance(getMillisecond(myEndTime)) }}
</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 v-for="(item, index) in rangeHistoryArr" :key="index" class="date-range-history-item"
@click="historyChange(item)">
@@ -53,7 +57,7 @@
:span="8"
class="date-range-panel-content date-range-panel-content-right"
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">
<li v-for="item in dateRangeArr"
@click="quickChange(item.value)"
@@ -68,7 +72,7 @@
</ul>
</el-col>
</el-row>
<el-row class="date-range-panel-bottom">
<el-row class="date-range-panel-bottom" >
<el-col :span="12">{{ address }}</el-col>
<el-col :span="12" class="utc-str">{{ utcStr }}</el-col>
</el-row>
@@ -79,9 +83,12 @@
<script>
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 { 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 {
name: 'DateTimeRange',
@@ -102,9 +109,49 @@ export default {
},
style: {
type: String
},
showPosition: {
type: String,
default: 'right'
}
},
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
}
},
methods: {
/**
* 时间选择器失去焦点之后就会隐藏此时Left展示类型的就需要重新设置下拉框的位置
*/
datePickerVisibleChange () {
if (this.showPosition === 'left') {
this.leftStyle = this.leftStyleBefore
// this.dropdownFlag = true
}
}
},
setup (props, ctx) {
// data
const store = useStore()
@@ -124,24 +171,18 @@ export default {
const rangeHistory = ref(localStorage.getItem(storageKey.dataRangeHistory) ? JSON.parse(localStorage.getItem(storageKey.dataRangeHistory)) : [])
const dateRangeValue = props.dateRange ? ref(props.dateRange) : ref(60)
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)
// 默认日历选择时间即开始时间YYYY-MM-DD 00:00:00,结束时间YYYY-MM-DD 59:59:59
const defaultTime = ref([
new Date(2023, 1, 1, 0, 0, 0),
new Date(2023, 1, 2, 23, 59, 59)
])
const rightStyle = 'position: absolute;top: 32px;right: 0px;'
const leftStyleBefore = 'position: absolute;top: 32px;left: 0px;'
const leftStyleAfter = 'position: absolute;top: 32px;left: 660px;'
const datePickerRightStyle = 'position: absolute;top: -53px;left: -536px;'
const datePickerLeftStyle = 'position: absolute;top: -53px;left: -536px;'
const leftStyle = ref('position: absolute;top: 32px;left: 0px;')
// computed
const utcStr = computed(() => {
let str = 'UTC '
@@ -159,13 +200,6 @@ export default {
str += ':00 '
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
// refs
@@ -186,6 +220,17 @@ export default {
}
})
/**
* 监测下拉框,一旦隐藏,则设置其位置靠最左边
* */
watch(() => dropdownFlag.value, (newVal) => {
if (!newVal) {
if (props.showPosition === 'left') {
leftStyle.value = leftStyleBefore
}
}
})
// methods
/**
* 打开/关闭时间面板
@@ -204,14 +249,14 @@ export default {
if (dropdownFlag.value) {
dropdownFlag.value = false
}
if (dropdownFlag.value) {
dropdownFlag.value = false
}
}
/**
* 打开时间选择器,从时间面板的“开始时间”、“结束时间”调用
*/
const myDatePickerShow = () => {
if (props.showPosition === 'left') {
leftStyle.value = leftStyleAfter
}
newDateValue.value = [
new Date(...timestampToList(myStartTime.value)),
new Date(...timestampToList(myEndTime.value))
@@ -254,12 +299,14 @@ export default {
const returnValue = () => {
store.commit('setTimeFilter', { startTime: myStartTime.value, endTime: myEndTime.value, range: dateRangeValue.value })
cancelHttp()
const obj = rangeHistory.value.find(d => d.start === myStartTime.value && d.end === myEndTime.value)
if (!obj) {
rangeHistory.value.unshift({
start: myStartTime.value,
end: myEndTime.value
})
if (rangeHistory.value[0]) {
const d = rangeHistory.value[0]
if (d.start !== myStartTime.value || d.end !== myEndTime.value) {
rangeHistory.value.unshift({
start: myStartTime.value,
end: myEndTime.value
})
}
}
if (!rangeHistory.value[0]) {
rangeHistory.value.unshift({
@@ -287,6 +334,14 @@ export default {
})
}
}
const language = localStorage.getItem(storageKey.language) || EN // 初始未选择默认 en 英文
let locale = en
if (language === ZH) {
locale = cn
}
const keyValue = window.$dayJs.tz().valueOf()
return {
myStartTime,
myEndTime,
@@ -295,23 +350,28 @@ export default {
utcStr,
rangeEchartsData,
address,
dateRangeArr,
defaultTime,
dateRangeValue,
isCustom,
newDateValue,
newDatePicker,
showDetail,
rangeHistory,
rangeHistoryArr,
getMillisecond,
datePickerLeftStyle,
datePickerRightStyle,
leftStyle,
leftStyleBefore,
rightStyle,
keyValue,
myDatePickerShow,
showDropdown,
changeDropdown,
timeArrChange,
returnValue,
quickChange,
historyChange
historyChange,
locale
}
}
}

View File

@@ -58,7 +58,7 @@
<script>
import axios from 'axios'
import { storageKey } from '@/utils/constants'
import { storageKey, EN } from '@/utils/constants'
export default {
name: 'TopToolMoreOptions',
@@ -108,7 +108,7 @@ export default {
if (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 => {
if (response.status === 200 && response.data.msg === 'success') {
this.$message({ duration: 2000, type: 'success', message: this.$t('tip.importSuccess') })
@@ -128,7 +128,7 @@ export default {
this.importFile = null
},
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'
let url = null
@@ -159,7 +159,7 @@ export default {
})
}
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.closeDialog()

View File

@@ -13,12 +13,12 @@
<template #dropdown>
<el-dropdown-menu>
<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
</div>
</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>
</el-dropdown-item>
@@ -42,11 +42,11 @@
</div>
<div class="cn-header__nav">
<i class="cn-icon cn-icon-a-NetworkAnalytics"></i>
<el-breadcrumb class="header__left-breadcrumb" separator=">">
<el-breadcrumb-item class="header__left-breadcrumb-item" :id="`breadcrumb${item.value}`" :title="item.value"
<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="index===3?item.value:''"
v-for="(item,index) in breadcrumb" :key="item.value">
<template v-if="index===3">
<div class="header__left-breadcrumb-item-select">
<template v-if="index===3" >
<div class="header__left-breadcrumb-item-select" >
<el-popover placement="bottom-start"
ref="breadcrumbPopover"
:show-arrow="false"
@@ -99,7 +99,7 @@
</div>
</template>
<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>
</template>
<!-- index=0和index=1的点击跳转由breadcrumb里的数据控制 -->
@@ -113,6 +113,13 @@
</template>
</el-breadcrumb-item>
</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>
<!-- 菜单 -->
@@ -125,7 +132,7 @@
:show-close="false"
>
<div class="cn-menu__left" v-if="otherMenu">
<div class="left-menu" v-for="menu in otherMenu" :key="menu.id" @click="jump(menu.route,'','',0)">
<div class="left-menu" v-for="menu in otherMenu" :key="menu.id" @click="jumpOther(menu.route,'','',0)">
<i :class="menu.icon"></i>
<span>{{ $t(menu.i18n || menu.name) }}</span>
<i class="cn-icon cn-icon-right"></i>
@@ -196,7 +203,9 @@ import {
networkTable,
operationType,
storageKey,
wholeScreenRouterMapping
wholeScreenRouterMapping,
ZH,
EN
} from '@/utils/constants'
import { api } from '@/utils/api'
import { ref } from 'vue'
@@ -234,7 +243,7 @@ export default {
return {
username: localStorage.getItem(storageKey.username),
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,
from: '', // entity类型
changePassForm: {
@@ -296,7 +305,9 @@ export default {
curTabState: curTabState,
urlChangeParams: {},
wholeScreenRouterMapping,
logo: 'images/logo-header.svg'
logo: 'images/logo-header.svg',
ZH,
EN
}
},
computed: {
@@ -304,7 +315,7 @@ export default {
return this.$store.getters.menuList.find(menu => menu.code === 'networkAnalytics')
},
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) {
for (let i = 0; i < menu.length; i++) {
@@ -322,15 +333,17 @@ export default {
breadcrumb () {
const breadcrumb = []
this.generateBreadcrumb(breadcrumb, this.$store.getters.menuList)
// 写死一级和二级菜单是否可以点击跳转
if (breadcrumb[0]) {
if (['knowledgeBase'].indexOf(breadcrumb[0].code) > -1) {
breadcrumb[0].clickable = true
}
if (breadcrumb[1]) {
if (breadcrumb[1].route && breadcrumb[1].route.indexOf('/panel/') === 0) {
breadcrumb[1].clickable = true
}
if (breadcrumb) {
// panel菜单是否可以点击跳转一级菜单不可点击二级菜单可以点击
if (breadcrumb[0] && breadcrumb[1] && breadcrumb[1].route &&
breadcrumb[1].route.indexOf('/panel/') === 0) {
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) {
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) {
this.dnsQtypeMapData = await getDnsMapData('dnsQtype')
}
@@ -384,7 +397,7 @@ export default {
async mounted () {
this.from = Object.keys(this.entityType)[0]
// 是否需要dns的qtype和rcode的数据字典
if (this.$route.params.typeName === fromRoute.dnsServiceInsights) {
if (this.$route.path.replace('/panel/', '') === fromRoute.dnsServiceInsights) {
if (this.dnsQtypeMapData.size === 0) {
this.dnsQtypeMapData = await getDnsMapData('dnsQtype')
}
@@ -392,7 +405,7 @@ export default {
this.dnsRcodeMapData = await getDnsMapData('dnsRcode')
}
}
let path = this.$route.path;
const path = this.$route.path
if (path.indexOf('panel') > -1 && path.indexOf('linkMonitor') === -1) {
await this.initDropdownList()
}
@@ -405,10 +418,10 @@ export default {
const endTimeParam = query.endTime
// 若url携带了使用携带的值否则使用默认值。
const dateRangeValue = rangeParam ? parseInt(query.range) : 60
const dateRangeValue = rangeParam ? parseInt(rangeParam) : 60
const chartTimeFilter = ref({ dateRangeValue })
if (!startTimeParam || !endTimeParam) {
const { startTime, endTime } = getNowTime(60)
const { startTime, endTime } = getNowTime(dateRangeValue)
chartTimeFilter.value.startTime = startTime
chartTimeFilter.value.endTime = endTime
} else {
@@ -423,55 +436,6 @@ export default {
},
methods: {
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)
if (menu) {
breadcrumb.unshift({
@@ -523,7 +487,7 @@ export default {
},
getCurTabByLabel (label) {
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
if (curTableInCode && curTableInCode.tabList) {
curTab = curTableInCode.tabList.find(item => item.label === label)
@@ -536,7 +500,7 @@ export default {
const currentValue = document.getElementById('breadcrumbValue') ? document.getElementById('breadcrumbValue').innerText : ''
const columnName = this.getUrlParam(this.curTabState.thirdMenu, '')
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
if (curTableInCode && curTableInCode.tabList) {
const curTab = curTableInCode.tabList.find(item => item.label === columnName)
@@ -560,7 +524,7 @@ export default {
axios.get(curTableInCode.url.drilldownList, { params }).then(async response => {
if (response.status === 200) {
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) {
this.dnsQtypeMapData = await getDnsMapData('dnsQtype')
}
@@ -627,21 +591,28 @@ export default {
if (curTab.prop === 'protocolPort') {
const valueGroup = value.split(':')
if (valueGroup) {
queryCondition.push('common_l7_protocol=\'' + valueGroup[0] + '\'')
queryCondition.push('common_server_port=' + valueGroup[1])
queryCondition.push('l7_protocol=\'' + valueGroup[0] + '\'')
queryCondition.push('server_port=' + valueGroup[1])
}
// console.log(queryCondition.join(' AND '))
this.urlChangeParams[this.curTabState.queryCondition] = queryCondition.join(' AND ')
this.urlChangeParams[this.curTabState.lineQueryCondition] = queryCondition.join(' AND ')
} else {
searchProps.forEach(item => {
queryCondition.push(item + '=\'' + handleSpecialValue(value) + '\'')
})
if (curTab.queryCondition) {
curTab.queryCondition.forEach(item => {
queryCondition.push(item.replaceAll('$param', value))
})
} else if (searchProps) {
searchProps.forEach(item => {
queryCondition.push(item + '=\'' + handleSpecialValue(value) + '\'')
})
}
this.urlChangeParams[this.curTabState.queryCondition] = queryCondition.join(' OR ')
const lineQueryCondition = []
if (curTab.lineQueryCondition) {
curTab.lineQueryCondition.forEach(item => {
lineQueryCondition.push(item.replaceAll('$param', value))
lineQueryCondition.push(item.replaceAll('$param', handleSpecialValue(value)))
})
this.urlChangeParams[this.curTabState.lineQueryCondition] = lineQueryCondition.join(' OR ')
}
@@ -691,7 +662,7 @@ export default {
},
async handleCurDrilldownTableConfig (thirdMenu) {
// 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 drillDownTableConfigs = await combineDrilldownTableWithUserConfig()
const currentTableConfig = drillDownTableConfigs.find(config => config.route === tableType)
@@ -712,7 +683,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) {
route = route.replace('redirect:', '')
if (route === '/panel/linkMonitor' && opeType === 3) {
return true
}
@@ -732,7 +722,7 @@ export default {
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 curTab = await getDefaultCurTab(tableType, metric, columnName)
this.$store.getters.menuList.forEach(menu => {
@@ -744,11 +734,6 @@ export default {
child.columnName = columnName
this.urlChangeParams[this.curTabState.thirdMenu] = columnName
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
} else if (columnName) { // 点击的为列名
child.columnValue = ''
@@ -786,6 +771,7 @@ export default {
t: +new Date()
}
})
return
} else if (opeType === 3) {
this.$router.push({
query: {
@@ -794,6 +780,7 @@ export default {
t: +new Date()
}
})
return
} else if (opeType !== 4) {
this.$router.push({
query: {
@@ -803,11 +790,12 @@ export default {
t: +new Date()
}
})
return
}
/* if (route === this.route) {
if (route === this.route) {
this.refresh()
return
} */
}
if (route) {
this.$router.push({
path: route,

View File

@@ -25,7 +25,7 @@
@change="timeConfigTypeChange"
>
<template v-for="time in timeRuleList" :key="time.value">
<el-option :label="time.name" :value="time.value"></el-option>
<el-option :label="$t(time.name)" :value="time.value"></el-option>
</template>
</el-select>
<template v-if="editObject.config.timeConfig.type === 'this'">
@@ -38,7 +38,7 @@
size="small"
@change="()=>{ this.$forceUpdate() }">
<template v-for="time in timeUnitList" :key="time.value">
<el-option :label="time.name" :value="time.value"></el-option>
<el-option :label="$t(time.name)" :value="time.value"></el-option>
</template>
</el-select>
</template>
@@ -56,7 +56,7 @@
size="small"
@change="()=>{ this.$forceUpdate() }">
<template v-for="time in timeUnitList" :key="time.value">
<el-option :label="time.name" :value="time.value"></el-option>
<el-option :label="$t(time.name)" :value="time.value"></el-option>
</template>
</el-select>
</div>
@@ -154,7 +154,6 @@
<el-checkbox v-for="item in dateList" :key="item" :label="item"/>
</el-checkbox-group>
</div>
<div class="enable-month-value">*一月三月五月七月八月十月和12月含31天;闰年二月含29天*</div>
</template>
<!-- 按周 -->
<template v-else-if="monthScheduleType === 'weekly'">
@@ -191,6 +190,7 @@
:format="dateFormat"
prefix-icon="cn-icon cn-icon-shijian"
type="datetime"
@change="onChangeSchedulerStart"
placeholder=" "
/>
</div>
@@ -207,6 +207,7 @@
:format="dateFormat"
prefix-icon="cn-icon cn-icon-shijian"
type="datetime"
@change="onChangeSchedulerEnd"
placeholder=" "
/>
</div>
@@ -243,7 +244,7 @@
placeholder=" "
filterable
:disabled="!!editObject.id"
popper-class="right-box-select-dropdown right-box-select-report "
popper-class="search-select right-box-select-dropdown right-box-select-report "
size="small"
>
<template #prefix>
@@ -277,7 +278,7 @@ import { storageKey, report } from '@/utils/constants'
import { api } from '@/utils/api'
import _ from 'lodash'
import axios from 'axios'
import { dateFormat, getMillisecond } from '@/utils/date-util'
import { dateFormat, getMillisecond, millTimestampDiffFromTz } from '@/utils/date-util'
import { ref, getCurrentInstance } from 'vue'
import i18n from '@/i18n'
export default {
@@ -293,10 +294,12 @@ export default {
const startTime = ref('')
const endTime = ref('')
function endTimeChange (val) {
endTime.value = val
// endTime.value = val + millTimestampDiffFromTz()
endTime.value = getMillisecond(val) + millTimestampDiffFromTz()
}
function startTimeChang (val) {
startTime.value = val
// startTime.value = val + millTimestampDiffFromTz()
startTime.value = getMillisecond(val) + millTimestampDiffFromTz()
}
const endDisabledDate = (time) => {
if (time.getTime() > new Date()) {
@@ -333,7 +336,7 @@ export default {
}
const startTimeValidator = (rule, value, callback) => {
const form = proxy.$refs.reportForm
if (form.model.config.endTime) {
if (form.model && form.model.config && form.model.config.endTime) {
form.validateField('config.endTime', () => null)
}
callback()
@@ -353,9 +356,6 @@ export default {
categoryId: [
{ required: true, message: i18n.global.t('validate.required'), trigger: 'change' }
],
schedulerStart: [
{ required: true, message: i18n.global.t('validate.required'), trigger: 'change' }
],
'config.startTime': [
{ required: true, message: i18n.global.t('validate.required'), trigger: 'change' },
{ validator: startTimeValidator, trigger: 'change' }
@@ -369,15 +369,53 @@ export default {
{ validator: paramValidator, message: i18n.global.t('validate.required'), trigger: 'blur' }
]
}
const mySchedulerStart = ref('')
const mySchedulerEnd = ref('')
const onChangeSchedulerStart = (val) => {
mySchedulerStart.value = getMillisecond(val) + millTimestampDiffFromTz()
}
const onChangeSchedulerEnd = (val) => {
mySchedulerEnd.value = getMillisecond(val) + millTimestampDiffFromTz()
}
return {
endDisabledDate,
startDisabledDate,
startTimeChang,
endTimeChange,
rules
rules,
startTime,
endTime,
mySchedulerStart,
mySchedulerEnd,
onChangeSchedulerStart,
onChangeSchedulerEnd
}
},
data () {
const schedulerStartTimeValidator = (rule, value, callback) => {
if (this.editObject.schedulerEnd) {
if (this.$refs.reportForm) {
this.$refs.reportForm.validateField('schedulerEnd')
}
}
callback()
}
const schedulerEndTimeValidator = (rule, value, callback) => {
if (value && this.editObject.schedulerStart && (getMillisecond(this.editObject.schedulerStart) >= getMillisecond(value))) {
callback(new Error(this.$t('config.user.timeVerification')))
} else {
callback()
}
}
this.rules.schedulerStart = [
{ required: true, message: i18n.global.t('validate.required'), trigger: 'change' },
{ validator: schedulerStartTimeValidator, trigger: 'change' }
]
this.rules.schedulerEnd = [
{ validator: schedulerEndTimeValidator, trigger: 'change' }
]
return {
url: api.reportTemp,
@@ -404,7 +442,9 @@ export default {
monthWeekdayCheckedAll: false,
monthWeekdayIsIndeterminate: false,
paramsOptions: []
paramsOptions: [],
newParamsOptions: [],
rangeNumber: 10
}
},
watch: {
@@ -415,6 +455,7 @@ export default {
scheduleChecked (n) {
this.editObject.config.isScheduler = n ? 1 : 0
this.cleanScheduleConfig()
this.initDateCalendarPreIcon()
},
monthScheduleType (n) {
this.cleanScheduleConfig()
@@ -496,6 +537,14 @@ export default {
}
}
if (n.config) {
if (n.config.startTime) {
this.editObject.config.startTime = getMillisecond(n.config.startTime) - millTimestampDiffFromTz()
}
if (n.config.endTime) {
this.editObject.config.endTime = getMillisecond(n.config.endTime) - millTimestampDiffFromTz()
}
}
if (n.schedulerStart) {
this.editObject.schedulerStart = dateFormat(this.editObject.schedulerStart, this.dateFormat)
}
@@ -522,10 +571,24 @@ export default {
}
}
},
mounted () {
this.initDateCalendarPreIcon()
},
methods: {
initDateCalendarPreIcon () {
this.$nextTick(() => {
const datePrefixIcon = document.getElementsByClassName('el-input__prefix-inner')
if (datePrefixIcon && datePrefixIcon.length > 0) {
Array.prototype.forEach.call(datePrefixIcon, function (element) {
element.innerHTML = '<i class="el-input__icon cn-icon cn-icon-shijian"></i>'
})
}
})
},
loadParamOptions () {
if (_.isArray(this.editObject.categoryParams) && !_.isEmpty(this.editObject.categoryParams)) {
this.editObject.categoryParams.forEach(param => {
// this.paramsOptions = _.cloneDeep(this.newParamsOptions)
if (!this.paramsOptions.some(p => p.key === param.key)) {
axios.get(api.dict, { params: { type: param.key, pageSize: -1 } }).then(response => {
if (response.status === 200) {
@@ -533,6 +596,7 @@ export default {
key: param.key,
options: response.data.data.list.map(d => d.value)
})
// this.newParamsOptions = this.paramsOptions
}
})
}
@@ -570,6 +634,7 @@ export default {
if (val === 'customize') {
this.scheduleChecked = false
}
this.initDateCalendarPreIcon()
},
scheduleTypeChange (val) {
this.scheduleType = val
@@ -585,10 +650,12 @@ export default {
let schedulerStart = ''
let schedulerEnd = ''
if (this.editObject.config && this.editObject.config.startTime) {
startTime = getMillisecond(this.editObject.config.startTime)
// startTime = getMillisecond(this.editObject.config.startTime)
startTime = this.startTime ? this.startTime : this.object.config.startTime
}
if (this.editObject.config && this.editObject.config.endTime) {
endTime = getMillisecond(this.editObject.config.endTime)
// endTime = getMillisecond(this.editObject.config.endTime)
endTime = this.endTime ? this.endTime : this.object.config.endTime
}
if (this.editObject.config && this.editObject.config.schedulerConfig) {
if (['day', 'week', 'month'].indexOf(this.editObject.config.schedulerConfig.type) > -1) {
@@ -598,10 +665,12 @@ export default {
}
}
if (this.editObject.schedulerStart) {
schedulerStart = getMillisecond(this.editObject.schedulerStart)
// schedulerStart = getMillisecond(this.editObject.schedulerStart)
schedulerStart = this.mySchedulerStart ? this.mySchedulerStart : this.object.schedulerStart
}
if (this.editObject.schedulerEnd) {
schedulerEnd = getMillisecond(this.editObject.schedulerEnd)
// schedulerEnd = getMillisecond(this.editObject.schedulerEnd)
schedulerEnd = this.mySchedulerEnd ? this.mySchedulerEnd : this.object.schedulerEnd
}
const copyObject = _.cloneDeep(this.editObject)
@@ -722,6 +791,36 @@ export default {
const checkedCount = val.length
this.monthWeekdayCheckedAll = checkedCount === this.weekdayList.length
this.monthWeekdayIsIndeterminate = checkedCount > 0 && checkedCount < this.weekdayList.length
},
/**
* params的option筛选方法组件自带的方法在paramsOptions的options长度超过1512后会保留部分值
* @param filterVal
*/
filterMethod (filterVal) {
const key = this.editObject.categoryParams[0].key
if (filterVal) {
const obj = this.newParamsOptions.find(d => d.key === key)
if (_.isString(filterVal) && obj) {
const filterArr = _.cloneDeep(obj.options).filter(d => d.toLowerCase().includes(filterVal.toLowerCase()))
const paramsOptionsObj = this.paramsOptions.find(d => d.key === key)
paramsOptionsObj.options = filterArr
} else if (_.isNumber(filterVal) && obj) {
const filterArr = _.cloneDeep(obj.options).filter(d => d.toLowerCase().includes(filterVal))
const paramsOptionsObj = this.paramsOptions.find(d => d.key === key)
paramsOptionsObj.options = filterArr
}
} else {
this.paramsOptions = _.cloneDeep(this.newParamsOptions)
this.rangeNumber = 10
}
},
loadMore () {
this.rangeNumber += 10
},
onVisibleChange (flag) {
if (!flag) {
this.rangeNumber = 0
}
}
}
}

View File

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

View File

@@ -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" @click="selectAllOrNone" :class="{'btn-active':selectAllFlag}"><span ><i class="cn-icon cn-icon-delete"></i></span></button>
</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 }">
<span>
<i v-if="data.type === '1'" class="el-icon-menu"></i>
@@ -90,7 +91,8 @@ export default {
menus: [],
selectedIds: [],
selectAllFlag: false,
expandAllFlag: true
expandAllFlag: true,
isCheckAll: false
}
},
watch: {
@@ -150,9 +152,59 @@ export default {
labelFormatter: function (data, node) {
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) {
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)) {
const parent = this.$refs.menuTree.getNode(node).parent
const 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 () {

View File

@@ -45,7 +45,7 @@
class="right-box__select"
clearable
collapse-tags
placeholder=""
placeholder=" "
popper-class="right-box-select-dropdown prevent-clickoutside"
size="small"
@change="()=>{ this.$forceUpdate() }">
@@ -61,7 +61,7 @@
class="right-box__select"
clearable
collapse-tags
placeholder=""
placeholder=" "
popper-class="right-box-select-dropdown prevent-clickoutside"
size="small">
<template v-for="lang in langData" :key="lang.value">
@@ -70,20 +70,20 @@
</el-select>
</el-form-item>
<!--theme-->
<el-form-item :label="$t('config.user.theme')" prop="i18n">
<!-- <el-form-item :label="$t('config.user.theme')" prop="i18n">
<el-select id="account-input-roleIds"
v-model="editObject.theme"
class="right-box__select"
clearable
collapse-tags
placeholder=""
placeholder=" "
popper-class="right-box-select-dropdown prevent-clickoutside"
size="small">
<template v-for="theme in themeData" :key="theme.value">
<el-option :label="theme.label" :value="theme.value"></el-option>
</template>
</el-select>
</el-form-item>
</el-form-item>-->
<!--enable-->
<el-form-item :label="$t('config.user.enable')">
<el-switch
@@ -125,16 +125,16 @@ export default {
mixins: [rightBoxMixin],
data () {
const validatePin = (rule, value, callback) => { // 确认密码
if (value.length < 5) {
if (value && value.length < 5) {
callback(new Error(this.$t('validate.atLeastFive')))
} else {
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')))
} else if (value !== this.editObject.pin) {
} else if (!_.isEmpty(value) && value !== this.editObject.pin) { // 密码有内容,确认密码也有内容,内容不一致
callback(new Error(this.$t('config.user.confirmPinErr')))
} else {
callback()
@@ -207,7 +207,7 @@ export default {
],
pinChange: [
{ validator: validateConfirmPin, trigger: 'blur' },
{ pattern: /^[a-zA-Z0-9]{5,64}$/, message: this.$t('validate.atLeastFive') }
{ validator: validatePin, trigger: 'blur' }
],
roleIds: [
{ required: true, message: this.$t('validate.required'), trigger: 'blur' }

View File

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

View File

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

View File

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

View File

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

View File

@@ -61,7 +61,7 @@
<div class="reference-tag__group">
<span class="reference-tag" v-for="(refer, index) in scope.row[item.prop].slice(0,2)" >{{refer}}</span>
</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>
</template>
<div class="reference-tag__tip">
@@ -70,9 +70,11 @@
</el-popover>
</templage>
<template v-else>
<template v-for="(refer, index) in scope.row[item.prop]">
<div class="type-tag">{{refer}}</div>
</template>
<div class="reference-tag__show">
<div class="reference-tag__group">
<span class="reference-tag" v-for="(refer, index) in scope.row[item.prop]" >{{refer}}</span>
</div>
</div>
</template>
</template>
<template v-else-if="item.prop === 'opTime' || item.prop === 'ctime'">
@@ -96,6 +98,7 @@
</template>
<template v-else-if="item.prop === 'status'">
<el-switch v-model="scope.row.status"
v-if="hasPermission('editUserDefinedLibrary')"
active-color="#38ACD2"
inactive-color="#C0CEDB"
:active-value="1"
@@ -103,6 +106,15 @@
@change="changeStatus($event,scope.row.knowledgeId)"
>
</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>
<span v-else>{{scope.row[item.prop] || '-'}}</span>
</template>
@@ -117,7 +129,7 @@
<script>
import table from '@/mixins/table'
import { knowledgeBaseCategory, knowledgeBaseSource } from '@/utils/constants'
import { knowledgeBaseCategory, knowledgeBaseSource, knowledgeBaseColor } from '@/utils/constants'
export default {
name: 'KnowledgeBaseTableForRow',
props: {
@@ -151,6 +163,11 @@ export default {
}, {
label: this.$t('knowledge.reference'),
prop: 'reference',
width: 190,
show: true
}, {
label: this.$t('overall.color'),
prop: 'color',
width: 180,
show: true
}, {
@@ -189,7 +206,8 @@ export default {
show: true,
width: 80
}
]
],
knowledgeBaseColor
}
},
watch: {
@@ -221,6 +239,20 @@ export default {
const t = knowledgeBaseSource.find(t => t.value === type)
return t ? t.name : ''
}
},
colorText () {
const vm = this
return function (color) {
const t = vm.knowledgeBaseColor.find(t => t.value === color)
return t ? vm.$t(t.label) : vm.$t(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
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,133 @@
<template>
<el-table
id="pluginTable"
ref="dataTable"
:data="tableData"
:height="height"
empty-text=" "
border
class="plugin"
@header-dragend="dragend"
@sort-change="tableDataSort"
@selection-change="selectionChange"
>
<el-table-column
v-for="(item, index) in customTableTitles"
:key="item.prop+index"
:fixed="item.fixed"
:label="item.label"
:min-width="`${item.minWidth}`"
:prop="item.prop"
:resizable="true"
:sort-orders="['ascending', 'descending']"
:sortable="item.sortable"
:width="`${item.width}`"
>
<template #header>
<span class="data-column__span">{{item.label}}</span>
<div class="col-resize-area"></div>
</template>
<template #default="scope" :column="item">
<template v-if="item.prop === 'triggerStatus'">
<el-switch
v-model="scope.row.triggerStatus"
active-value="1"
inactive-value="0"
@change="()=>{statusChange(scope.row)}">
</el-switch>
</template>
<template v-else-if="item.prop === 'type'">
<span class="type-tag" v-for="type in scope.row.type">{{type}}</span>
</template>
<template v-else-if="item.prop === 'name'">
<div class="plugin-name">
<div class="icon-background"><img class="plugin-name-icon" :src="scope.row['iconUrl']"/></div>
{{scope.row['name']}}
</div>
</template>
<template v-else-if="item.prop === 'description'">
<div class="two-line" :title="$t(scope.row['desc'])">{{$t(scope.row['desc'])}}</div>
</template>
<template v-else-if="item.prop === 'schedule'">
<div class="two-line" >{{$t(scope.row['schedule'])}}</div>
</template>
<span v-else>{{scope.row[item.prop] || '-'}}</span>
</template>
</el-table-column>
<template v-slot:empty >
<div class="table-no-data" v-if="isNoData">
<div class="table-no-data__title">{{ $t('npm.noData') }}</div>
</div>
</template>
</el-table>
</template>
<script>
import table from '@/mixins/table'
import axios from 'axios'
import { api } from '@/utils/api'
import { pluginBasicInfo } from '@/utils/constants'
export default {
name: 'pluginTable',
props: {
isNoData: {
type: Boolean,
default: false
}
},
mixins: [table],
data () {
return {
tableTitle: [ // 原始table列
{
label: this.$t('overall.name'),
prop: 'name',
show: true,
minWidth: 100
}, {
label: this.$t('overall.remark'),
prop: 'description',
show: true,
minWidth: 150
}, {
label: this.$t('overall.type'),
prop: 'type',
show: true,
minWidth: 150
}, {
label: this.$t('config.plugin.schedule'),
prop: 'schedule',
show: true,
minWidth: 150
}, {
label: this.$t('overall.status'),
prop: 'triggerStatus',
show: true,
minWidth: 200
}
]
}
},
computed: {
},
methods: {
statusChange (plugin) {
let triggerStatus = plugin.triggerStatus
let statusUrl = triggerStatus === '1' ? api.pluginStatusEnable : api.pluginStatusDisable
statusUrl = statusUrl.replace('{{id}}', plugin.id)
axios.post(statusUrl).then(response => {
if (response.status === 200) {
this.$message({ duration: 1000, type: 'success', message: this.$t('tip.operateSuccess') })
} else {
this.$message.error(response.data.message)
}
this.$emit('reload')
}).catch(e => {
console.error(e)
this.$message.error(this.errorMsgHandler(e))
})
}
}
}
</script>

View File

@@ -1,16 +1,17 @@
import { createI18n } from 'vue-i18n/index'
import { storageKey } from '@/utils/constants'
import { storageKey, EN } from '@/utils/constants'
import { getI18n } from '@/utils/api'
import store from '@/store'
const i18n = createI18n({
locale: localStorage.getItem(storageKey.language) || 'en'
locale: localStorage.getItem(storageKey.language) || EN
})
export async function loadI18n () {
if (!store.state.i18n) {
const items = await getI18n()
if (items) {
store.commit('loadI18n')
store.state.i18nObj = items
Object.keys(items).forEach(lang => {
i18n.global.mergeLocaleMessage(lang, items[lang])
})

View File

@@ -5,9 +5,8 @@ import router from '@/router'
import store from '@/store'
import App from '@/App.vue'
import '@/utils/http.js'
import { hasPermission } from '@/permission'
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 i18n from '@/i18n'
// import '@/mock/index.js'
@@ -22,6 +21,7 @@ import DateTimeRange from '@/components/common/TimeRange/DateTimeRange'
import TimeRefresh from '@/components/common/TimeRange/TimeRefresh'
import PanelChartList from '@/views/charts/PanelChartList'
import Error from '@/components/common/Error'
import Renderer from '@/components/advancedSearch/showhint/Hint/Renderer'
import 'lib-flexible'
const emitter = new bus()
@@ -37,10 +37,10 @@ app.use(i18n)
app.use(hljsVuePlugin)
app.use(VueGridLayout)
app.directive('has', hasPermission) // 注册指令
app.directive('ele-click-outside', ClickOutside)
app.directive('cancel', cancelWithChange)
app.directive('no-data', noData)
app.directive('high-light', myHighLight)
app.config.globalProperties.$_ = _
app.mixin(commonMixin)
@@ -49,6 +49,7 @@ app.component('date-time-range', DateTimeRange)
app.component('time-refresh', TimeRefresh)
app.component('panel-chart-list', PanelChartList)
app.component('chart-error', Error)
app.component('Renderer', Renderer)
app.mount('#app')

View File

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

View File

@@ -110,64 +110,26 @@ export default {
if (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 => {
if (response.status === 200) {
this.tableData = _.get(response, 'data.data.list', [])
this.pageObj.total = _.get(response, 'data.data.total', 0)
this.isNoData = !this.tableData || this.tableData.length === 0
} else {
console.error(response)
this.isNoData = true
if (response.data.message) {
this.$message.error(response.data.message)
} else {
this.$message.error(this.$t('tip.somethingWentWrong'))
}
}
}).catch(() => {
axios.get(listUrl, { params: this.searchLabel }).then(response => {
if (response.status === 200) {
this.tableData = _.get(response, 'data.data.list', [])
this.pageObj.total = _.get(response, 'data.data.total', 0)
this.isNoData = !this.tableData || this.tableData.length === 0
} else {
console.error(response)
this.isNoData = true
}).finally(() => {
this.toggleLoading(false)
this.loading = false
})
}
if (response.data.message) {
this.$message.error(response.data.message)
} else {
this.$message.error(this.$t('tip.somethingWentWrong'))
}
}
}).catch(() => {
this.isNoData = true
}).finally(() => {
this.toggleLoading(false)
this.loading = false
})
},
del (row) {
this.$confirm(this.$t('tip.confirmDelete'), {
@@ -391,7 +353,9 @@ export default {
},
dragend () {
this.$nextTick(() => {
if (this.$refs.dataTable && this.$refs.dataTable.$refs.dataTable) {
if (this.$refs.dataTable && this.$refs.dataTable.$refs &&
this.$refs.dataTable.$refs.dataTable
) {
this.$refs.dataTable.$refs.dataTable.doLayout()
}
})
@@ -400,10 +364,23 @@ export default {
this.searchLabel.orderBy = orderBy
this.getTableData()
},
search (params) {
search (params, flag, list) {
this.pageObj.pageNo = 1
delete this.searchLabel.category
delete this.searchLabel.source
if (flag !== 'detection') {
delete this.searchLabel.category
delete this.searchLabel.source
}
if (list && list.length > 0) {
if (list.indexOf('status') > -1) {
delete this.searchLabel.status
}
if (list.indexOf('category') > -1) {
delete this.searchLabel.category
}
if (list.indexOf('eventType') > -1) {
delete this.searchLabel.eventType
}
}
this.getTableData(params)
},
getTimeString () {

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