Compare commits

..

314 Commits

Author SHA1 Message Date
Jiangjie.Bai
7e8de4c73b Merge pull request #1061 from jumpserver/dev
merge: dev to master
2021-09-16 20:16:02 +08:00
老广
47a0641fed Merge pull request #1060 from jumpserver/pr@dev@perF_setting
perf: 修改setting
2021-09-16 20:02:35 +08:00
ibuler
ad118c5eda perf: 修改setting 2021-09-16 20:01:38 +08:00
老广
9bd3b37371 Merge pull request #1059 from jumpserver/dev
Merge dev to master
2021-09-16 19:33:01 +08:00
ibuler
274f50da95 fix: 修改select2 2021-09-16 19:09:55 +08:00
xinwen
8f87c42c8c fix: Otp 绑定问题 2021-09-16 19:09:34 +08:00
ibuler
35c793785c perf: 优化 detail formatter 2021-09-16 16:54:01 +08:00
feng626
abe176831b fix: 修复数据库改密bug 2021-09-16 16:53:41 +08:00
xinwen
c97512c99b fix: 重置用户密码提示消息 2021-09-16 16:46:43 +08:00
Jiangjie.Bai
6a8553ddfd Merge pull request #1053 from jumpserver/dev
v2.14.0 rc3
2021-09-15 21:06:24 +08:00
feng626
23f1f371e6 Merge pull request #1052 from jumpserver/pr@dev@mail_setting_bug
fix: 去掉站内信
2021-09-15 20:45:11 +08:00
feng626
0878e93f24 fix: 去掉站内信 2021-09-15 20:36:15 +08:00
fit2bot
cc76a66a34 perf: 优化用户资产页面宽度 (#1049)
Co-authored-by: ibuler <ibuler@qq.com>
2021-09-15 20:32:39 +08:00
fit2bot
d17556cdc7 perf: 优化认证中的url检查 (#1050)
* perf: 优化认证中的url检查

* perf: 优化配置

Co-authored-by: ibuler <ibuler@qq.com>
2021-09-15 20:32:17 +08:00
Michael Bai
f5f4edaf63 fix: 修复vmware账号密码必填标志显示问题 2021-09-15 20:21:53 +08:00
Michael Bai
62329d57a4 fix: 修复系统用户页面资产页中没有显示已关联节点的问题 2021-09-15 19:40:13 +08:00
ibuler
bc90d1e9c6 perf: 优化安全设置,保存后刷新页面 2021-09-15 19:39:35 +08:00
feng626
60d4da29cb Merge pull request #1047 from jumpserver/pr@dev@ticket_perf
perf: 工单流详情优化
2021-09-15 19:34:52 +08:00
feng626
b9bdf35bb9 perf: 工单流详情优化 2021-09-15 19:32:28 +08:00
fit2bot
b9b6bccb7f perf: 优化路由 (#1045)
* perf: 优化路由

* perf: 优化提示

* oerf: ...

Co-authored-by: ibuler <ibuler@qq.com>
2021-09-15 18:56:09 +08:00
xinwen
f2bbe4841a feat: 用户详情页面显示 手机号,钉钉,企业微信,飞书 2021-09-15 18:26:18 +08:00
Michael Bai
d920eeda7a fix: 修改创建/更新系统用户标题文案 2021-09-15 18:05:04 +08:00
ibuler
7361acae6a perf: 优化弹窗显示 2021-09-15 18:04:39 +08:00
xinwen
a56799ca22 fix: 用户不能重置自己的 mfa 2021-09-15 17:28:53 +08:00
Michael Bai
88ddd86154 fix: 修改录像水印 2021-09-15 17:28:07 +08:00
feng626
d9532d5a93 Merge pull request #1040 from jumpserver/pr@dev@ticket_perf
perf: 工单详情样式优化
2021-09-15 14:07:18 +08:00
feng626
02b9e83e3d perf: 工单详情样式优化 2021-09-15 14:03:35 +08:00
feng626
a29afbbeea Merge pull request #1039 from jumpserver/pr@dev@ticket_bug
perf: 工单优化
2021-09-15 11:19:02 +08:00
feng626
0f4f4a2ca7 perf: 工单优化 2021-09-15 11:18:26 +08:00
feng626
a23cd151ae Merge pull request #1037 from jumpserver/pr@dev@ticket_flow_perm
fix: 工单流程权限
2021-09-14 22:26:23 +08:00
feng626
5f3855d8c1 fix: 工单流程权限 2021-09-14 22:12:30 +08:00
ibuler
6ad44e2830 perf: 修复路由问题 2021-09-14 18:54:32 +08:00
ibuler
51446c0c9a perf: 添加会话翻译 2021-09-14 17:43:34 +08:00
feng626
e3a493a6c5 fix: 不同组织资产可读 2021-09-14 17:42:06 +08:00
Michael Bai
9dfca98828 fix: 删除 PERIOD_TASK_ENABLED 配置 2021-09-14 17:41:34 +08:00
Michael Bai
599a1bb705 fix: 修复CAS配置RENAME_ATTR未加载的问题 2021-09-14 16:42:59 +08:00
ibuler
52e4a6d6ed perf: remove debug 2021-09-14 16:41:57 +08:00
ibuler
4b5ac9aaa9 perf: 优化资产列表,拆分大组件 2021-09-14 16:41:57 +08:00
feng626
3b2c4975b6 Merge pull request #1030 from jumpserver/pr@dev@ticket_perf
perf: 工单详情优化
2021-09-14 14:09:55 +08:00
feng626
c3cbafce6d perf: 工单详情优化 2021-09-14 14:00:53 +08:00
ibuler
b75f0be9e5 fix: 修复资产页面table不能拖拽 2021-09-14 11:02:09 +08:00
Jiangjie.Bai
8df0f05ee5 Merge pull request #1028 from jumpserver/dev
v2.14.0 rc2
2021-09-13 21:28:12 +08:00
Michael Bai
fa7a78523f fix: i18n 2021-09-13 20:44:30 +08:00
ibuler
11e803be19 perf: 优化短信通用 2021-09-13 20:40:18 +08:00
Michael Bai
060781443f fix: 授权规则支持通过 from_ticket 工单过滤 2021-09-13 20:25:11 +08:00
fit2bot
555c8fd2d6 perf: 优化短信设置 (#1024)
* perf: 优化短信设置

* perf: 优化分宜

Co-authored-by: ibuler <ibuler@qq.com>
2021-09-13 19:44:14 +08:00
feng626
2c84e4ffa6 fix: 修复工单bug 2021-09-13 14:18:58 +08:00
Michael Bai
16dafaaf42 perf: 取消API Token的配置 2021-09-13 14:03:16 +08:00
xinwen
819438a2e8 fix: 用户消息订阅暂不支持短信 2021-09-13 13:25:20 +08:00
fit2bot
61e8467849 perf: 优化更新auth (#1018)
* perf: 优化更新auth

* perf: 优化提交

Co-authored-by: ibuler <ibuler@qq.com>
2021-09-10 16:46:51 +08:00
feng626
491f3d3dde feat: 添加登陆复合 2021-09-10 16:23:02 +08:00
ibuler
4588965a8d perf: 优化站内信点击的问题 2021-09-10 13:59:19 +08:00
feng626
48d07b4d6d perf: 工单流优化 2021-09-10 13:51:44 +08:00
feng626
45c74ba470 perf: sms样式修改 2021-09-10 13:51:08 +08:00
feng626
2e2c8fe086 feat: 短信迁移 2021-09-10 13:51:08 +08:00
Michael Bai
459843669b perf: 修改授权规则Actions上传下载复制粘贴选择时,连接不可取消 2021-09-10 11:28:36 +08:00
fit2bot
f839e1fc0d perf: 去掉debug (#1013)
* perf: 去掉debug

* perf: 修改 debug

* perf: 修改msg

Co-authored-by: ibuler <ibuler@qq.com>
2021-09-10 10:21:03 +08:00
Jiangjie.Bai
399b381aa9 Merge pull request #1014 from jumpserver/dev
v2.14.0 rc1
2021-09-09 21:19:35 +08:00
Eric_Lee
7670c277aa Merge pull request #1012 from jumpserver/revert-1004-dependabot/npm_and_yarn/axios-0.21.2
Revert "chore(deps): bump axios from 0.21.1 to 0.21.2"
2021-09-09 21:03:52 +08:00
老广
cbc78de7c4 Revert "chore(deps): bump axios from 0.21.1 to 0.21.2"
This reverts commit e8f013a5be.
2021-09-09 21:02:36 +08:00
ibuler
e64cd82dba perf: 修改setting 2021-09-09 20:45:21 +08:00
ibuler
f154a252d3 perf: 修改配置 2021-09-09 20:01:06 +08:00
Michael Bai
984483c470 perf: 用户个人信息页面添加开启消息接受方式 2021-09-09 19:46:31 +08:00
dependabot[bot]
e8f013a5be chore(deps): bump axios from 0.21.1 to 0.21.2
Bumps [axios](https://github.com/axios/axios) from 0.21.1 to 0.21.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/master/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.21.1...v0.21.2)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-09 19:41:02 +08:00
ibuler
e1850d758f perf: 提供xrdp开启禁用 2021-09-09 19:39:28 +08:00
feng626
60e5c994f4 feat: 短信服务配置 2021-09-09 18:53:32 +08:00
xinwen
1adf138175 fix: 用户个人页面,修改个人部分信息使用 patch 2021-09-09 16:16:05 +08:00
fit2bot
4e5ed82ec7 perf: 优化导入导出顺序 (#999)
Co-authored-by: ibuler <ibuler@qq.com>
Co-authored-by: Jiangjie.Bai <32935519+BaiJiangJie@users.noreply.github.com>
2021-09-09 16:15:21 +08:00
fit2cloud-jiangweidong
52e2d58567 feat: 改密计划支持数据库改密 (#991)
* feat: 改密计划支持数据库改密

* perf: 修改应用账号

* merge: dev

* perf: 暂存修改数据库

* fix: bug

* fix: bug

* perf: 优化系统用户详情,增加应用列表

* perf: 修改clone

* fix: 修复更新系统用户bug

Co-authored-by: ibuler <ibuler@qq.com>
Co-authored-by: feng626 <1304903146@qq.com>
2021-09-09 16:04:40 +08:00
fit2bot
411fca98b0 feat: 页面配置serializer版 (#1000)
* feat: 页面配置serializer版

* perf: 优化配置

* perf: 优化匹配值

* perf: 优化设置

* perf: 优化配置

* perf: 修改翻译

* perf: 修改secure

Co-authored-by: feng626 <1304903146@qq.com>
Co-authored-by: ibuler <ibuler@qq.com>
2021-09-09 14:03:47 +08:00
Bai
e0c3243986 feat: 云管中心添加GCP 2021-09-07 18:26:48 +08:00
Jiangjie.Bai
e4240d7a8f feat: 会话详情添加活动页面 (#1003)
* fix: 添加配置 SECURITY_SESSION_SHARE

* fix: 添加会话活动页面

* fix: 添加会话活动页面

* fix: 添加会话活动页面

* fix: 添加会话活动页面
2021-09-07 18:16:38 +08:00
feng626
db4b09603b perf: disabled 2021-09-06 18:16:51 +08:00
Eric
a88499d10c fix: 修复普通用户页面 动作 问题 2021-08-27 15:17:31 +08:00
feng626
0b2cfb88a2 Merge pull request #993 from jumpserver/pr@dev@ticket_flow_bug
perf: 优化
2021-08-27 10:14:17 +08:00
feng626
13443a7137 perf: 优化 2021-08-26 18:31:52 +08:00
fit2bot
5aa3226132 feat: 审批模版管理 (#964)
Co-authored-by: feng626 <1304903146@qq.com>
Co-authored-by: Jiangjie.Bai <32935519+BaiJiangJie@users.noreply.github.com>
2021-08-25 19:01:45 +08:00
fit2bot
ea9316f0a0 feat: 工单管理 (#963)
* feat: 工单多级审批

* fix: action bug

Co-authored-by: feng626 <1304903146@qq.com>
2021-08-25 19:00:21 +08:00
ibuler
d05d3f3fa3 perf: 优化创建简单 2021-08-20 14:22:09 +08:00
Bai
5da000c8a1 fix: 修复AssetSelect弹窗的protocols显示 2021-08-20 14:14:36 +08:00
Jiangjie.Bai
f85d3567a7 Merge pull request #986 from jumpserver/dev
v2.13.0
2021-08-19 18:53:13 +08:00
ibuler
78174b23fd fix: 修复app详情更新按钮 2021-08-19 16:05:10 +08:00
xinwen
07151b2bfe fix: 命令记录默认倒序 2021-08-19 15:47:28 +08:00
ibuler
1cc395c22d perf: 修复 url 2021-08-19 15:36:00 +08:00
ibuler
a18507f9c6 perf: 修复命令导出问题
perf: 优化
2021-08-19 15:00:59 +08:00
ibuler
790e2315e2 perf: 修复app 授权详情中更新 2021-08-19 12:00:03 +08:00
Jiangjie.Bai
4f2f4a7a9d Merge pull request #978 from jumpserver/dev
v2.13.0 rc4
2021-08-18 19:46:06 +08:00
Bai
32817390c2 fix: 修复同步任务创建时协议字段为空的错误提示 2021-08-18 19:33:16 +08:00
Bai
8af7c46f02 feat: 系统设置添加水印开启选项 2021-08-18 19:15:55 +08:00
ibuler
ed4162cbd5 perf: 优化select2初始值
perf: 优化select2

perf: 修改初始值
2021-08-18 15:16:42 +08:00
Eric
973b1fbae2 fix: 修复消息接受人用户搜索问题 2021-08-18 11:50:28 +08:00
ibuler
8906fa3292 fix: 修复资产树点击的bug 2021-08-18 11:13:16 +08:00
Bai
5c94c1c870 fix: 修复管理员更新用户登录规则没有权限的问题 2021-08-18 10:32:13 +08:00
Jiangjie.Bai
16757b7df0 Merge pull request #972 from jumpserver/dev
v2.13.0 rc3
2021-08-17 20:34:39 +08:00
Bai
679103db03 fix: 更新用户,密码规则根据当前用户的原角色(非当前表单角色)进行处理 2021-08-17 20:29:12 +08:00
ibuler
a7aa763e37 fix: 修复导入时页码跳动问题 2021-08-17 20:28:21 +08:00
ibuler
f30ba0a98d perf: 修改应用权限创建的bug
perf: 修改列表

fix: 去掉get url
2021-08-17 14:00:46 +08:00
ibuler
c426841a7c perf: 优化应用授权 2021-08-17 14:00:46 +08:00
老广
288bfe4727 Merge pull request #967 from jumpserver/pr@dev@fix_ldap_setting
fix(settings): 修复ldap settings的bug
2021-08-16 15:09:48 +08:00
ibuler
c4c39188aa fix(settings): 修复ldap settings的bug 2021-08-16 15:07:07 +08:00
老广
52d8bfeac3 Merge pull request #966 from jumpserver/dev
v2.13.0-rc1
2021-08-16 11:24:36 +08:00
ibuler
20c201f351 fix(applications): 修复更新数据库页面不对的问题 2021-08-16 11:01:25 +08:00
老广
94b5aa700d Merge pull request #962 from jumpserver/dev
v2.13.0 rc1
2021-08-13 11:05:47 +08:00
fit2bot
e030fc313e feat: 添加飞书 (#958)
* feat: 添加飞书

* s

* 翻译

* 翻译

Co-authored-by: xinwen <coderWen@126.com>
2021-08-13 11:03:53 +08:00
Jiangjie.Bai
917fa0ea44 Merge pull request #961 from jumpserver/dev
v2.13.0 rc1
2021-08-12 19:51:30 +08:00
Bai
68df311a9c feat: 云管中心同步任务添加 unix_admin_user 和 windows_admin_user 2021-08-11 14:43:25 +08:00
Bai
e819e4aaff feat: 添加Monitor组件 celery和core 2021-08-10 10:17:21 +08:00
feng626
364b6b0bcf perf: 改密计划前端优化 2021-08-06 17:25:11 +08:00
feng626
a0534e0c2e feat: xpack 批量修改密钥 2021-08-03 15:47:29 +08:00
ibuler
0152ad269b perf: 修改import 2021-07-30 19:28:19 +08:00
ibuler
b651f82eb6 perf: 优化树 2021-07-30 17:30:22 +08:00
ibuler
0e665912ff perf: 优化应用授权列表 2021-07-30 17:30:22 +08:00
fit2bot
8929957fd9 perf: 管理员密码长度 (#946)
* feat: 支持配置系统管理员强制MFA和独立密码长度限制

* feat: 支持配置系统管理员强制MFA和独立密码长度限制

* feat: 支持配置系统管理员强制MFA和独立密码长度限制

* feat: 支持配置系统管理员强制MFA和独立密码长度限制

* fix: 修改管理员最小密码长度变量名称

* fix: 修改管理员最小密码长度变量名称

* perf: 基本完成更改

Co-authored-by: fit2cloud-jiangweidong <weidong.jiang@fit2cloud.com>
Co-authored-by: ibuler <ibuler@qq.com>
2021-07-30 15:33:49 +08:00
Bai
959bb56a3e fix: 修复数据库应用更新页面异常问题 2021-07-29 14:57:48 +08:00
fit2bot
cb5fe0f2a0 perf: 使用 mini 接口 (#945)
* perf: 使用 mini 接口

* perf: 资产列表默认展示平台和协议

* perf: 添加排序

Co-authored-by: 吴小白 <296015668@qq.com>
2021-07-29 14:57:20 +08:00
吴小白
05c06e4fc2 feat: 资产授权添加批量删除 2021-07-29 11:33:04 +08:00
ibuler
d3ae9e3f0f perf: 账号列表去掉克隆 2021-07-28 18:20:42 +08:00
fit2bot
1af14a5743 perf: 修改系统用户展现 (#940)
* perf: 优化系统用户select显示

* perf: 修改系统用户展现

Co-authored-by: ibuler <ibuler@qq.com>
2021-07-28 15:18:29 +08:00
feng626
7c0621b357 feat: 应用管理添加批量删除 2021-07-27 16:18:32 +08:00
feng626
4d4085798d fix: 去除前端规则判断 自定义承接后端required 2021-07-27 15:52:34 +08:00
Jiangjie.Bai
0da0c2af4f Merge pull request #931 from jumpserver/pr@dev@perf_account_list
perf: 优化账号列表
2021-07-27 15:52:06 +08:00
ibuler
ba78bdeed7 perf: 还原收集用户 2021-07-27 14:37:00 +08:00
ibuler
0f76f6ad96 perf: 基本完成应用正好改造 2021-07-27 14:18:37 +08:00
Bai
2a14b0cfcd feat: 改密计划执行列表添加trigger字段和搜索项 2021-07-27 10:45:55 +08:00
Jiangjie.Bai
8537a7b6e4 Merge pull request #935 from jumpserver/pr@dev@fix_generic_route_userloginacl
fix: 修改通用创建更新成功后的Route; 优化用户ACL创建ip_group错误时的信息显示
2021-07-26 17:45:25 +08:00
Bai
bf74adc037 fix: 修改通用创建更新成功后的Route; 优化用户ACL创建ip_group错误时的信息显示 2021-07-26 17:20:36 +08:00
Bai
f7d75ca92c fix: 修改通用创建更新成功后的Route; 优化用户ACL创建ip_group错误时的信息显示 2021-07-26 17:11:43 +08:00
fit2bot
0d3029e6b1 perf: 优化表单 (#933)
* perf: 优化表单

* fix: 修复国际化

* fix: 去掉排序

* perf: 系统平台排序

* fix: 修复错误

* fix: 去掉多余的逗号

Co-authored-by: 吴小白 <296015668@qq.com>
2021-07-26 17:09:28 +08:00
feng626
7fda66cda4 工单处理人显示 与右侧一致 2021-07-26 17:04:13 +08:00
ibuler
de4af07858 perf: 修改 icon 2021-07-26 11:09:06 +08:00
Bai
6bab31d74c fix: 修改账号列表更新表单错误提示 2021-07-23 19:02:52 +08:00
ibuler
1c6da15301 perf: 优化账号列表 2021-07-22 18:56:04 +08:00
dependabot[bot]
cbd18814f6 chore(deps): bump ws from 5.2.2 to 5.2.3 (#838)
* chore: 更新Submodule指向

* fix: 修复Build失败的问题

* fix(clone): 修复会话页面产生的clone的问题

* chore(deps): bump ws from 5.2.2 to 5.2.3

Bumps [ws](https://github.com/websockets/ws) from 5.2.2 to 5.2.3.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/5.2.2...5.2.3)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: Orange <orangemtony@gmail.com>
Co-authored-by: Jiangjie.Bai <32935519+BaiJiangJie@users.noreply.github.com>
Co-authored-by: 老广 <ibuler@qq.com>
Co-authored-by: Eric_Lee <xplzv@126.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-22 14:15:37 +08:00
ibuler
3be8794a58 perf: 优化记录当前组织和角色 2021-07-22 14:08:00 +08:00
ibuler
2183cf9bb0 perf: 修复微信钉钉测试两次报错 2021-07-22 14:06:49 +08:00
ibuler
dfd710daf7 perf: 优化打开task页面 2021-07-22 14:02:26 +08:00
ibuler
e38817b24a fix(settings): 修复配置消息订阅时,用户为当前组织的问题 2021-07-21 19:11:51 +08:00
fit2bot
34b18d5220 云管中心同步任务支持设置同步IP网段和协议组 (#925)
* 云管中心同步任务支持设置同步IP网段和协议组

* fix: 修改重复定义的getMethod()和getUrl()等方法

* fix: 修改重复定义的getMethod()和getUrl()等方法2

Co-authored-by: Bai <bugatti_it@163.com>
2021-07-21 19:06:14 +08:00
ibuler
d07b311b76 perf: 去掉首页无意义的标签显示 2021-07-21 10:24:21 +08:00
ibuler
0e67594ba9 perf: 新页面创建资产,创建成功重定向到详情页 2021-07-20 19:33:38 +08:00
wojiushixiaobai
5f3b307785 perf: 优化构建速度 2021-07-20 19:31:18 +08:00
ibuler
96ee47b4ac perf: 优化工单详情 2021-07-20 19:30:34 +08:00
ibuler
574ec53d23 fix(assets): 修复创建网关没有密码的问题 2021-07-20 19:29:52 +08:00
Bai
1720c183ad feat: 资产详情页面添加关联系统用户列表 2021-07-20 17:01:13 +08:00
Jiangjie.Bai
9bbd2ac293 Merge pull request #916 from jumpserver/dev
v2.12.0 rc5
2021-07-15 19:19:49 +08:00
Bai
fc7bb98fbb fix: 修改翻译 2021-07-15 19:18:18 +08:00
Jiangjie.Bai
32d37a4860 Merge pull request #914 from jumpserver/dev
v2.12.0 rc5
2021-07-15 18:24:21 +08:00
xinwen
699b12178a fix: 组织管理页面 管理用户改成特权用户 2021-07-15 18:15:01 +08:00
Jiangjie.Bai
fb87ebd0e7 Merge pull request #912 from jumpserver/dev
v2.12.0 rc5
2021-07-15 17:05:11 +08:00
fit2bot
b8ba53cf5e perf: 添加 系统用户 到 账号 (#911)
Co-authored-by: ibuler <ibuler@qq.com>
2021-07-15 10:30:22 +08:00
Jiangjie.Bai
c3f27dd0a8 Merge pull request #910 from jumpserver/dev
v2.12.0 rc4
2021-07-14 19:03:39 +08:00
Bai
c94544df39 fix: 隐藏ROOT组织资产列表导入按钮 2021-07-14 18:06:43 +08:00
ibuler
35c7b81359 perf: 优化clone 2021-07-14 16:51:09 +08:00
ibuler
e7ae4bfbd3 perf: 优化特权账号创建,都特殊的api 2021-07-14 16:31:59 +08:00
Bai
d1c3c63efc fix: 修改收集用户任务详情也文案 2021-07-14 15:39:59 +08:00
Bai
bfc5ae9bf7 fix: 修改普通用户/特权用户文案 2021-07-14 12:25:33 +08:00
Jiangjie.Bai
fa0c4d66bb Merge pull request #904 from jumpserver/dev
v2.12.0 rc3
2021-07-13 20:47:54 +08:00
ibuler
31d011b1fc perf: 修复账号导出了所有的bug 2021-07-13 19:12:48 +08:00
老广
f4cd0efd29 Merge pull request #901 from jumpserver/dev
v2.12.0 rc3
2021-07-13 19:10:00 +08:00
Bai
f946cf5d11 fix: 完美修复录像存储无法clone的问题 2021-07-13 18:51:07 +08:00
ibuler
8d42071d4a perf: root组织,去掉导入 2021-07-13 18:38:54 +08:00
Bai
b374979b32 fix: 修复网关更新密码代填提交问题 2021-07-13 18:25:02 +08:00
ibuler
430f2e4856 perf: 普通系统用户添加批量处理 2021-07-13 18:24:41 +08:00
ibuler
59a8971af0 perf: 修复消息订阅,取消按钮无法使用的问题 2021-07-13 18:07:10 +08:00
fit2bot
4f70f3bea7 pref: 添加指纹 (#896)
* fix(perms): 修复无法clone的问题

* pref: 添加指纹

Co-authored-by: ibuler <ibuler@qq.com>
2021-07-13 17:26:18 +08:00
ibuler
30ffa13b01 fix(perms): 修复无法clone的问题 2021-07-13 17:22:52 +08:00
fit2bot
4fb8e4073c fix: 修复审计员首页用户数空白的问题 (#894)
Co-authored-by: ibuler <ibuler@qq.com>
2021-07-13 16:24:58 +08:00
Bai
fef38ea007 fix: 修复审计员可以点击dashboard页面的资产/用户总数的问题 2021-07-13 13:44:30 +08:00
ibuler
41c5ac5f12 perf: 优化左侧组织列表,有xpack时直接显示
perf: 优化显示组织
2021-07-13 13:09:03 +08:00
ibuler
4056c601b1 perf: admin sftp root help text 2021-07-13 13:06:03 +08:00
ibuler
34a72467d2 fix: 修复导入时url中有参数导致的拼接错误
perf: 优化一下获取url
2021-07-13 10:56:58 +08:00
fit2bot
e4657f3bf6 fix: 修复系统用户详情中测试连接性 (#889)
Co-authored-by: ibuler <ibuler@qq.com>
2021-07-12 20:32:37 +08:00
Jiangjie.Bai
c6cde723ec Merge pull request #888 from jumpserver/dev
v2.12.0 rc2
2021-07-12 18:29:50 +08:00
ibuler
8b31666f67 fix: 修复管理用户克隆 2021-07-12 18:22:18 +08:00
Bai
0a01ad3552 fix: 修复资产可连接性显示图标 2021-07-12 18:21:47 +08:00
ibuler
8d783dd650 fix: 修复动态系统用户无法提交
fix: 修复动态系统用户
2021-07-12 12:59:12 +08:00
ibuler
425e47d613 perf: 修复资产详情中账号列表 2021-07-08 18:55:43 +08:00
Jiangjie.Bai
2bf83e241e Merge pull request #880 from jumpserver/dev
v2.12 rc1
2021-07-08 15:24:38 +08:00
fit2bot
ec3bdb0eac perf: 显示账号 (#881)
Co-authored-by: ibuler <ibuler@qq.com>
2021-07-08 15:18:36 +08:00
健健
49ee321c83 for k8s system user, hide () (#879)
* chore: 更新Submodule指向

* fix: 修复Build失败的问题

* fix(clone): 修复会话页面产生的clone的问题

* for k8s system user, not show ()

Co-authored-by: Orange <orangemtony@gmail.com>
Co-authored-by: Jiangjie.Bai <32935519+BaiJiangJie@users.noreply.github.com>
Co-authored-by: 老广 <ibuler@qq.com>
Co-authored-by: Eric_Lee <xplzv@126.com>
2021-07-08 14:32:58 +08:00
fit2bot
44f93f5aea perf: 合并系统用户和管理用户 (#878)
* perf: 暂存优化 vault table

* perf: 修改导入出

* refactor: 整合系统用户和管理用户

* perf: 暂存一下

* perf: 暂存

* xxx

* ..

* perf: ...

* .

* xxx

* perf: 优化翻译

* perf: 优化提示

* ...

* 完成list

* xx

* perf: 修改更新

* perf: 完成更新和查看

* ...

* stash it

* perf: 完成下载导入的 mfa

* xxx

* perf: 基本完成

* 修改一点

* perf: 优化资产列表

Co-authored-by: ibuler <ibuler@qq.com>
2021-07-08 14:28:23 +08:00
ibuler
baf6ba7496 fix(settings): 修复系统设置中邮件没有更改就无法更新的问题 2021-06-28 19:03:34 +08:00
Bai
fa9b4abba1 feat: 支持设置默认存储(2) 2021-06-28 10:33:20 +08:00
Bai
35e0d57a05 feat: 支持设置默认存储 2021-06-28 10:33:20 +08:00
jiangweidong
90eebea2e6 fix: 修改华为及青云私有云变量名称 2021-06-21 15:56:41 +08:00
jiangweidong
dab32e33ad feat: 华为私有云、青云私有云云管同步 2021-06-21 15:56:41 +08:00
ibuler
bf877f415a fix: 修复会话详情中播放bug 2021-06-18 18:08:13 +08:00
ibuler
1ffb1d9cab fix(infra): 修复基础平台等可以删除更新问题 2021-06-18 11:05:47 +08:00
ibuler
4d4dc35735 perf: 优化 table 去掉clone
perf: 去掉

perf: xxx
2021-06-18 10:26:59 +08:00
Jiangjie.Bai
95ccc5bc16 Merge pull request #869 from jumpserver/dev
Dev
2021-06-17 15:23:54 +08:00
ibuler
dc112bfee8 fix: 修复创建资产 2021-06-17 15:23:15 +08:00
ibuler
84930b27fa fix: 修复切换组织时的错误提示 2021-06-17 15:22:37 +08:00
Jiangjie.Bai
2bf1496480 Merge pull request #866 from jumpserver/dev
v2.11.0 rc5
2021-06-17 12:23:58 +08:00
Bai
27a78c3b23 fix: 修改应用账号(k8s)导出/查看均显示token字段 2021-06-17 12:13:41 +08:00
ibuler
95007a52a5 fix(assets): 修复资产创建时,没有把节点带过去的问题 2021-06-17 12:00:51 +08:00
Jiangjie.Bai
50850af382 Merge pull request #863 from jumpserver/dev
v2.11.0 rc5
2021-06-17 11:33:42 +08:00
Bai
92097e11e5 fix: 修改账号管理样式 2021-06-17 11:31:09 +08:00
Jiangjie.Bai
02e1299cbd Merge pull request #859 from jumpserver/dev
v2.11.0 rc5 (2)
2021-06-16 19:48:27 +08:00
ibuler
bccaaac1c7 perf: 优化系统用户详情信息 2021-06-16 19:10:40 +08:00
ibuler
3b91b60bc2 perf: 优化工单申请
去掉debug
2021-06-16 18:38:43 +08:00
ibuler
dca0753492 perf(setting): 优化安全设置中的,危险命令告警 2021-06-16 18:03:47 +08:00
Bai
d1920595d8 fix: 列表页添加org_name列表名 2021-06-16 14:29:49 +08:00
Bai
97638bc7a3 fix: 收集用户列表去掉asset搜索项 2021-06-16 14:29:49 +08:00
Bai
f8ab8035e5 fix: 修改云管账号详情页validity显示字段 2021-06-16 14:29:49 +08:00
Jiangjie.Bai
39c1aa1e7e Merge pull request #853 from jumpserver/dev
v2.11.0 rc5
2021-06-16 13:05:08 +08:00
ibuler
6f07c36393 fix(sessions): 修复监控地址错误的问题 2021-06-16 12:26:44 +08:00
Jiangjie.Bai
b700d0fc83 Merge pull request #850 from jumpserver/dev
v2.11.0 rc3
2021-06-15 14:50:23 +08:00
xinwen
2d7fd30da1 fix: 修复站内信 WebSocket 问题 2021-06-15 14:48:12 +08:00
Jiangjie.Bai
99b852d552 Merge pull request #848 from jumpserver/dev
v2.11.0 rc2
2021-06-15 10:51:38 +08:00
Jiangjie.Bai
37234f0e20 Merge pull request #840 from jumpserver/pr@dev@fix_accounts
fix: 修复账号管理-资产账号导出需要进行MFA认证样式
2021-06-15 10:43:14 +08:00
ibuler
da87848f44 fix: 去掉执行任务中的clone按钮 2021-06-11 18:12:03 +08:00
ibuler
2aa363f6ce perf: 优化websocket,实时获取数量
perf: 优化提示i18n

perf: remove yarn

..
2021-06-11 18:11:38 +08:00
Bai
97a98ace03 perf: 优化账号管理-列表2 2021-06-11 13:54:16 +08:00
Bai
f53be25e19 perf: 优化账号管理-列表 2021-06-11 11:48:07 +08:00
ibuler
3c36b50c35 fix: 修复es添加报错问题 2021-06-11 11:25:12 +08:00
Bai
eb75122a76 fix: 修复账号管理-应用账号导出需要进行MFA认证 2021-06-11 11:03:40 +08:00
fit2bot
9f3a1e082b fix: 修复我的资产页面button消失的bug (#844)
* perf: 修改命令存储创建

* fix: 修复我的资产页面button消失的bug

Co-authored-by: ibuler <ibuler@qq.com>
2021-06-11 10:44:52 +08:00
fit2bot
ca8307338f perf: 继续优化名称 (#842)
* perf: 修改命令存储创建

* perf: 继续优化名称

xxx

Co-authored-by: ibuler <ibuler@qq.com>
2021-06-10 19:18:32 +08:00
fit2bot
ee53941fb0 fix(settings): 修复ldap提交时,json报错 (#836)
* perf: 修改命令存储创建

* fix(settings): 修复ldap提交时,json报错

Co-authored-by: ibuler <ibuler@qq.com>
2021-06-10 19:05:08 +08:00
Bai
6d7fec5bf9 fix: 修复账号管理-资产账号导出需要进行MFA认证搜索项 2021-06-10 17:10:31 +08:00
Bai
d31492f43b fix: 修复账号管理-资产账号导出需要进行MFA认证样式 2021-06-10 16:56:56 +08:00
Bai
5056c8cbf9 fix: 修复账号管理-资产账号导出需要进行MFA认证 2021-06-10 16:52:28 +08:00
Jiangjie.Bai
00cf2c34b9 Merge pull request #835 from jumpserver/dev
v2.11.0 rc1
2021-06-10 14:01:52 +08:00
fit2bot
c15f22e05b perf: 优化一下,暂时不支持查看全部站内信 (#833)
* perf: 修改命令存储创建

* perf: 优化一下,暂时不支持查看全部站内信

Co-authored-by: ibuler <ibuler@qq.com>
2021-06-10 12:58:25 +08:00
fit2bot
283dfc97ec perf: 系统监控中添加 xrdp (#834)
* perf: 修改命令存储创建

* perf: 系统监控中添加 xrdp

perf: gua

Co-authored-by: ibuler <ibuler@qq.com>
2021-06-10 12:53:01 +08:00
ibuler
a13c009b37 perf: 优化站内信和xrdp监控
perf: 优化 can has

perf: 修改提示i18n
2021-06-09 19:59:31 +08:00
fit2bot
2494583098 perf: 优化session监控 (#830)
* perf: 修改命令存储创建

* perf: 优化session监控

Co-authored-by: ibuler <ibuler@qq.com>
2021-06-09 15:43:00 +08:00
fit2bot
2bb1fce491 feat: 添加rdp配置 (#828)
* perf: 修改命令存储创建

* feat: 添加rdp配置

* perf: 优化xrdp显示位置

Co-authored-by: ibuler <ibuler@qq.com>
2021-06-08 15:26:40 +08:00
Bai
49e2c547e7 fix: 修改账号管理列表相关问题 2021-06-08 11:38:26 +08:00
老广
0bac33c9fc Merge pull request #822 from jumpserver/pr@dev@feat_subscription
feat: 系统消息订阅页面
2021-06-08 11:23:14 +08:00
ibuler
1b3f5403dd perf: 国际化 2021-06-07 20:05:36 +08:00
xinwen
b4fd09e308 去掉websocket 2021-06-07 19:40:22 +08:00
xinwen
96b002eccb 添加标记已读与websockt 2021-06-07 17:09:10 +08:00
ibuler
15e253804e perf: 优化msg 2021-06-07 15:47:13 +08:00
fit2bot
8427cb4e22 feat(terminal): 危险命令变色 (#825)
* perf: 修改命令存储创建

* feat(terminal): 危险命令变色

* perf: 去掉那个标签

Co-authored-by: ibuler <ibuler@qq.com>
2021-06-07 10:47:57 +08:00
Jiangjie.Bai
c8c6cbc3b1 Merge pull request #826 from jumpserver/feat_account_manager
feat: 添加账号管理模块
2021-06-04 11:21:01 +08:00
xinwen
ef841149de feat: 添加站内信页面 2021-06-04 10:41:20 +08:00
Bai
b51783d98f feat: 账号管理(翻译) 2021-06-03 13:10:18 +08:00
dependabot[bot]
80ff4064e6 chore(deps): bump browserslist from 4.10.0 to 4.16.6 (#820)
* chore: 更新Submodule指向

* fix: 修复Build失败的问题

* fix(clone): 修复会话页面产生的clone的问题

* chore(deps): bump browserslist from 4.10.0 to 4.16.6

Bumps [browserslist](https://github.com/browserslist/browserslist) from 4.10.0 to 4.16.6.
- [Release notes](https://github.com/browserslist/browserslist/releases)
- [Changelog](https://github.com/browserslist/browserslist/blob/main/CHANGELOG.md)
- [Commits](https://github.com/browserslist/browserslist/compare/4.10.0...4.16.6)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: Orange <orangemtony@gmail.com>
Co-authored-by: Jiangjie.Bai <32935519+BaiJiangJie@users.noreply.github.com>
Co-authored-by: 老广 <ibuler@qq.com>
Co-authored-by: Eric_Lee <xplzv@126.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-02 19:26:46 +08:00
Bai
091038de95 feat: 优化收集用户列表 2021-06-02 18:53:29 +08:00
ibuler
cc994b8ecd perf: logo和文案
perf: 去掉title
2021-06-02 18:11:34 +08:00
dependabot[bot]
73d4ec0fa5 chore(deps): bump dns-packet from 1.3.1 to 1.3.4 (#821)
* chore: 更新Submodule指向

* fix: 修复Build失败的问题

* fix(clone): 修复会话页面产生的clone的问题

* chore(deps): bump dns-packet from 1.3.1 to 1.3.4

Bumps [dns-packet](https://github.com/mafintosh/dns-packet) from 1.3.1 to 1.3.4.
- [Release notes](https://github.com/mafintosh/dns-packet/releases)
- [Changelog](https://github.com/mafintosh/dns-packet/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mafintosh/dns-packet/compare/v1.3.1...v1.3.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: Orange <orangemtony@gmail.com>
Co-authored-by: Jiangjie.Bai <32935519+BaiJiangJie@users.noreply.github.com>
Co-authored-by: 老广 <ibuler@qq.com>
Co-authored-by: Eric_Lee <xplzv@126.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-02 17:52:15 +08:00
Bai
8ee39c036f feat: 资产账号列表添加 BackendDisplay 和 Name 字段 2021-06-02 17:01:34 +08:00
xinwen
e8335d9c73 feat: 系统消息订阅页面 2021-06-01 17:48:36 +08:00
Bai
f720eaf05a feat: 账号管理模块(基本完成) 2021-05-28 17:19:35 +08:00
Bai
7f118d7074 feat: 移动 xpack/ChangeAuthPlan > accounts/CahngeAuthPlan 2 2021-05-28 16:53:08 +08:00
Bai
a1ad901b40 feat: 移动 xpack/ChangeAuthPlan > accounts/CahngeAuthPlan 2021-05-28 16:50:40 +08:00
Bai
27b39ad910 feat: 移除 xpack/vault 2021-05-28 16:00:17 +08:00
Bai
f053a37606 feat: 移动 xpack/GatheredUser > accounts/GatheredUser 2021-05-28 15:40:34 +08:00
Bai
dcb94f4aac feat: 优化应用账号(操作按钮: 更新) 2021-05-28 11:35:18 +08:00
Bai
91cbe97a5b feat: 优化应用账号(应用列表选中行、应用用户列表背景色) 2021-05-28 11:23:49 +08:00
Bai
d3053fdf18 feat: 优化资产账号(资产列表选中行及资产用户列表背景色) 2021-05-27 16:45:55 +08:00
Bai
226f17c4fd feat: 优化资产账号(资产树的显示/隐藏) 2021-05-27 14:10:06 +08:00
Bai
34a30571c1 feat: 添加应用账号模块(基本完成,细节待优化) 2021-05-26 18:38:02 +08:00
Bai
0a19829c4a feat: 添加资产账号模块(基本完成,细节待优化) 2021-05-26 17:23:42 +08:00
Bai
09a252f282 feat: 添加账号管理路由 2021-05-25 12:51:28 +08:00
Michael Bai
c7c6f5ac82 fix: 修复导入模版生成table时,没有显示number字段类型所在列的问题 2021-05-23 22:00:30 -05:00
Michael Bai
f3b15727cb fix: 修改用户创建/更新密码策略选项值 2021-05-23 21:58:28 -05:00
Jiangjie.Bai
0eb8e1fab3 Merge pull request #815 from jumpserver/dev
v2.10.0 rc4
2021-05-19 19:29:38 +08:00
ibuler
a80918139b perf: 修改命令存储创建 2021-05-19 05:59:40 -05:00
ibuler
67ffda2fd0 fix: 修复ldap导入不能隐藏dialog问题 2021-05-19 18:34:10 +08:00
ibuler
fa6831b743 perf: 优化钉钉企业微信提交 2021-05-19 18:33:11 +08:00
Bai
8bd77fa6c9 fix: 修复创建云管账号成功重定向页面问题 2021-05-19 18:29:50 +08:00
ibuler
6a45fccfcd fix: 修复系统用户用户名禁用的问题 2021-05-19 18:13:43 +08:00
ibuler
c39733cf66 fix: 修复多tab引起的设置和弹窗问题
fix: 优化

perf: 移除不用的代码

perf: 修复不能刷新的bug

perf: 去掉conole
2021-05-18 22:22:35 -05:00
Jiangjie.Bai
ea4a7f53e3 Merge pull request #808 from jumpserver/dev
v2.10.0 rc3
2021-05-18 19:18:19 +08:00
Bai
6639614caf fix: 修复云管中心创建账户后跳转到任务列表页面的问题 2021-05-18 18:31:59 +08:00
Bai
f17ec57b3a feat: 收集用户任务添加详情页面和执行历史页面 2021-05-18 17:46:53 +08:00
Bai
b04cf80201 fix: 修复云管账号详情页面点击更新失败的问题 2021-05-18 17:45:51 +08:00
ibuler
6dc71fe612 perf(assets): 修复系统用户详情中更新按钮地址不对的问题 2021-05-18 14:15:13 +08:00
ibuler
9cbec5e1ab perf: 优化系统用户用户名显示禁用
perf: 优化系统用户用户名隐藏与禁用
2021-05-18 14:14:22 +08:00
ibuler
703eadf292 perf: 优化工单,处理人去掉关闭的按钮,只能同意或拒绝 2021-05-18 14:12:13 +08:00
Bai
d2aa4e99da fix: 修复改密计划页面更新资产/节点报错的问题 2021-05-18 14:11:02 +08:00
Bai
4509970f7d fix: 修复组织详情关联用户报错的问题 2021-05-17 19:12:39 +08:00
ibuler
b03af2c995 perf(users): 修复全局组织用户组问题的 2021-05-17 18:35:14 +08:00
ibuler
328e068aca fix(xpack): 修复收集任务表头设置的bug 2021-05-17 18:34:04 +08:00
Bai
65c6922621 fix: 修复收集用户任务创建不填写interval报错的问题 2021-05-17 18:30:54 +08:00
xinwen
ebb8af42f2 fix: 修复绑定企业微信&钉钉的一些问题 2021-05-17 17:21:14 +08:00
ibuler
0b821ffc04 perf: 优化profile中的翻译 2021-05-17 16:57:53 +08:00
Bai
5b6e2970bf fix: 去掉RemoteApp的导入/导出功能 2021-05-17 16:36:05 +08:00
Bai
d01e991885 fix: 申请资产工单详情添加资产授权详情链接 2021-05-17 15:52:37 +08:00
Bai
552b26e163 fix: 申请资产详情添加 action 字段 2021-05-17 15:52:37 +08:00
Bai
35814d3d5c fix: 添加系统监控组件名称(lion) 2021-05-17 15:00:17 +08:00
xinwen
31e46a3ede fix: oauth2 i18n 2021-05-17 01:51:56 -05:00
ibuler
e7815f528c fix: 修复系统用户详情中,没有更多操作的bug 2021-05-17 01:51:25 -05:00
ibuler
4d747686cd fix: 修复资产详情页面,toLowerCase 报错 2021-05-17 01:50:47 -05:00
Jiangjie.Bai
12697ab279 Merge pull request #788 from jumpserver/dev
v2.10.0 rc1
2021-05-13 19:47:27 +08:00
老广
f4f4b7ccc1 增加 rdp vnc 监控 (#787)
Co-authored-by: Eric <xplzv@126.com>
2021-05-13 19:44:47 +08:00
ibuler
41bbd4a70b perf(assets): 优化资产创建更新时的label 2021-05-13 19:24:35 +08:00
Jiangjie.Bai
a66403dcf0 Merge pull request #786 from jumpserver/dev
v2.10 rc1
2021-05-13 19:21:04 +08:00
xinwen
31b75aa139 feat: 添加企业微信,钉钉扫码登录 2021-05-13 19:12:51 +08:00
xinwen
411aae6829 feat: ES 命令存储支持忽略证书验证 2021-05-07 03:48:15 -05:00
ibuler
0ad025f63b fix: 修复系统用户vnc用户名可以不必填 2021-05-06 22:31:40 -05:00
jym503558564
312de6c6eb perf(i18n): 修改用户页面Router 翻译 2021-05-06 22:30:47 -05:00
jym503558564
5b05bd12c6 fix(i18n): 修改更新资源密码时,更新密码字段没翻译的问题 2021-05-06 22:30:10 -05:00
Bai
a87ea94e78 fix: 删除资产授权创建页面多余的输入框 2021-04-30 14:57:25 +08:00
ibuler
9994020f97 perf: 优化自动推送那个按钮
perf: 修改名称
2021-04-29 04:31:31 -05:00
fit2bot
fc0709f1ce fix: 修复点击修改密码密码框丢失的问题 (#771)
Co-authored-by: ibuler <ibuler@qq.com>
2021-04-29 17:28:23 +08:00
ibuler
9235d68825 refactor: 改变组件位置
stash

refacter: 整理了forms组件位置

perf: 修改导入
2021-04-28 23:59:49 -05:00
ibuler
4a17efb015 perf: 优化登录前更新密码 2021-04-28 23:58:43 -05:00
fit2cloud-jiangweidong
1cc69e4203 feat: 管理员可以设置用户是否下次登录需修改密码 (#758)
* feat: 管理员可以设置用户是否下次登录需修改密码

* feat: 管理员可以设置用户下次是否需要更改密码,本次修改:字段命名规范化

* fix: 用户是否需要更改密码,变量名称规范化

* fix: 管理员可设置用户下次登录是否需要改密,字段名称更改
2021-04-28 19:24:30 +08:00
jym503558564
9de8d622e4 fix(i18n): 修改翻译 2021-04-28 05:20:40 -05:00
Bai
844dfb5ac1 feat: 修复用户页面获取命令复核功能详情失败的问题 2021-04-28 16:26:37 +08:00
fit2cloud-jiangweidong
9d3fd73367 feat: 用户更改密码不可使用前n次历史密码,管理员可设置历史密码重复次数 2021-04-28 02:46:13 -05:00
ibuler
c7731a1d2f perf: 优化公钥认证,根据开关来限制公钥认证 2021-04-27 04:50:43 -05:00
Bai
bf161d688a feat: 添加命令复核工单逻辑3 2021-04-27 16:37:54 +08:00
Bai
ab2d5b3fed feat: 添加命令复核工单逻辑2 2021-04-27 16:37:54 +08:00
Bai
6a40ab1a4c feat: 添加命令复核工单逻辑 2021-04-27 16:37:54 +08:00
jym503558564
4d11e638e8 fix:修改Xpack-router翻译 2021-04-26 05:37:13 -05:00
fit2bot
97c4a397e2 fix:修改翻译 (#762)
Co-authored-by: jym503558564 <503558564@qq.com>
2021-04-25 18:07:31 +08:00
Orange
9239d69c14 fix: 修改DB协议系统用户手动登录时用户名必填 2021-04-25 01:16:26 -05:00
ibuler
3ea68fe13f perf: 优化换行 2021-04-25 01:09:39 -05:00
Orange
04ce056076 fix: 社区版本隐藏全局组织名称设置 2021-04-25 01:09:39 -05:00
Orange
67c1c8db58 fix: 临时移除 Database 协议的用户名与密码相同 选项 2021-04-19 04:01:32 -05:00
322 changed files with 10179 additions and 3998 deletions

View File

@@ -1,8 +1,6 @@
# 全局环境变量 请勿随意改动
ENV = 'development'
# base api
VUE_APP_BASE_API = ''
VUE_APP_PUBLIC_PATH = '/ui/'
@@ -23,4 +21,5 @@ VUE_APP_LOGOUT_PATH = '/core/auth/logout/'
# Dev server for core proxy
VUE_APP_CORE_HOST = 'http://localhost:8080'
VUE_APP_CORE_WS = 'ws://localhost:8080'
VUE_APP_ENV = 'development'

View File

@@ -12,8 +12,8 @@ RUN npm config set sass_binary_site=${SASS_BINARY_SITE}
RUN npm config set registry ${NPM_REGISTRY}
RUN yarn config set registry ${NPM_REGISTRY}
COPY package.json yarn.lock /data/
COPY utils /data/utils/
RUN ls && cd utils && bash -xieu build.sh dep
RUN yarn install
RUN npm rebuild node-sass
ADD . /data
RUN cd utils && bash -xieu build.sh build

View File

@@ -15,7 +15,9 @@
"test:ci": "npm run lint && npm run test:unit",
"svgo": "svgo -f src/icons/svg --config=src/icas/svgo.yml",
"vue-i18n-extract": "vue-i18n-extract",
"vue-i18n-report": "vue-i18n-extract report -v './src/**/*.?(js|vue)' -l './src/i18n/langs/**/*.json'"
"vue-i18n-report": "vue-i18n-extract report -v './src/**/*.?(js|vue)' -l './src/i18n/langs/**/*.json'",
"vue-i18n-report-json": "vue-i18n-extract report -v './src/**/*.?(js|vue)' -l './src/i18n/langs/**/*.json' -o /tmp/abc.json",
"vue-i18n-report-add-miss": "vue-i18n-extract report -v './src/**/*.?(js|vue)' -l './src/i18n/langs/**/*.json' -a"
},
"dependencies": {
"@babel/plugin-proposal-optional-chaining": "^7.13.12",
@@ -29,6 +31,7 @@
"install": "^0.13.0",
"jquery": "^3.5.0",
"js-cookie": "2.2.0",
"krry-transfer": "^1.7.3",
"less": "^3.10.3",
"less-loader": "^5.0.0",
"lodash": "^4.17.15",
@@ -43,6 +46,7 @@
"lodash.set": "^4.3.2",
"lodash.topairs": "^4.3.0",
"lodash.values": "^4.3.0",
"moment": "^2.29.1",
"moment-parseformat": "^3.0.0",
"normalize.css": "7.0.0",
"npm": "^7.8.0",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -8,7 +8,6 @@
<meta http-equiv="Cache-control" content="no-cache">
<meta http-equiv="Cache" content="no-cache">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= webpackConfig.name %></title>
</head>
<body>

View File

@@ -57,6 +57,26 @@ export function TestReplayStorage(id) {
})
}
function SetToDefaultStorage(url) {
return request({
url: url,
method: 'patch',
data: { 'is_default': true }
})
}
export function SetToDefaultCommandStorage(id) {
return SetToDefaultStorage(
`/api/v1/terminal/command-storages/${id}/`,
)
}
export function SetToDefaultReplayStorage(id) {
return SetToDefaultStorage(
`/api/v1/terminal/replay-storages/${id}/`,
)
}
export function getReplayStorage(id) {
return request({
url: `/api/v1/terminal/replay-storages/${id}/`,

View File

@@ -51,7 +51,7 @@ export function refreshLdapUserCache() {
})
}
export function StartLdapUserCache() {
export function startLdapUserCache() {
return request({
disableFlashErrorMsg: true,
url: '/api/v1/settings/ldap/users/?cache_police=1',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,83 @@
<template>
<div>
<MFAVerifyDialog
@MFAVerifyDone="getAuthInfo"
@MFAVerifyCancel="exit"
/>
<Dialog
:title="dialogTitle"
:show-confirm="false"
:show-cancel="false"
:destroy-on-close="true"
:width="'50'"
:visible.sync="showAuthInfo"
v-bind="$attrs"
v-on="$listeners"
>
<div>
<el-form label-position="right" label-width="80px" :model="authInfo">
<el-form-item :label="this.$t('assets.Hostname')">
<el-input v-model="account.hostname" readonly />
</el-form-item>
<el-form-item :label="this.$t('assets.Username')">
<el-input v-model="account['username']" readonly />
</el-form-item>
<el-form-item :label="this.$t('assets.Password')">
<el-input v-model="authInfo.password" type="password" show-password />
</el-form-item>
<el-form-item :label="this.$t('assets.SSHKey')">
<el-input v-model="authInfo['private_key']" type="password" show-password />
</el-form-item>
</el-form>
</div>
</Dialog>
</div>
</template>
<script>
import Dialog from '@/components/Dialog'
import MFAVerifyDialog from '@/components/MFAVerifyDialog'
export default {
name: 'ShowSecretInfo',
components: {
Dialog,
MFAVerifyDialog
},
props: {
account: {
type: Object,
default: () => ({})
},
visible: {
type: Boolean,
default: false
}
},
data() {
return {
dialogTitle: this.$t('common.ViewSecret'),
authInfo: {},
showAuthInfo: false
}
},
mounted() {
this.getAuthInfo()
},
methods: {
getAuthInfo() {
const url = `/api/v1/assets/account-secrets/${this.account.id}/`
this.$axios.get(url, { disableFlashErrorMsg: true }).then(resp => {
this.authInfo = resp
this.showAuthInfo = true
})
},
exit() {
this.$emit('update:visible', false)
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,92 @@
<template>
<Dialog
width="50"
:title="this.$t('assets.UpdateAssetUserToken')"
:destroy-on-close="true"
v-bind="$attrs"
@confirm="handleConfirm()"
@cancel="handleCancel()"
v-on="$listeners"
>
<el-form label-position="right" label-width="80px">
<el-form-item :label="this.$t('assets.Hostname')">
<el-input v-model="account.hostname" readonly />
</el-form-item>
<el-form-item :label="this.$t('assets.Username')">
<el-input v-model="account['username']" readonly />
</el-form-item>
<el-form-item :label="this.$t('assets.Password')">
<el-input v-model="authInfo.password" type="password" />
</el-form-item>
<el-form-item :label="this.$t('assets.SSHKey')">
<input type="file" @change="onPrivateKeyLoaded">
</el-form-item>
</el-form>
</Dialog>
</template>
<script>
import Dialog from '@/components/Dialog'
export default {
name: 'UpdateSecretInfo',
components: {
Dialog
},
props: {
account: {
type: Object,
default: () => ({})
}
},
data() {
return {
authInfo: {
password: '',
private_key: ''
}
}
},
methods: {
handleConfirm() {
const data = {}
if (this.authInfo.password !== '') {
data.password = this.authInfo.password
}
if (this.authInfo.private_key !== '') {
data.private_key = this.authInfo.private_key
}
this.$axios.patch(
`/api/v1/assets/accounts/${this.account.id}/`,
data
).then(res => {
this.authInfo = { password: '', private_key: '' }
this.$message.success(this.$tc('common.updateSuccessMsg'))
this.$emit('updateAuthDone', res)
this.$emit('update:visible', false)
}).catch(err => {
const errMsg = Object.values(err.response.data).join(', ')
this.$message.error(this.$tc('common.updateErrorMsg') + ' ' + errMsg)
this.$emit('update:visible', true)
})
},
handleCancel() {
this.$emit('update:visible', false)
},
onPrivateKeyLoaded(e) {
const vm = this
// TODO 校验文件类型
const reader = new FileReader()
reader.onload = function() {
vm.authInfo.private_key = this.result
}
reader.readAsText(
e.target.files[0]
)
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,31 @@
import { ChoicesFormatter } from '@/components/TableFormatters'
import { toSafeLocalDateStr } from '@/utils/common'
import i18n from '@/i18n/i18n'
export const connectivityMeta = {
label: i18n.t('assets.Reachable'),
formatter: ChoicesFormatter,
formatterArgs: {
iconChoices: {
ok: 'fa-check text-primary',
failed: 'fa-times text-danger',
unknown: 'fa-circle text-warning'
},
hasTips: true,
getTips: ({ row, cellValue }) => {
const mapper = {
'ok': i18n.t('assets.Reachable'),
'failed': i18n.t('assets.Unreachable'),
'unknown': i18n.t('assets.Unknown')
}
let tips = mapper[cellValue]
if (row['date_verified']) {
const datetime = toSafeLocalDateStr(row['date_verified'])
tips += '<br> ' + datetime
}
return tips
}
},
width: '90px',
align: 'center'
}

View File

@@ -0,0 +1,184 @@
<template>
<div>
<ListTable ref="ListTable" :table-config="tableConfig" :header-actions="headerActions" />
<ShowSecretInfo v-if="showViewSecretDialog" :visible.sync="showViewSecretDialog" :account="account" />
<UpdateSecretInfo :visible.sync="showUpdateSecretDialog" :account="account" @updateAuthDone="onUpdateAuthDone" />
</div>
</template>
<script>
import ListTable from '@/components/ListTable/index'
import { ActionsFormatter, DetailFormatter, DisplayFormatter } from '@/components/TableFormatters'
import ShowSecretInfo from './ShowSecretInfo'
import UpdateSecretInfo from './UpdateSecretInfo'
import { connectivityMeta } from './const'
import { openTaskPage } from '@/utils/jms'
export default {
name: 'AccountListTable',
components: {
ListTable,
UpdateSecretInfo,
ShowSecretInfo
},
props: {
url: {
type: String,
required: true
},
exportUrl: {
type: String,
default() {
return this.url.replace('/assets/accounts/', '/assets/account-secrets/')
}
},
hasLeftActions: {
type: Boolean,
default: false
},
otherActions: {
type: Array,
default: null
},
hasClone: {
type: Boolean,
default: false
}
},
data() {
return {
showViewSecretDialog: false,
showUpdateSecretDialog: false,
account: {},
tableConfig: {
url: this.url,
columns: [
'hostname', 'ip', 'username', 'version', 'connectivity',
'systemuser', 'date_created', 'date_updated', 'actions'
],
columnsShow: {
min: ['username', 'actions'],
default: ['hostname', 'ip', 'username', 'version', 'actions']
},
columnsMeta: {
hostname: {
prop: 'hostname',
label: this.$t('assets.Hostname'),
showOverflowTooltip: true,
formatter: DetailFormatter,
formatterArgs: {
getRoute({ row }) {
return {
name: 'AssetDetail',
params: { id: row.asset }
}
}
}
},
ip: {
width: '120px'
},
username: {
showOverflowTooltip: true
},
systemuser: {
formatter: DisplayFormatter
},
version: {
width: '70px'
},
connectivity: connectivityMeta,
actions: {
formatter: ActionsFormatter,
formatterArgs: {
hasUpdate: false, // can set function(row, value)
hasDelete: false, // can set function(row, value)
hasClone: this.hasClone,
moreActionsTitle: this.$t('common.More'),
extraActions: [
{
name: 'View',
title: this.$t('common.View'),
type: 'primary',
callback: function({ row }) {
this.account = row
this.showViewSecretDialog = true
}.bind(this)
},
{
name: 'Delete',
title: this.$t('common.Delete'),
type: 'primary',
callback: ({ row }) => {
this.$axios.delete(`/api/v1/assets/accounts/${row.id}/`).then(() => {
this.$message.success(this.$tc('common.deleteSuccessMsg'))
this.$refs.ListTable.reloadTable()
})
}
},
{
name: 'Test',
title: this.$t('common.Test'),
callback: ({ row }) => {
this.$axios.post(
`/api/v1/assets/accounts/${row.id}/verify/`,
{ action: 'test' }
).then(res => {
openTaskPage(res['task'])
})
}
},
{
name: 'Update',
title: this.$t('common.Update'),
can: !this.$store.getters.currentOrgIsRoot,
callback: function({ row }) {
this.account = row
this.showUpdateSecretDialog = true
}.bind(this)
}
]
}
}
}
},
headerActions: {
hasLeftActions: this.hasLeftActions,
hasMoreActions: false,
hasImport: false,
hasExport: true,
exportOptions: {
url: this.exportUrl,
mfaVerifyRequired: true
},
searchConfig: {
exclude: ['systemuser', 'asset']
},
hasSearch: true
}
}
},
watch: {
url(iNew) {
this.$set(this.tableConfig, 'url', iNew)
}
},
mounted() {
if (this.otherActions) {
const actionColumn = this.tableConfig.columns[this.tableConfig.columns.length - 1]
for (const item of this.otherActions) {
actionColumn.formatterArgs.extraActions.push(item)
}
}
},
methods: {
onUpdateAuthDone(account) {
Object.assign(this.account, account)
}
}
}
</script>
<style lang='less' scoped>
</style>

View File

@@ -0,0 +1,80 @@
<template>
<div>
<MFAVerifyDialog
@MFAVerifyDone="getAuthInfo"
@MFAVerifyCancel="exit"
/>
<Dialog
:title="dialogTitle"
:show-confirm="false"
:show-cancel="false"
:destroy-on-close="true"
:width="'50'"
:visible.sync="showAuthInfo"
v-bind="$attrs"
v-on="$listeners"
>
<div>
<el-form label-position="right" label-width="80px" :model="authInfo">
<el-form-item :label="this.$t('applications.appName')">
<el-input v-model="account['app_display']" readonly />
</el-form-item>
<el-form-item :label="this.$t('assets.Username')">
<el-input v-model="account['username']" readonly />
</el-form-item>
<el-form-item :label="this.$t('assets.Password')">
<el-input v-model="authInfo.password" type="password" show-password />
</el-form-item>
</el-form>
</div>
</Dialog>
</div>
</template>
<script>
import Dialog from '@/components/Dialog'
import MFAVerifyDialog from '@/components/MFAVerifyDialog'
export default {
name: 'ShowSecretInfo',
components: {
Dialog,
MFAVerifyDialog
},
props: {
account: {
type: Object,
default: () => ({})
},
visible: {
type: Boolean,
default: false
}
},
data() {
return {
dialogTitle: this.$t('common.ViewSecret'),
authInfo: {},
showAuthInfo: false
}
},
mounted() {
this.getAuthInfo()
},
methods: {
getAuthInfo() {
const url = `/api/v1/applications/account-secrets/${this.account.id}/`
this.$axios.get(url, { disableFlashErrorMsg: true }).then(resp => {
this.authInfo = resp
this.showAuthInfo = true
})
},
exit() {
this.$emit('update:visible', false)
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,31 @@
import { ChoicesFormatter } from '@/components/TableFormatters'
import { toSafeLocalDateStr } from '@/utils/common'
import i18n from '@/i18n/i18n'
export const connectivityMeta = {
label: i18n.t('assets.Reachable'),
formatter: ChoicesFormatter,
formatterArgs: {
iconChoices: {
ok: 'fa-check text-primary',
failed: 'fa-times text-danger',
unknown: 'fa-circle text-warning'
},
hasTips: true,
getTips: ({ row, cellValue }) => {
const mapper = {
'ok': i18n.t('assets.Reachable'),
'failed': i18n.t('assets.Unreachable'),
'unknown': i18n.t('assets.Unknown')
}
let tips = mapper[cellValue]
if (row['date_verified']) {
const datetime = toSafeLocalDateStr(row['date_verified'])
tips += '<br> ' + datetime
}
return tips
}
},
width: '90px',
align: 'center'
}

View File

@@ -0,0 +1,167 @@
<template>
<div>
<ListTable ref="ListTable" :table-config="tableConfig" :header-actions="headerActions" />
<ShowSecretInfo v-if="showViewSecretDialog" :visible.sync="showViewSecretDialog" :account="account" />
</div>
</template>
<script>
import ListTable from '@/components/ListTable/index'
import { ActionsFormatter, DetailFormatter } from '@/components/TableFormatters'
import ShowSecretInfo from './ShowSecretInfo'
export default {
name: 'Detail',
components: {
ListTable,
ShowSecretInfo
},
props: {
url: {
type: String,
required: true
},
exportUrl: {
type: String,
default() {
return this.url.replace('/applications/accounts/', '/applications/account-secrets/')
}
},
hasLeftActions: {
type: Boolean,
default: false
},
otherActions: {
type: Array,
default: null
},
hasClone: {
type: Boolean,
default: false
}
},
data() {
return {
showViewSecretDialog: false,
showUpdateSecretDialog: false,
account: {},
tableConfig: {
url: this.url,
columns: [
'app_display', 'username', 'category_display',
'type_display', 'systemuser', 'actions'
],
columnsMeta: {
app_display: {
showOverflowTooltip: true,
formatter: DetailFormatter,
formatterArgs: {
getRoute({ row }) {
switch (row['category']) {
case 'remote_app':
return {
name: 'RemoteAppDetail',
params: { id: row.app }
}
case 'db':
return {
name: 'DatabaseAppDetail',
params: { id: row.app }
}
default:
return {
name: 'KubernetesAppDetail',
params: { id: row.app }
}
}
}
}
},
username: {
showOverflowTooltip: true
},
systemuser: {
showOverflowTooltip: true,
formatter: DetailFormatter,
formatterArgs: {
getTitle({ row }) {
return row.systemuser_display
},
getRoute({ row }) {
return {
name: 'SystemUserDetail',
params: { id: row.systemuser }
}
}
}
},
actions: {
formatter: ActionsFormatter,
formatterArgs: {
hasUpdate: false, // can set function(row, value)
hasDelete: false, // can set function(row, value)
hasClone: this.hasClone,
moreActionsTitle: this.$t('common.More'),
extraActions: [
{
name: 'View',
title: this.$t('common.View'),
type: 'primary',
callback: function({ row }) {
this.account = row
this.showViewSecretDialog = true
}.bind(this)
},
{
name: 'Update',
title: this.$t('common.Update'),
can: !this.$store.getters.currentOrgIsRoot,
callback: function({ row }) {
this.$message.success(this.$tc('applications.updateAccountMsg'))
}.bind(this)
}
]
}
}
}
},
headerActions: {
hasLeftActions: this.hasLeftActions,
hasMoreActions: false,
hasImport: false,
hasExport: true,
exportOptions: {
url: this.exportUrl,
mfaVerifyRequired: true
},
searchConfig: {
exclude: ['systemuser', 'asset']
},
hasSearch: true
}
}
},
watch: {
url(iNew) {
this.$set(this.tableConfig, 'url', iNew)
}
},
mounted() {
if (this.otherActions) {
const actionColumn = this.tableConfig.columns[this.tableConfig.columns.length - 1]
for (const item of this.otherActions) {
actionColumn.formatterArgs.extraActions.push(item)
}
}
},
methods: {
onUpdateAuthDone(account) {
Object.assign(this.account, account)
}
}
}
</script>
<style lang='less' scoped>
</style>

View File

@@ -23,8 +23,8 @@
<script>
import TreeTable from '@/components/TreeTable'
import { DetailFormatter } from '@/components/ListTable/formatters'
import Select2 from '@/components/Select2'
import { DetailFormatter } from '@/components/TableFormatters'
import Select2 from '@/components/FormFields/Select2'
import Dialog from '@/components/Dialog'
export default {
@@ -76,7 +76,7 @@ export default {
select2Config: select2Config,
dialogSelect2Config: select2Config,
tableConfig: {
url: '/api/v1/assets/assets/',
url: '/api/v1/assets/assets/?fields_size=mini',
hasTree: true,
canSelect: this.canSelect,
columns: [
@@ -94,6 +94,18 @@ export default {
prop: 'ip',
label: this.$t('assets.ipDomain'),
sortable: 'custom'
},
{
prop: 'platform',
label: this.$t('assets.Platform'),
sortable: true
},
{
prop: 'protocols',
formatter: function(row) {
return <span> {row.protocols.toString()} </span>
},
label: this.$t('assets.Protocols')
}
],
listeners: {

View File

@@ -1,371 +0,0 @@
<template>
<div>
<div>
<ListTable ref="ListTable" :table-config="tableConfig" :header-actions="headerActions" />
<Dialog v-if="showMFADialog" width="50" :title="this.$t('common.MFAConfirm')" :visible.sync="showMFADialog" :show-confirm="false" :show-cancel="false" :destroy-on-close="true">
<div v-if="MFAConfirmed">
<el-form label-position="right" label-width="80px" :model="MFAInfo">
<el-form-item :label="this.$t('assets.Hostname')">
<el-input v-model="MFAInfo.hostname" disabled />
</el-form-item>
<el-form-item :label="this.$t('assets.Username')">
<el-input v-model="MFAInfo.username" disabled />
</el-form-item>
<el-form-item :label="this.$t('assets.Password')">
<el-input v-model="MFAInfo.password" type="password" show-password />
</el-form-item>
</el-form>
</div>
<el-row v-else :gutter="20">
<el-col :span="4">
<div style="line-height: 34px;text-align: center">MFA</div>
</el-col>
<el-col :span="14">
<el-input v-model="MFAInput" />
<span class="help-tips help-block">{{ $t('common.MFARequireForSecurity') }}</span>
</el-col>
<el-col :span="4">
<el-button size="mini" type="primary" style="line-height:20px " @click="MFAConfirm">{{ this.$t('common.Confirm') }}</el-button>
</el-col>
</el-row>
</Dialog>
<Dialog width="50" :title="this.$t('assets.UpdateAssetUserToken')" :visible.sync="showDialog" @confirm="handleConfirm()" @cancel="handleCancel()">
<el-form label-position="right" label-width="80px" :model="dialogInfo">
<el-form-item :label="this.$t('assets.Hostname')">
<el-input v-model="dialogInfo.hostname" disabled />
</el-form-item>
<el-form-item :label="this.$t('assets.Username')">
<el-input v-model="dialogInfo.username" disabled />
</el-form-item>
<el-form-item :label="this.$t('assets.Password')">
<el-input v-model="dialogInfo.password" type="password" />
</el-form-item>
<el-form-item :label="this.$t('assets.sshkey')">
<input type="file" @change="Onchange">
</el-form-item>
</el-form>
</Dialog>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import ListTable from '@/components/ListTable/index'
import Dialog from '@/components/Dialog'
import { ActionsFormatter, DateFormatter } from '@/components/ListTable/formatters'
export default {
name: 'Detail',
components: {
ListTable,
Dialog
},
props: {
url: {
type: String,
required: true
},
hasLeftActions: {
type: Boolean,
default: false
},
otherActions: {
type: Array,
default: null
},
handleExport: {
type: Function,
default: null
},
handleImport: {
type: Function,
default: null
},
hasImport: {
type: Boolean,
default: true
},
hasExport: {
type: Boolean,
default: true
},
hasClone: {
type: Boolean,
default: true
}
},
data() {
return {
MFAConfirmed: false,
MFAInput: '',
MFAInfo: {
asset: '',
username: '',
hostname: '',
password: ''
},
showDialog: false,
showMFADialog: false,
dialogInfo: {
asset: '',
username: '',
hostname: '',
password: '',
private_key: ''
},
tableConfig: {
url: this.url,
columns: [
{
prop: 'hostname',
label: this.$t('assets.Hostname'),
showOverflowTooltip: true
},
{
prop: 'ip',
label: this.$t('assets.ip'),
width: '120px'
},
{
prop: 'username',
label: this.$t('assets.Username'),
showOverflowTooltip: true
},
{
prop: 'version',
label: this.$t('assets.Version'),
width: '70px'
},
{
prop: 'date_created',
label: this.$t('assets.date_joined'),
formatter: DateFormatter
},
{
prop: 'id',
label: this.$t('common.Action'),
align: 'center',
width: 150,
formatter: ActionsFormatter,
formatterArgs: {
hasUpdate: false, // can set function(row, value)
hasDelete: false, // can set function(row, value)
hasClone: this.hasClone,
moreActionsTitle: this.$t('common.More'),
extraActions: [
{
name: 'View',
title: this.$t('common.View'),
type: 'primary',
callback: function(val) {
this.MFAInfo.asset = val.row.id
if (!this.needMFAVerify) {
this.showMFADialog = true
this.MFAConfirmed = true
this.$axios.get(`/api/v1/assets/asset-user-auth-infos/${this.MFAInfo.asset}/`).then(res => {
this.MFAConfirmed = true
this.MFAInfo.hostname = res.hostname
this.MFAInfo.password = res.password
this.MFAInfo.username = res.username
})
} else {
this.showMFADialog = true
}
}.bind(this)
},
{
name: 'Delete',
title: this.$t('common.Delete'),
type: 'primary',
callback: (val) => {
this.$axios.delete(`/api/v1/assets/asset-users/${val.row.id}/`).then(() => {
this.$message.success(this.$t('common.deleteSuccessMsg'))
this.$refs.ListTable.reloadTable()
})
}
},
{
name: 'Test',
title: this.$t('common.Test'),
callback: (val) => {
this.$axios.post(
`/api/v1/assets/asset-users/tasks/?id=${val.row.id}`,
{ action: 'test' }
).then(res => {
window.open(`/#/ops/celery/task/${res.task}/log/`, '', 'width=900,height=600')
})
}
},
{
name: 'Update',
title: this.$t('common.Update'),
can: !this.$store.getters.currentOrgIsRoot,
callback: function(val) {
this.showDialog = true
this.dialogInfo.asset = val.row.asset
this.dialogInfo.hostname = val.row.hostname
this.dialogInfo.username = val.row.username
}.bind(this)
}
]
}
}
],
extraQuery: {
latest: 1
}
},
headerActions: {
hasLeftActions: this.hasLeftActions,
hasMoreActions: false,
hasImport: this.hasImport,
hasExport: this.hasExport,
hasSearch: true,
searchConfig: {
options: [
{
label: this.$t('assets.OnlyLatestVersion'),
value: 'latest',
children: [
{
label: this.$t('common.Yes'),
value: 1
},
{
label: this.$t('common.No'),
value: 0
}
]
}
]
}
}
}
},
computed: {
...mapGetters([
'MFA_TTl',
'MFAVerifyAt',
'publicSettings'
]),
needMFAVerify() {
if (!this.publicSettings.SECURITY_VIEW_AUTH_NEED_MFA) {
return false
}
const ttl = this.publicSettings.SECURITY_MFA_VERIFY_TTL
const now = new Date()
return !(this.MFAVerifyAt && (now - this.MFAVerifyAt) < ttl * 1000)
}
},
watch: {
url(iNew) {
this.$set(this.tableConfig, 'url', iNew)
}
},
mounted() {
if (this.otherActions) {
const actionColumn = this.tableConfig.columns[this.tableConfig.columns.length - 1]
for (const item of this.otherActions) {
actionColumn.formatterArgs.extraActions.push(item)
}
}
},
created() {
if (this.handleExport) {
this.headerActions.handleExport = this.handleExport
}
if (this.handleImport) {
this.headerActions.handleImport = this.handleImport
}
},
methods: {
MFAConfirm() {
if (this.MFAInput.length !== 6) {
return this.$message.error(this.$t('common.MFAErrorMsg'))
}
this.$axios.post(
`/api/v1/authentication/otp/verify/`, {
code: this.MFAInput
}
).then(
res => {
this.$store.dispatch('users/setMFAVerify')
this.$axios.get(`/api/v1/assets/asset-user-auth-infos/${this.MFAInfo.asset}/`).then(res => {
this.MFAConfirmed = true
this.MFAInfo.hostname = res.hostname
this.MFAInfo.password = res.password
this.MFAInfo.username = res.username
})
}
)
},
handleMFAConfirm() {
this.MFAInfo = {
asset: '',
username: '',
hostname: '',
password: ''
}
this.MFAInput = ''
this.showMFADialog = false
this.MFAConfirmed = false
},
handleCancel() {
this.dialogInfo = {
asset: '',
username: '',
hostname: '',
password: '',
private_key: ''
}
this.showDialog = false
this.$refs.ListTable.reloadTable()
},
Onchange(e) {
const vm = this
// TODO 校验文件类型
const reader = new FileReader()
reader.onload = function() {
vm.dialogInfo.private_key = this.result
}
reader.readAsText(
e.target.files[0]
)
},
handleConfirm() {
const data = {
asset: this.dialogInfo.asset,
username: this.dialogInfo.username
}
if (this.dialogInfo.password !== '') {
data.password = this.dialogInfo.password
}
if (this.dialogInfo.private_key !== '') {
data.private_key = this.dialogInfo.private_key
}
this.$axios.post(
`/api/v1/assets/asset-users/`,
data
).then(res => {
this.$message.success(this.$t('common.updateSuccessMsg'))
}).catch(err => {
this.$message.error(this.$t('common.updateErrorMsg' + ' ' + err))
})
this.dialogInfo = {
asset: '',
username: '',
hostname: '',
password: '',
private_key: ''
}
this.showDialog = false
this.$refs.ListTable.reloadTable()
}
}
}
</script>
<style lang='less' scoped>
</style>

View File

@@ -9,7 +9,7 @@
</template>
<script>
import { DataForm } from '@/components'
import DataForm from '@/components/DataForm'
export default {
name: 'NestedField',
@@ -35,7 +35,7 @@ export default {
kwargs: {
hasReset: false,
hasSaveContinue: false,
defaultButton: false
hasButtons: false
}
}
},

View File

@@ -1,6 +1,13 @@
<template>
<DataForm ref="dataForm" v-loading="loading" :fields="totalFields" :form="iForm" v-bind="$attrs" v-on="$listeners">
<FormGroupHeader v-for="(group, i) in groups" :slot="'id:'+group.name" :key="'group-'+group.name" :title="group.title" :line="i !== 0" />
<FormGroupHeader
v-for="(group, i) in groups"
:slot="'id:'+group.name"
:key="'group-'+group.name"
:group="group"
:index="i"
:line="i !== 0"
/>
</DataForm>
</template>
@@ -44,7 +51,8 @@ export default {
totalFields: [],
loading: true,
groups: [],
iForm: this.form
iForm: this.form,
errors: {}
}
},
mounted() {

View File

@@ -1,5 +1,5 @@
import Vue from 'vue'
import Select2 from '@/components/Select2'
import Select2 from '@/components/FormFields/Select2'
import NestedField from '@/components/AutoDataForm/components/NestedField'
import rules from '@/components/DataForm/rules'
import { assignIfNot } from '@/utils/common'
@@ -33,11 +33,11 @@ export class FormFieldGenerator {
break
case 'string':
type = 'input'
if (!fieldRemoteMeta.max_length) {
if (!fieldRemoteMeta['max_length']) {
field.el.type = 'textarea'
field.el.rows = 3
}
if (fieldRemoteMeta.write_only) {
if (fieldRemoteMeta['write_only']) {
field.el.type = 'password'
}
break
@@ -124,7 +124,7 @@ export class FormFieldGenerator {
field.el = el
field.rules = rules
_.set(field, 'attrs.error', '')
Vue.$log.debug('Generate field: ', name, field)
// Vue.$log.debug('Generate field: ', name, field)
return field
}
generateFieldGroup(field, fieldsMeta, remoteFieldsMeta) {
@@ -132,7 +132,8 @@ export class FormFieldGenerator {
this.groups.push({
id: groupTitle,
title: groupTitle,
name: fields[0]
name: fields[0],
fields: fields
})
return this.generateFields(fields, fieldsMeta, remoteFieldsMeta)
}
@@ -146,7 +147,9 @@ export class FormFieldGenerator {
field = this.generateField(field, fieldsMeta, remoteFieldsMeta)
fields.push(field)
} else if (field instanceof Object) {
this.errors[field.prop] = ''
if (this.errors) {
this.errors[field.prop] = ''
}
fields.push(field)
}
}

View File

@@ -58,6 +58,10 @@ export default {
minColumns: {
type: Array,
default: () => []
},
url: {
type: String,
default: ''
}
},
data() {
@@ -67,15 +71,17 @@ export default {
}
},
mounted() {
this.$eventBus.$on('showColumnSettingPopover', () => {
this.showColumnSettingPopover = true
this.iCurrentColumns = this.currentColumns
this.$eventBus.$on('showColumnSettingPopover', ({ url }) => {
if (url === this.url) {
this.showColumnSettingPopover = true
this.iCurrentColumns = this.currentColumns
}
})
},
methods: {
handleColumnConfirm() {
this.showColumnSettingPopover = false
this.$emit('columnsUpdate', this.iCurrentColumns)
this.$emit('columnsUpdate', { columns: this.iCurrentColumns, url: this.url })
}
}
}

View File

@@ -5,6 +5,7 @@
:current-columns="popoverColumns.currentCols"
:total-columns-list="popoverColumns.totalColumnsList"
:min-columns="popoverColumns.minCols"
:url="config.url"
@columnsUpdate="handlePopoverColumnsChange"
/>
</div>
@@ -12,9 +13,10 @@
<script type="text/jsx">
import DataTable from '../DataTable'
import { DateFormatter, DetailFormatter, DisplayFormatter, BooleanFormatter, ActionsFormatter } from '@/components/ListTable/formatters'
import { DateFormatter, DetailFormatter, DisplayFormatter, BooleanFormatter, ActionsFormatter } from '@/components/TableFormatters'
import i18n from '@/i18n/i18n'
import ColumnSettingPopover from './components/ColumnSettingPopover'
import { newURL } from '@/utils/common'
export default {
name: 'AutoDataTable',
components: {
@@ -168,12 +170,12 @@ export default {
col.filters = column.choices.map(item => {
if (typeof (item.value) === 'boolean') {
if (item.value) {
return { text: item.display_name, value: 'True' }
return { text: item['display_name'], value: 'True' }
} else {
return { text: item.display_name, value: 'False' }
return { text: item['display_name'], value: 'False' }
}
}
return { text: item.display_name, value: item.value }
return { text: item['display_name'], value: item.value }
})
col.sortable = false
col['column-key'] = col.prop
@@ -221,14 +223,15 @@ export default {
defaultColumnsNames = totalColumnsNames.filter(n => defaultColumnsNames.indexOf(n) > -1)
// 最小列
const minColumnsNames = _.get(this.iConfig, 'columnsShow.min', ['action', 'id'])
const minColumnsNames = _.get(this.iConfig, 'columnsShow.min', ['actions', 'id'])
.filter(n => defaultColumnsNames.indexOf(n) > -1)
// 应该显示的列
const _tableConfig = localStorage.getItem('tableConfig')
? JSON.parse(localStorage.getItem('tableConfig'))
: {}
const configShowColumnsNames = _.get(_tableConfig[this.$route.name], 'showColumns', null)
const tableName = this.config.name || this.$route.name + '_' + newURL(this.iConfig.url).pathname
const configShowColumnsNames = _.get(_tableConfig[tableName], 'showColumns', null)
let showColumnsNames = configShowColumnsNames || defaultColumnsNames
if (showColumnsNames.length === 0) {
showColumnsNames = totalColumnsNames
@@ -248,7 +251,7 @@ export default {
min: minColumnsNames,
configShow: configShowColumnsNames
}
this.$log.debug('Cleaned colums show: ', this.cleanedColumnsShow)
this.$log.debug('Cleaned columns show: ', this.cleanedColumnsShow)
},
filterShowColumns() {
this.cleanColumnsShow()
@@ -264,13 +267,14 @@ export default {
this.popoverColumns.minCols = this.cleanedColumnsShow.min
this.$log.debug('Popover cols: ', this.popoverColumns)
},
handlePopoverColumnsChange(columns) {
// this.$log.debug('Columns change: ', columns)
handlePopoverColumnsChange({ columns, url }) {
this.$log.debug('Columns change: ', columns)
this.popoverColumns.currentCols = columns
const _tableConfig = localStorage.getItem('tableConfig')
? JSON.parse(localStorage.getItem('tableConfig'))
: {}
_tableConfig[this.$route.name] = {
const tableName = this.config.name || this.$route.name + '_' + newURL(url).pathname
_tableConfig[tableName] = {
'showColumns': columns
}
localStorage.setItem('tableConfig', JSON.stringify(_tableConfig))

View File

@@ -40,7 +40,7 @@ export default {
autoParam: ['id=key', 'name=n', 'level=lv'],
type: 'get',
headers: {
'X-JMS-ORG': JSON.parse(this.$cookie.get('jms_current_org')) ? JSON.parse(this.$cookie.get('jms_current_org')).id : ''
'X-JMS-ORG': this.$store.getters.currentOrg ? this.$store.getters.currentOrg.id : ''
}
},
callback: {
@@ -89,7 +89,7 @@ export default {
return
}
if (currentNode) {
currentNode.name = currentNode.meta.node.value
currentNode.name = currentNode.meta.data.value
}
this.zTree.editName(currentNode)
},
@@ -108,12 +108,12 @@ export default {
const query = Object.assign({}, this.$route.query)
if (treeNode.meta.type === 'node') {
this.currentNode = treeNode
this.currentNodeId = treeNode.meta.node.id
this.currentNodeId = treeNode.meta.data.id
query['node'] = this.currentNodeId
url = `${this.setting.url}${combinator}node_id=${treeNode.meta.node.id}&show_current_asset=${show_current_asset}`
url = `${this.setting.url}${combinator}node_id=${treeNode.meta.data.id}&show_current_asset=${show_current_asset}`
} else if (treeNode.meta.type === 'asset') {
query['asset'] = treeNode.meta.asset.id
url = `${this.setting.url}${combinator}asset_id=${treeNode.meta.asset.id}&show_current_asset=${show_current_asset}`
query['asset'] = treeNode.meta.data.id
url = `${this.setting.url}${combinator}asset_id=${treeNode.meta.data.id}&show_current_asset=${show_current_asset}`
}
this.$router.push({ query })
this.$emit('urlChange', url)
@@ -125,7 +125,7 @@ export default {
return
}
this.$axios.delete(
`${this.treeSetting.nodeUrl}${currentNode.meta.node.id}/`
`${this.treeSetting.nodeUrl}${currentNode.meta.data.id}/`
).then(() => {
this.$message.success(this.$t('common.deleteSuccessMsg'))
this.zTree.removeNode(currentNode)
@@ -143,7 +143,7 @@ export default {
url,
{ 'value': treeNode.name }
).then(res => {
let assetsAmount = treeNode.meta.node.assetsAmount
let assetsAmount = treeNode.meta.data.assetsAmount
if (!assetsAmount) {
assetsAmount = 0
}
@@ -208,9 +208,9 @@ export default {
onDrop: function(event, treeId, treeNodes, targetNode, moveType) {
const treeNodesIds = []
$.each(treeNodes, function(index, value) {
treeNodesIds.push(value.meta.node.id)
treeNodesIds.push(value.meta.data.id)
})
const theUrl = `${this.treeSetting.nodeUrl}${targetNode.meta.node.id}/children/add/`
const theUrl = `${this.treeSetting.nodeUrl}${targetNode.meta.data.id}/children/add/`
this.$axios.put(
theUrl, {
nodes: treeNodesIds
@@ -229,21 +229,21 @@ export default {
}
this.zTree.expandNode(parentNode, true, false, true, false)
// http://localhost/api/v1/assets/nodes/85aa4ee2-0bd9-41db-9079-aa3646448d0c/children/
const url = `${this.treeSetting.nodeUrl}${parentNode.meta.node.id}/children/`
const url = `${this.treeSetting.nodeUrl}${parentNode.meta.data.id}/children/`
this.$axios.post(url, {}).then(data => {
const newNode = {
id: data['key'],
name: data['value'],
pId: parentNode.id,
meta: {
'node': data
data: data
}
}
newNode.checked = this.zTree.getSelectedNodes()[0].checked
this.zTree.addNodes(parentNode, 0, newNode)
// vm.$refs.dataztree.refresh()
const node = this.zTree.getNodeByParam('id', newNode.id, parentNode)
this.currentNodeId = node.meta.node.id || newNode.id
this.currentNodeId = node.meta.data.id || newNode.id
this.zTree.editName(node)
this.$message.success(this.$t('common.createSuccessMsg'))
}).catch(error => {

View File

@@ -37,15 +37,13 @@
class="action-item"
@click="handleClick(action)"
>
<el-tooltip v-if="action.tip" effect="dark" :content="action.tip" placement="top">
<i v-if="action.fa" :class="'fa ' + action.fa" />{{ action.title }}
<el-tooltip :disabled="!action.tip" :content="action.tip" placement="top">
<span>
<i v-if="action.fa" :class="'fa ' + action.fa" />{{ action.title }}
</span>
</el-tooltip>
<span v-else>
<i v-if="action.fa" :class="'fa ' + action.fa" />{{ action.title }}
</span>
</el-button>
</template>
</div>
</template>

View File

@@ -97,6 +97,7 @@ export default {
}
},
props: {
// eslint-disable-next-line vue/require-default-prop
data: Object,
prop: {
type: String,
@@ -104,10 +105,13 @@ export default {
return this.data.id
}
},
// eslint-disable-next-line vue/require-prop-types,vue/require-default-prop
itemValue: {},
// eslint-disable-next-line vue/require-default-prop
value: Object,
disabled: Boolean,
readonly: Boolean,
// eslint-disable-next-line vue/require-default-prop
options: Array
},
data() {

View File

@@ -1,7 +1,7 @@
<template>
<el-form ref="elForm" v-bind="$attrs" :model="value" class="el-form-renderer">
<template v-for="item in innerContent">
<slot :name="`id:${item.id}`" />
<slot v-if="!isHidden(item)" :name="`id:${item.id}`" />
<component
:is="item.type === GROUP ? 'render-form-group' : 'render-form-item'"
:key="item.id"
@@ -13,7 +13,7 @@
:options="options[item.id]"
@updateValue="updateValue"
/>
<slot :name="`$id:${item.id}`" />
<slot v-if="!isHidden(item)" :name="`$id:${item.id}`" />
</template>
<slot />
</el-form>
@@ -202,6 +202,18 @@ export default {
setOptions(id, options) {
_set(this.options, id, options)
this.options = { ...this.options } // 设置之前不存在的 options 时需要重新设置响应式更新
},
isHidden(item) {
if (!item.el || !item.el['hiddenGroup']) {
return false
}
if (item.hidden === true) {
return true
}
if (typeof item.hidden === 'function') {
return item.hidden(this.value)
}
return false
}
}
}

View File

@@ -12,7 +12,7 @@
<slot v-for="item in fields" :slot="`id:${item.id}`" :name="`id:${item.id}`" />
<slot v-for="item in fields" :slot="`$id:${item.id}`" :name="`$id:${item.id}`" />
<el-form-item>
<el-form-item v-if="hasButtons" class="form-buttons">
<el-button v-for="button in moreButtons" :key="button.title" size="small" v-bind="button" @click="handleClick(button)">{{ button.title }}</el-button>
<el-button v-if="defaultButton && hasReset" size="small" @click="resetForm('form')">{{ $t('common.Reset') }}</el-button>
<el-button v-if="defaultButton && hasSaveContinue" size="small" @click="submitForm('form', true)">{{ $t('common.SaveAndAddAnother') }}</el-button>
@@ -32,6 +32,10 @@ export default {
type: Boolean,
default: true
},
hasButtons: {
type: Boolean,
default: true
},
hasReset: {
type: Boolean,
default: true

View File

@@ -19,3 +19,16 @@ export default {
RequiredChange,
EmailCheck
}
export const JsonRequired = {
required: true,
trigger: 'change',
validator: (rule, value, callback) => {
try {
JSON.parse(value)
callback()
} catch (e) {
callback(new Error(i18n.t('common.InvalidJson')))
}
}
}

View File

@@ -426,7 +426,6 @@ export default {
onEdit: {
type: Function,
default(row) {
// console.log('On delete row')
}
},
/**
@@ -841,7 +840,7 @@ export default {
this.$emit('selection-change', val)
},
totalData(val) {
if (val) {
if (val && val.length !== this.total) {
this.page = defaultFirstPage
this.total = val.length
this.getList()
@@ -862,6 +861,9 @@ export default {
}
}
}
if (this.totalData) {
this.getList()
}
},
methods: {
getQuery() {
@@ -925,6 +927,9 @@ export default {
if (!this.hasPagination) {
this.data = this.totalData
this.loading = false
if (this.isTree) {
this.data = this.tree2Array(this.data, this.expandAll)
}
return this.data
}
// page
@@ -935,6 +940,7 @@ export default {
this.$log.debug(`page: ${page}, size: ${this.size}, start: ${start}, end: ${end}`)
this.data = this.totalData.slice(start, end)
this.loading = false
this.data = this.tree2Array(this.data, this.expandAll)
return this.data
},
/**

View File

@@ -94,7 +94,12 @@ export default {
tableConfig() {
const tableDefaultConfig = this.defaultConfig
tableDefaultConfig.paginationSize = _.get(this.globalTableConfig, 'paginationSize', 15)
let tableAttrs = tableDefaultConfig.tableAttrs
if (this.config.tableAttrs) {
tableAttrs = Object.assign(tableAttrs, this.config.tableAttrs)
}
const config = Object.assign(tableDefaultConfig, this.config)
config.tableAttrs = tableAttrs
return config
},
iListeners() {

View File

@@ -28,13 +28,20 @@ export default {
return this.formatter(this.item, this.value)
}
if (typeof this.value === 'boolean') {
return <span>{this.toChoicesDisplay(this.value)}</span>
return (
<span class='item-value'>{this.toChoicesDisplay(this.value)}</span>
)
}
return <span>{this.value}</span>
return (
<span class='item-value'>{this.value}</span>
)
}
}
</script>
<style scoped>
.item-value {
word-break: break-word;
}
</style>

View File

@@ -74,7 +74,7 @@ export default {
}
.dialog-footer {
padding-right: 50px;
padding-right: 20px;
}
</style>

View File

@@ -0,0 +1,30 @@
<template>
<el-link @click="onClick">
{{ title }}
</el-link>
</template>
<script>
export default {
name: 'Link',
props: {
href: {
type: String,
default: ''
},
title: {
type: String,
default: ''
}
},
methods: {
onClick() {
window.open(this.href)
}
}
}
</script>
<style scoped>
</style>

View File

@@ -2,13 +2,14 @@
<el-select
ref="select"
v-model="iValue"
v-loading="!initialized"
v-loadmore="loadMore"
:options="iOptions"
:remote="remote"
:remote-method="filterOptions"
:multiple="multiple"
filterable
:clearable="clearable"
filterable
popper-append-to-body
class="select2"
v-bind="$attrs"
@@ -97,7 +98,6 @@ export default {
return {
loading: false,
initialized: false,
iValue: this.value ? this.value : [],
defaultParams: _.cloneDeep(defaultParams),
params: _.cloneDeep(defaultParams),
iOptions: this.options || [],
@@ -112,6 +112,17 @@ export default {
optionsValues() {
return this.iOptions.map((v) => v.value)
},
iValue: {
set(val) {
if (!val) {
return
}
this.$emit('input', val)
},
get() {
return this.value
}
},
iAjax() {
const defaultPageSize = 10
const defaultMakeParams = (params) => {
@@ -161,11 +172,14 @@ export default {
this.iValue = iNew
}
},
mounted() {
async mounted() {
// this.$log.debug('Select2 url is: ', this.iAjax.url)
if (!this.initialized) {
this.initialSelect()
this.initialized = true
await this.initialSelect()
setTimeout(() => {
this.initialized = true
this.iValue = this.value
})
}
this.$nextTick(() => {
// elform
@@ -243,9 +257,13 @@ export default {
// this.$log.debug('Select ajax config', this.iAjax)
if (this.iAjax.url) {
if (this.value && this.value.length !== 0) {
this.$log.debug('Start init select2 value')
const data = await createSourceIdCache(this.value)
this.params.spm = data.spm
this.$log.debug('Start init select2 value, ', this.value)
let value = this.value
if (!Array.isArray(value)) {
value = [value]
}
const data = await createSourceIdCache(value)
this.params.spm = data['spm']
await this.getInitialOptions()
}
await this.getOptions()

View File

@@ -6,16 +6,19 @@
</template>
<script>
export default {
props: {
value: {
type: String,
default: () => ''
},
// value: {
// type: String,
// default: () => ''
// },
tip: {
type: String,
default: () => ''
},
toFormat: {
type: String,
default: () => 'string'
}
},
methods: {
@@ -26,7 +29,11 @@ export default {
const vm = this
const reader = new FileReader()
reader.onload = function() {
vm.$emit('input', this.result)
let result = this.result
if (vm.toFormat === 'object') {
result = JSON.parse(result)
}
vm.$emit('input', result)
}
reader.readAsText(
e.target.files[0]

View File

@@ -7,7 +7,7 @@
</template>
<script>
import PasswordInput from '../PasswordInput'
import PasswordInput from './PasswordInput'
import { mapGetters } from 'vuex'
import store from '@/store'
import i18n from '@/i18n/i18n'
@@ -22,8 +22,12 @@ export default {
}
},
rules(item) {
let userIsOrgAdmin = item.el.userIsOrgAdmin
// undefined 使使
userIsOrgAdmin = userIsOrgAdmin === undefined ? store.getters.currentUserIsAdmin : userIsOrgAdmin
const passwordRule = store.getters.publicSettings.PASSWORD_RULE
const validatePassword = (rule, value, callback) => {
const validatePassword = function(rule, value, callback) {
if (!value) {
return callback()
}
@@ -46,7 +50,10 @@ export default {
return callback(new Error(msg))
}
}
const secureLength = passwordRule ? passwordRule.SECURITY_PASSWORD_MIN_LENGTH : 7
let secureLength = passwordRule ? passwordRule.SECURITY_PASSWORD_MIN_LENGTH : 7
if (userIsOrgAdmin) {
secureLength = passwordRule ? passwordRule.SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH : 7
}
if (value.length < secureLength) {
return callback(new Error(i18n.t('common.password.MIN_LENGTH_ERROR', [secureLength])))
}
@@ -59,17 +66,12 @@ export default {
data() {
return {
attrs: {
secureLength: 7
}
}
},
computed: {
...mapGetters(['publicSettings'])
},
created() {
const passwordRule = this.publicSettings.PASSWORD_RULE || {}
this.attrs.secureLength = passwordRule ? passwordRule.SECURITY_PASSWORD_MIN_LENGTH : 7
},
methods: {
handleInput(value) {
this.$emit('input', value)

View File

@@ -0,0 +1,250 @@
<template>
<div>
<div class="hours-container">
<div v-for="(item, index) in hours" :key="index" class="hours-item">
<div class="hours-item-header">{{ compItem(item) }}</div>
<div class="hours-item-value">
<div
:class="compClass(2 * item)"
@click="handleClick(2 * item)"
@mouseover="handleHover(2 * item)"
/>
</div>
</div>
</div>
<div class="tips">{{ tips }}</div>
</div>
</template>
<script>
export default {
model: {
prop: 'sendTimeList'
},
props: {
sendTimeList: {
type: Object,
required: true,
default: () => []
},
readonly: {
type: Boolean,
default: false
}
},
data() {
return {
hours: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23], // 选项
selectStart: false, // 开始
startIndex: '', // 开始下标
timeRangeList: [], // 选择的时间段
timeRangeListIndex: [], // 选中的下标
tempRangeIndex: [], // 预选下标
tips: '向右选中,向左取消选择'
}
},
computed: {
},
watch: {
timeRangeList: function(value) {
this.$emit('change', value)
this.$parent.$emit('el.form.change')// 触发父组件的校验规则
},
sendTimeList: {
handler() {
this.transformedIndex()
},
deep: true
}
},
mounted() {
this.transformedIndex()
},
methods: {
// 时间区间转换成下标区间
transformedIndex() {
this.timeRangeListIndex = []
this.timeRangeList = this.sendTimeList
this.timeRangeList.forEach(element => {
const [startTime, endTime] = element.match(/\d+\:\d+/g)
if (startTime && endTime) {
const [startHour, startMin] = startTime.split(':')
const [endHour, endMin] = endTime.split(':')
if (startHour && startMin && endHour && endMin) {
let startNum, endNum
if (startMin === '00') {
startNum = 2 * parseInt(startHour)
} else {
startNum = 2 * parseInt(startHour) + 1
}
if (endMin === '00') {
endNum = 2 * parseInt(endHour) - 1
} else {
endNum = 2 * parseInt(endHour)
}
while (endNum >= startNum) {
this.timeRangeListIndex.push(startNum)
startNum++
}
} else {
this.$message.error('时间段格式不正确')
}
} else {
this.$message.error('没有拿到开始时间或结束时间或者时间段格式不对')
}
})
this.tips = this.timeRangeList && this.timeRangeList.length > 0 ? this.timeRangeList : '向右选中,向左取消选择'
},
// 下标区间转换成时间区间
transformedSection() {
this.timeRangeList = []
let startTime = ''; let endTime = ''; const len = this.hours.length
for (let index = this.hours[0] * 2; index < 2 * (len + 1); index++) {
if (this.timeRangeListIndex.indexOf(index) > -1) {
if (startTime) { // 如果有开始时间,直接确定结束时间
const endHour = Math.floor((index + 1) / 2)
const endMin = (index + 1) % 2 === 0 ? '00' : '30'
endTime = `${endHour < 10 ? '0' + endHour : endHour}:${endMin}`
} else { // 没有开始时间,确定当前点为开始时间
const startHour = Math.floor(index / 2)
const startMin = index % 2 === 0 ? '00' : '30'
startTime = `${startHour < 10 ? '0' + startHour : startHour}:${startMin}`
}
if (index === 2 * this.hours.length + 1) { // 如果是最后一格,直接结束
endTime = `${Math.floor((index + 1) / 2)}:00`
this.timeRangeList.push(`${startTime || '23:30'}-${endTime}`)
startTime = ''
endTime = ''
}
} else { // 若这个点不在选择区间,确定一个时间段
if (startTime && endTime) {
this.timeRangeList.push(`${startTime}-${endTime}`)
startTime = ''
endTime = ''
} else if (startTime && !endTime) { // 这里可能只选半个小时
const endHour = Math.floor(index / 2)
const endMin = index % 2 === 0 ? '00' : '30'
endTime = `${endHour < 10 ? '0' + endHour : endHour}:${endMin}`
this.timeRangeList.push(`${startTime}-${endTime}`)
startTime = ''
endTime = ''
}
}
}
this.tips = this.timeRangeList && this.timeRangeList.length > 0 ? this.timeRangeList : '向右选中,向左取消选择'
},
// 点击事件
handleClick(index) {
if (this.selectStart) {
if (index === this.startIndex) { // 双击取反
if (this.timeRangeListIndex.indexOf(index) > -1) {
this.timeRangeListIndex.splice(this.timeRangeListIndex.indexOf(index), 1)
} else {
this.timeRangeListIndex.push(this.startIndex)
}
} else if (index > this.startIndex) { // 选取数据--向右添加,向左取消
while (index >= this.startIndex) {
this.timeRangeListIndex.push(this.startIndex)
this.startIndex++
}
this.timeRangeListIndex = Array.from(new Set(this.timeRangeListIndex))
} else { // 删除数据
while (this.startIndex >= index) {
if (this.timeRangeListIndex.indexOf(index) > -1) {
this.timeRangeListIndex.splice(this.timeRangeListIndex.indexOf(index), 1)
}
index++
}
}
this.startIndex = ''
this.tempRangeIndex = []
this.transformedSection()
} else {
this.startIndex = index
}
this.selectStart = !this.selectStart
},
// 预选区间
handleHover(index) {
if (this.selectStart) {
this.tempRangeIndex = []
if (index > this.startIndex) { // 选取数据--向右添加,向左取消
while (index >= this.startIndex) {
this.tempRangeIndex.push(index)
index--
}
} else { // 删除数据
while (this.startIndex >= index) {
this.tempRangeIndex.push(index)
index++
}
}
}
},
// 是否选中计算className
compClass(index) {
if (index === this.startIndex) {
return 'hours-item-left preSelected'
}
if (index >= this.startIndex) {
if (this.tempRangeIndex.indexOf(index) > -1) {
return 'hours-item-left preSelected'
}
} else {
if (this.tempRangeIndex.indexOf(index) > -1) {
return 'hours-item-left unSelected'
}
}
return this.timeRangeListIndex.indexOf(index) > -1 ? 'hours-item-left selected' : 'hours-item-left'
},
compItem(item) { // 不足10前面补0
return item < 10 ? `0${item}` : item
}
}
}
</script>
<style lang='scss' scoped>
.hours-container {
display: flex;
cursor: pointer;
.hours-item {
width: 30px;
height: 60px;
border: 1px solid #c2d0f3;
border-right: none;
text-align: center;
&:last-child {
border-right: 1px solid #c2d0f3;
}
.hours-item-header {
width: 100%;
height: 30px;
line-height: 30px;
border-bottom: 1px solid #c2d0f3;
}
.hours-item-value {
width: 100%;
height: 30px;
box-sizing: border-box;
display: flex;
}
.selected {
background-color: #4e84fe;
border-bottom: 1px solid #c2d0f3;
}
.preSelected {
background-color: #8eaffc;
border-bottom: 1px solid #c2d0f3;
}
.unSelected {
background-color: #ffffff;
border-bottom: 1px solid #c2d0f3;
}
}
}
.tips {
width: 100%;
line-height: 30px;
}
</style>

View File

@@ -0,0 +1,33 @@
import DatetimeRangePicker from './DatetimeRangePicker'
import Link from './Link'
import PasswordInput from './PasswordInput'
import Select2 from './Select2'
import Swicher from './Swicher'
import UploadField from './UploadField'
import UploadKey from './UploadKey'
import UserPassword from './UserPassword'
import WeekCronSelect from './WeekCronSelect'
export default {
DatetimeRangePicker,
Link,
PasswordInput,
Select2,
Swicher,
UploadKey,
UploadField,
UserPassword,
WeekCronSelect
}
export {
DatetimeRangePicker,
Link,
PasswordInput,
Select2,
Swicher,
UploadKey,
UploadField,
UserPassword,
WeekCronSelect
}

View File

@@ -1,20 +1,24 @@
<template>
<div class="form-group-header">
<div v-if="line" class="hr-line-dashed" />
<h3>{{ title }}</h3>
<h3>{{ group.title }}</h3>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
default: 'Title'
},
line: {
type: Boolean,
default: true
},
index: {
type: Number,
default: 1
},
group: {
type: Object,
default: () => ({})
}
}
}

View File

@@ -3,7 +3,7 @@
</template>
<script type="text/jsx">
import { DetailFormatter, SystemUserFormatter } from '@/components/ListTable/formatters'
import { DetailFormatter, SystemUserFormatter } from '@/components/TableFormatters'
import TreeTable from '../TreeTable'
export default {
@@ -27,7 +27,7 @@ export default {
vm.tableConfig.initialUrl = vm.tableConfig.url
}
const initialUrl = vm.tableConfig.initialUrl
const nodeId = node.meta.node.id
const nodeId = node.meta.data.id
const url = initialUrl.replace('/assets/', `/nodes/${nodeId}/assets/`)
vm.tableConfig.url = url
}

View File

@@ -1,29 +1,45 @@
<template>
<Dialog v-if="showExportDialog" :title="$t('common.Export')" :visible.sync="showExportDialog" :destroy-on-close="true" @confirm="handleExportConfirm()" @cancel="handleExportCancel()">
<el-form label-position="left" style="padding-left: 50px">
<el-form-item :label="$t('common.fileType' )" :label-width="'100px'">
<el-radio-group v-model="exportTypeOption">
<el-radio v-for="option of exportTypeOptions" :key="option.value" style="padding: 10px 20px;" :label="option.value" :disabled="!option.can">{{ option.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item class="export-form" :label="this.$t('common.imExport.ExportRange')" :label-width="'100px'">
<el-radio-group v-model="exportOption">
<el-radio v-for="option of exportOptions" :key="option.value" class="export-item" :label="option.value" :disabled="!option.can">{{ option.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</Dialog>
<div>
<MFAVerifyDialog
v-if="mfaDialogShow"
@MFAVerifyDone="showExportDialog"
@MFAVerifyCancel="handleExportCancel"
/>
<Dialog
v-if="exportDialogShow"
:title="$t('common.Export')"
:visible.sync="exportDialogShow"
:destroy-on-close="true"
@confirm="handleExportConfirm()"
@cancel="handleExportCancel()"
>
<el-form label-position="left" style="padding-left: 50px">
<el-form-item :label="$t('common.fileType' )" :label-width="'100px'">
<el-radio-group v-model="exportTypeOption">
<el-radio v-for="option of exportTypeOptions" :key="option.value" style="padding: 10px 20px;" :label="option.value" :disabled="!option.can">{{ option.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item class="export-form" :label="this.$t('common.imExport.ExportRange')" :label-width="'100px'">
<el-radio-group v-model="exportOption">
<el-radio v-for="option of exportOptions" :key="option.value" class="export-item" :label="option.value" :disabled="!option.can">{{ option.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</Dialog>
</div>
</template>
<script>
import Dialog from '@/components/Dialog'
import MFAVerifyDialog from '@/components/MFAVerifyDialog'
import { createSourceIdCache } from '@/api/common'
import * as queryUtil from '@/components/DataTable/compenents/el-data-table/utils/query'
export default {
name: 'ExportDialog',
components: {
Dialog
Dialog,
MFAVerifyDialog
},
props: {
selectedRows: {
@@ -32,12 +48,20 @@ export default {
},
url: {
type: String,
default: () => ''
default: ''
},
beforeExport: {
type: Function,
default: () => {}
},
mfaVerifyRequired: {
type: Boolean,
default: false
},
performExport: {
type: Function,
default(selectedRows, exportOptions, query) {
return this.defaultPerformExport(selectedRows, exportOptions, query)
default(selectedRows, exportOptions, query, exportType) {
return this.defaultPerformExport(selectedRows, exportOptions, query, exportType)
}
},
canExportAll: {
@@ -55,10 +79,12 @@ export default {
},
data() {
return {
showExportDialog: false,
exportDialogShow: false,
exportOption: 'all',
exportTypeOption: 'csv',
meta: {}
meta: {},
mfaVerified: false,
mfaDialogShow: false
}
},
computed: {
@@ -115,18 +141,33 @@ export default {
}
},
mounted() {
this.$eventBus.$on('showExportDialog', (row) => {
this.showExportDialog = true
this.$eventBus.$on('showExportDialog', ({ selectedRows, url, name }) => {
// Todo: 没有时间了,只能先这么处理了
if (url === this.url || url.indexOf(this.url) > -1 || url.indexOf('account') > -1) {
this.showExportDialog()
}
})
},
methods: {
showExportDialog() {
if (!this.mfaVerifyRequired) {
this.exportDialogShow = true
return
}
// 这是需要校验 MFA 的
if (!this.mfaDialogShow) {
this.mfaDialogShow = true
} else {
this.exportDialogShow = true
}
},
downloadCsv(url) {
const a = document.createElement('a')
a.href = url
a.click()
window.URL.revokeObjectURL(url)
},
async defaultPerformExport(selectRows, exportOption, q) {
async defaultPerformExport(selectRows, exportOption, q, exportTypeOption) {
const url = (process.env.VUE_APP_ENV === 'production') ? (`${this.url}`) : (`${process.env.VUE_APP_BASE_API}${this.url}`)
const query = Object.assign({}, q)
if (exportOption === 'selected') {
@@ -137,13 +178,8 @@ export default {
}
const spm = await createSourceIdCache(resources)
query['spm'] = spm.spm
} else if (exportOption === 'filtered') {
// console.log(listTableRef)
// console.log(listTableRef.dataTable)
// delete query['limit']
// delete query['offset']
}
query['format'] = this.exportTypeOption
query['format'] = exportTypeOption
const queryStr =
(url.indexOf('?') > -1 ? '&' : '?') +
queryUtil.stringify(query, '=', '&')
@@ -151,17 +187,20 @@ export default {
},
async handleExport() {
const listTableRef = this.$parent.$parent.$parent.$parent
const query = listTableRef.dataTable.getQuery()
const query = listTableRef['dataTable'].getQuery()
delete query['limit']
delete query['offset']
return this.performExport(this.selectedRows, this.exportOption, query)
await this.beforeExport()
return this.performExport(this.selectedRows, this.exportOption, query, this.exportTypeOption)
},
async handleExportConfirm() {
await this.handleExport()
this.showExportDialog = false
this.exportDialogShow = false
this.mfaDialogShow = false
},
handleExportCancel() {
this.showExportDialog = false
this.exportDialogShow = false
this.mfaDialogShow = false
}
}
}

View File

@@ -1,7 +1,7 @@
<template>
<div>
<ExportDialog :selected-rows="selectedRows" :url="url" v-bind="$attrs" v-on="$listeners" />
<ImportDialog :selected-rows="selectedRows" :url="url" v-bind="$attrs" v-on="$listeners" />
<ExportDialog :selected-rows="selectedRows" v-bind="exportOptions" v-on="$listeners" />
<ImportDialog :selected-rows="selectedRows" v-bind="importOptions" v-on="$listeners" />
</div>
</template>
@@ -10,7 +10,7 @@ import ExportDialog from './ExportDialog'
import ImportDialog from './ImportDialog'
export default {
name: 'DialogAction',
name: 'ImExportDialog',
components: {
ExportDialog,
ImportDialog
@@ -20,9 +20,13 @@ export default {
type: Array,
default: () => []
},
url: {
type: String,
default: () => ''
exportOptions: {
type: Object,
default: () => ({})
},
importOptions: {
type: Object,
default: () => ({})
}
},
data() {

View File

@@ -7,7 +7,6 @@
:loading-status="loadStatus"
width="80%"
class="importDialog"
:confirm-title="confirmTitle"
:show-cancel="false"
:show-confirm="false"
@close="handleImportCancel"
@@ -118,9 +117,6 @@ export default {
} else {
return this.$t('common.Import') + this.$t('common.Update')
}
},
confirmTitle() {
return '导入'
}
},
watch: {
@@ -129,8 +125,10 @@ export default {
}
},
mounted() {
this.$eventBus.$on('showImportDialog', (row) => {
this.showImportDialog = true
this.$eventBus.$on('showImportDialog', ({ url }) => {
if (url === this.url) {
this.showImportDialog = true
}
})
},
methods: {

View File

@@ -33,8 +33,8 @@
<script>
import DataTable from '@/components/DataTable'
import { sleep } from '@/utils/common'
import { EditableInputFormatter, StatusFormatter } from '@/components/ListTable/formatters'
import { sleep, getUpdateObjURL } from '@/utils/common'
import { EditableInputFormatter, StatusFormatter } from '@/components/TableFormatters'
export default {
name: 'ImportTable',
components: {
@@ -207,7 +207,7 @@ export default {
if (!d) {
return 0
}
if (!itemColData || !itemColData.length) {
if (typeof itemColData !== 'number' && (!itemColData || !itemColData.length)) {
return 0
}
return itemColData.length
@@ -350,7 +350,7 @@ export default {
}
},
async performUpdateObject(item) {
const updateUrl = `${this.url}${item.id}/`
const updateUrl = getUpdateObjURL(this.url, item.id)
return this.$axios.put(
updateUrl,
item,
@@ -421,6 +421,7 @@ export default {
.importTable >>> .cell {
min-height: 20px;
height: 100%;
overflow: auto;
}
</style>

View File

@@ -24,6 +24,16 @@ export default {
hasLeftActions: defaultTrue,
hasCreate: defaultTrue,
canCreate: defaultTrue,
createRoute: {
type: [String, Object, Function],
default: function() {
return this.$route.name.replace('List', 'Create')
}
},
createInNewPage: {
type: Boolean,
default: false
},
hasBulkDelete: defaultTrue,
hasBulkUpdate: defaultFalse,
hasMoreActions: defaultTrue,
@@ -31,12 +41,6 @@ export default {
type: String,
default: ''
},
createRoute: {
type: [String, Object],
default: function() {
return this.$route.name.replace('List', 'Create')
}
},
reloadTable: {
type: Function,
default: () => {}
@@ -149,14 +153,22 @@ export default {
},
methods: {
handleCreate() {
let route = {}
let route
if (typeof this.createRoute === 'string') {
route = { name: this.createRoute }
route.name = this.createRoute
} else {
} else if (typeof this.createRoute === 'function') {
route = this.createRoute()
} else if (typeof this.createRoute === 'object') {
route = this.createRoute
}
this.$router.push(route)
this.$log.debug('handle create')
if (this.createInNewPage) {
const { href } = this.$router.resolve(route)
window.open(href, '_blank')
} else {
this.$router.push(route)
}
},
defaultBulkDeleteCallback({ selectedRows, reloadTable }) {
const msg = this.$t('common.deleteWarningMsg') + ' ' + selectedRows.length + ' ' + this.$t('common.rows') + ' ?'

View File

@@ -1,7 +1,12 @@
<template>
<div>
<ActionsGroup :is-fa="true" :actions="rightSideActions" class="right-side-actions right-side-item" />
<ImExportDialog :selected-rows="selectedRows" :url="tableUrl" v-bind="$attrs" />
<ImExportDialog
:selected-rows="selectedRows"
:export-options="iExportOptions"
:import-options="iImportOptions"
v-bind="$attrs"
/>
</div>
</template>
@@ -9,6 +14,7 @@
import ActionsGroup from '@/components/ActionsGroup'
import ImExportDialog from './ImExportDialog'
import { cleanActions } from './utils'
import { assignIfNot } from '@/utils/common'
const defaultTrue = { type: Boolean, default: true }
@@ -24,27 +30,41 @@ export default {
default: ''
},
hasExport: defaultTrue,
handleExport: {
exportOptions: {
type: Object,
default: () => ({})
},
handleExportClick: {
type: Function,
default: function({ selectedRows }) {
this.$eventBus.$emit('showExportDialog', { selectedRows })
this.$eventBus.$emit('showExportDialog', { selectedRows, url: this.tableUrl, name: this.name })
}
},
hasImport: defaultTrue,
handleImport: {
importOptions: {
type: Object,
default: () => ({})
},
handleImportClick: {
type: Function,
default: function({ selectedRows }) {
this.$eventBus.$emit('showImportDialog', { selectedRows })
this.$eventBus.$emit('showImportDialog', { selectedRows, url: this.tableUrl, name: this.name })
}
},
hasColumnSetting: defaultTrue,
handleColumnConfig: {
handleTableSettingClick: {
type: Function,
default: function() {
this.$eventBus.$emit('showColumnSettingPopover')
default: function({ selectedRows }) {
this.$eventBus.$emit('showColumnSettingPopover', { url: this.tableUrl, row: selectedRows, name: this.name })
}
},
hasRefresh: defaultTrue,
handleRefreshClick: {
type: Function,
default: function() {
this.reloadTable()
}
},
selectedRows: {
type: Array,
default: () => []
@@ -61,13 +81,12 @@ export default {
data() {
return {
defaultRightSideActions: [
{ name: 'actionColumnSetting', fa: 'fa-cog', tip: this.$t('common.CustomCol'), has: this.hasColumnSetting, callback: this.handleColumnConfig.bind(this) },
{ name: 'actionExport', fa: 'fa-download', tip: this.$t('common.Export'), has: this.hasExport, callback: this.handleExport.bind(this) },
{ name: 'actionImport', fa: 'fa-upload', tip: this.$t('common.Import'), has: this.hasImport, callback: this.handleImport.bind(this) },
{ name: 'actionRefresh', fa: 'fa-refresh', tip: this.$t('common.Refresh'), has: this.hasRefresh, callback: this.handleRefresh }
{ name: 'actionColumnSetting', fa: 'fa-cog', tip: this.$t('common.CustomCol'), has: this.hasColumnSetting, callback: this.handleTableSettingClick.bind(this) },
{ name: 'actionImport', fa: 'fa-upload', tip: this.$t('common.Import'), has: this.hasImport, callback: this.handleImportClick.bind(this) },
{ name: 'actionExport', fa: 'fa-download', tip: this.$t('common.Export'), has: this.hasExport, callback: this.handleExportClick.bind(this) },
{ name: 'actionRefresh', fa: 'fa-refresh', tip: this.$t('common.Refresh'), has: this.hasRefresh, callback: this.handleRefreshClick.bind(this) }
],
dialogExportVisible: false,
exportValue: 2
dialogExportVisible: false
}
},
computed: {
@@ -81,20 +100,18 @@ export default {
},
hasSelectedRows() {
return this.selectedRows.length > 0
},
iImportOptions() {
return assignIfNot(this.importOptions, { url: this.tableUrl })
},
iExportOptions() {
const options = assignIfNot(this.exportOptions, { url: this.tableUrl })
return options
}
},
methods: {
handleTagSearch(val) {
this.searchTable(val)
},
// handleExport({ selectedRows }) {
// this.$eventBus.$emit('showExportDialog', { selectedRows })
// },
// handleImport({ selectedRows }) {
// this.$eventBus.$emit('showImportDialog', { selectedRows })
// },
handleRefresh() {
this.reloadTable()
}
}
}

View File

@@ -20,7 +20,7 @@
<script>
import AutoDataSearch from '@/components/AutoDataSearch'
import LeftSide from './LeftSide'
import DatetimeRangePicker from '@/components/DatetimeRangePicker'
import DatetimeRangePicker from '@/components/FormFields/DatetimeRangePicker'
import RightSide from './RightSide'
const defaultTrue = { type: Boolean, default: true }

View File

@@ -1,116 +0,0 @@
<template>
<ActionsGroup v-loading="loadStatus" :size="'mini'" :actions="actions" :more-actions="moreActions" />
</template>
<script>
import ActionsGroup from '@/components/ActionsGroup/index'
import BaseFormatter from './base'
export default {
name: 'LoadingActionsFormatter',
components: { ActionsGroup },
extends: BaseFormatter,
props: {
formatterArgsDefault: {
type: Object,
default: function() {
return {
hasUpdate: true, // can set function(row, value)
canUpdate: true, // can set function(row, value)
hasDelete: true, // can set function(row, value)
canDelete: true,
updateRoute: this.$route.name.replace('List', 'Update'),
extraActions: [] // format see defaultActions
}
}
}
},
data() {
const colActions = Object.assign(this.formatterArgsDefault, this.col.formatterArgs)
const defaultActions = [
{
name: 'update',
title: this.$t('common.Update'),
type: 'primary',
has: colActions.hasUpdate,
can: colActions.canUpdate,
callback: colActions.onUpdate
},
{
name: 'delete',
title: this.$t('common.Delete'),
type: 'danger',
has: colActions.hasDelete,
can: colActions.canDelete,
callback: colActions.onDelete
}
]
return {
colActions: colActions,
defaultActions: defaultActions,
extraActions: colActions.extraActions
}
},
computed: {
cleanedActions() {
let actions = [...this.defaultActions, ...this.extraActions]
actions = _.cloneDeep(actions)
actions = actions.map((v) => {
v.has = this.cleanBoolean(v, 'has')
v.can = this.cleanBoolean(v, 'can')
v.fa = this.cleanFa(v, 'fa')
v.callback = this.cleanCallback(v)
return v
})
actions = actions.filter((v) => v.has)
return actions
},
actions() {
if (this.cleanedActions.length <= 2) {
return this.cleanedActions
}
return this.cleanedActions.slice(0, 1)
},
moreActions() {
if (this.cleanedActions.length <= 2) {
return []
}
return this.cleanedActions.slice(1, this.cleanedActions.length)
},
loadStatus() {
return this.col.formatterArgs.loading
}
},
methods: {
cleanBoolean(item, attr) {
const ok = item[attr]
if (typeof ok !== 'function') {
return ok === undefined ? true : ok
}
return ok(this.row, this.cellValue)
},
cleanCallback(item) {
const callback = item.callback
const attrs = {
reload: this.reload,
row: this.row,
col: this.col,
cellValue: this.cellValue,
tableData: this.tableData
}
return () => { return callback.bind(this)(attrs) }
},
cleanFa(item, attr) {
const ok = item[attr]
if (typeof ok !== 'function') {
return ok === undefined ? false : ok
}
return ok(this.row, this.cellValue)
}
}
}
</script>
<style scoped>
</style>

View File

@@ -1,8 +1,21 @@
<template>
<div>
<TableAction :table-url="iTableConfig.url" :search-table="search" :date-pick="handleDateChange" v-bind="headerActions" :selected-rows="selectedRows" :reload-table="reloadTable" />
<TableAction
:table-url="tableUrl"
:search-table="search"
:date-pick="handleDateChange"
:selected-rows="selectedRows"
:reload-table="reloadTable"
v-bind="headerActions"
/>
<IBox class="table-content">
<AutoDataTable ref="dataTable" :filter-table="filter" :config="iTableConfig" @selection-change="handleSelectionChange" v-on="$listeners" />
<AutoDataTable
ref="dataTable"
:filter-table="filter"
:config="iTableConfig"
@selection-change="handleSelectionChange"
v-on="$listeners"
/>
</IBox>
</div>
</template>
@@ -50,6 +63,9 @@ export default {
this.$log.debug('Header actions', this.headerActions)
this.$log.debug('ListTable: iTableConfig change', config)
return config
},
tableUrl() {
return this.iTableConfig.url
}
},
watch: {
@@ -113,15 +129,6 @@ export default {
& >>> .el-table__header thead > tr > th {
background-color: white;
}
/*& >>> .el-table--striped .el-table__body tr.el-table__row--striped td {*/
/*background: white;*/
/*}*/
/*& >>> .el-table th, .el-table tr {*/
/*background-color: red;*/
/*!*background-color: #FAFAFA;*!*/
/*}*/
}
//修改颜色

View File

@@ -0,0 +1,77 @@
<template>
<Dialog
:title="$t('common.MFAVerify')"
:width="'50'"
:show-confirm="false"
:show-cancel="false"
:visible.sync="visible"
:destroy-on-close="true"
v-bind="$attrs"
v-on="$listeners"
>
<el-row :gutter="20">
<el-col :span="4">
<div style="line-height: 34px;text-align: center">MFA</div>
</el-col>
<el-col :span="14">
<el-input v-model="MFAToken" />
<span class="help-tips help-block">{{ $t('common.MFARequireForSecurity') }}</span>
</el-col>
<el-col :span="4">
<el-button size="mini" type="primary" style="line-height:20px " @click="verifyMFA">
{{ this.$t('common.Confirm') }}
</el-button>
</el-col>
</el-row>
</Dialog>
</template>
<script>
import Dialog from '@/components/Dialog'
export default {
name: 'MFAVerifyDialog',
components: {
Dialog
},
data() {
return {
MFAToken: '',
visible: false
}
},
watch: {
visible(val) {
if (!val) {
this.$emit('MFAVerifyCancel', true)
}
}
},
mounted() {
this.$axios.get('/api/v1/authentication/otp/verify/', { disableFlashErrorMsg: true }).then(() => {
this.$emit('MFAVerifyDone', true)
}).catch(err => {
this.$log.debug('Verify otp code error: ', err)
this.visible = true
})
},
methods: {
verifyMFA() {
if (this.MFAToken.length !== 6) {
return this.$message.error(this.$tc('common.MFAErrorMsg'))
}
this.$axios.post(
`/api/v1/authentication/otp/verify/`, {
code: this.MFAToken
}
).then(res => {
this.$emit('MFAVerifyDone', true)
})
}
}
}
</script>
<style scoped>
</style>

View File

@@ -10,7 +10,7 @@
</template>
<script>
import Switcher from '../Swicher'
import Switcher from '../FormFields/Swicher'
export default {
name: 'ActionItem',
components: {

View File

@@ -1,5 +1,5 @@
<template>
<IBox fa="fa-edit" :title="title" v-bind="$attrs">
<IBox :fa="fa" :title="title" v-bind="$attrs">
<div v-for="action of actions" :key="action.title" class="quick-actions">
<table>
<ActionItem v-if="action.has === undefined || action.has" :action="action" />
@@ -20,6 +20,10 @@ export default {
ActionItem
},
props: {
fa: {
type: String,
default: () => 'fa-edit'
},
title: {
type: String,
default() {

View File

@@ -41,7 +41,7 @@
</template>
<script>
import Select2 from '../Select2'
import Select2 from '../FormFields/Select2'
import IBox from '../IBox'
import { createSourceIdCache } from '@/api/common'
import { mapGetters } from 'vuex'

View File

@@ -2,15 +2,11 @@
<el-card shadow="never">
<div slot="header" class="summary-header">
<span class="header-title">{{ title }}</span>
<span class="pull-right right-side">
<slot name="header-right">
<el-tag :type="rightSideLabel.type || 'success'" effect="dark" size="mini">{{ rightSideLabel.title }}</el-tag>
</slot>
</span>
</div>
<slot>
<h1 class="no-margins">
<router-link :to="body.route">
<span v-if="body.disabled" class="disabled-link">{{ body.count }}</span>
<router-link v-else :to="body.route">
<span>{{ body.count }}</span>
</router-link>
</h1>
@@ -74,4 +70,8 @@ export default {
.no-margins {
margin: 0 !important;
}
.disabled-link {
color: #428bca;
}
</style>

View File

@@ -1,16 +1,19 @@
<template>
<ActionsGroup :size="'mini'" :actions="actions" :more-actions="moreActions" :more-actions-title="moreActionsTitle" />
<ActionsGroup v-loading="loadingStatus" :size="'mini'" :actions="actions" :more-actions="moreActions" :more-actions-title="moreActionsTitle" />
</template>
<script>
import ActionsGroup from '@/components/ActionsGroup/index'
import ActionsGroup from '@/components/ActionsGroup'
import BaseFormatter from './base'
const defaultPerformDelete = function({ row, col }) {
const id = row.id
const url = `${this.url}${id}/`
return this.$axios.delete(url)
const url = new URL(this.url, location.origin)
url.pathname += `${id}/`
const deleteUrl = url.href
return this.$axios.delete(deleteUrl)
}
const defaultUpdateCallback = function({ row, col }) {
const id = row.id
let route = { params: { id: id }}
@@ -144,10 +147,12 @@ export default {
let actions = [...this.defaultActions, ...this.extraActions]
actions = _.cloneDeep(actions)
actions = actions.map((v) => {
v.has = this.cleanBoolean(v, 'has')
v.can = this.cleanBoolean(v, 'can')
v.callback = this.cleanCallback(v)
v.has = this.cleanBoolean(v, 'has', true)
v.can = this.cleanBoolean(v, 'can', true)
v.callback = this.cleanCallback(v, 'callback')
v.fa = this.cleanValue(v, 'fa')
v.order = v.order || 100
v.tip = this.cleanValue(v, 'tip')
return v
})
actions = actions.filter((v) => v.has)
@@ -165,18 +170,21 @@ export default {
return []
}
return this.cleanedActions.slice(1, this.cleanedActions.length)
},
loadingStatus() {
return this.col.formatterArgs.loading
}
},
methods: {
cleanBoolean(item, attr) {
cleanBoolean(item, attr, defaults) {
const ok = item[attr]
if (typeof ok !== 'function') {
return ok === undefined ? true : ok
return ok === undefined ? defaults : ok
}
return ok(this.row, this.cellValue)
return this.cleanValue(item, attr)
},
cleanCallback(item) {
const callback = item.callback
cleanCallback(item, attr) {
const callback = item[attr]
const attrs = {
reload: this.reload,
row: this.row,
@@ -185,6 +193,20 @@ export default {
tableData: this.tableData
}
return () => { return callback.bind(this)(attrs) }
},
cleanValue(item, attr) {
const value = item[attr]
if (!value || typeof value !== 'function') {
return value
}
const attrs = {
reload: this.reload,
row: this.row,
col: this.col,
cellValue: this.cellValue,
tableData: this.tableData
}
return value(attrs)
}
}
}

View File

@@ -1,7 +1,7 @@
<template>
<div>
<el-tooltip v-if="formatterArgs.hasTips" placement="bottom" effect="dark">
<div slot="content">{{ tipStatus }}<br>{{ tipTime }}</div>
<div slot="content" v-html="tips" />
<i :class="'fa ' + iconClass" />
</el-tooltip>
<i v-else :class="'fa ' + iconClass" />
@@ -10,7 +10,6 @@
<script>
import BaseFormatter from './base'
import { toSafeLocalDateStr } from '@/utils/common'
export default {
name: 'ChoicesFormatter',
extends: BaseFormatter,
@@ -23,21 +22,12 @@ export default {
true: 'fa-check text-primary',
false: 'fa-times text-danger'
},
typeChange(val) {
return !!val
getIconKey({ row, cellValue }) {
return cellValue
},
hasTips: false,
tipStatus(val, vm) {
if (!val) {
return vm.$t('assets.Unknown')
}
if (val.status === 0) {
return vm.$t('assets.Unreachable')
} else if (val.status === 1) {
return vm.$t('assets.Reachable')
} else if (val.status === 2) {
return vm.$t('assets.Unknown')
}
getTips: ({ row, cellValue }) => {
return cellValue
}
}
}
@@ -50,18 +40,11 @@ export default {
},
computed: {
iconClass() {
const key = this.formatterArgs.typeChange(this.cellValue)
const key = this.formatterArgs.getIconKey({ row: this.row, cellValue: this.cellValue })
return this.formatterArgs.iconChoices[key]
},
tipStatus() {
const vm = this
return this.formatterArgs.tipStatus(this.cellValue, vm)
},
tipTime() {
if (!this.cellValue) {
return ''
}
return toSafeLocalDateStr(this.cellValue.datetime)
tips() {
return this.formatterArgs.getTips({ cellValue: this.cellValue, row: this.row })
}
}
}

View File

@@ -20,7 +20,7 @@ export default {
defaultOnDelete(col, row, cellValue, reload) {
const url = col.deleteUrl + cellValue
this.$axios.delete(url).then(res => {
this.$message.success(this.$t('common.deleteSuccessMsg'))
this.$message.success(this.$tc('common.deleteSuccessMsg'))
reload()
}).catch(error => {
this.$message.error(this.$t('common.deleteErrorMsg') + ' ' + error)

View File

@@ -25,8 +25,16 @@ export default {
data() {
const formatterArgs = Object.assign(this.formatterArgsDefault, this.col.formatterArgs)
return {
formatterArgs: formatterArgs,
iTitle: formatterArgs.getTitle({ col: this.col, row: this.row, cellValue: this.cellValue })
formatterArgs: formatterArgs
}
},
computed: {
iTitle() {
return this.formatterArgs.getTitle({
col: this.col,
row: this.row,
cellValue: this.cellValue
})
}
},
methods: {

View File

@@ -7,9 +7,8 @@ import ActionsFormatter from './ActionsFormatter'
import DeleteActionFormatter from './DeleteActionFormatter'
import DateFormatter from './DateFormatter'
import SystemUserFormatter from './GrantedSystemUsersShowFormatter'
import ShowKeyFormatter from '@/components/ListTable/formatters/ShowKeyFormatter'
import ShowKeyFormatter from '@/components/TableFormatters/ShowKeyFormatter'
import DialogDetailFormatter from './DialogDetailFormatter'
import LoadingActionsFormatter from './LoadingActionsFormatter'
import EditableInputFormatter from './EditableInputFormatter'
import StatusFormatter from './StatusFormatter'
@@ -24,7 +23,6 @@ export default {
SystemUserFormatter,
ShowKeyFormatter,
DialogDetailFormatter,
LoadingActionsFormatter,
ArrayFormatter,
EditableInputFormatter,
StatusFormatter
@@ -41,7 +39,6 @@ export {
SystemUserFormatter,
ShowKeyFormatter,
DialogDetailFormatter,
LoadingActionsFormatter,
ArrayFormatter,
EditableInputFormatter,
StatusFormatter

View File

@@ -77,9 +77,9 @@ export default {
}
},
methods: {
handleUrlChange(_url) {
this.$set(this.iTableConfig, 'url', _url)
this.$emit('urlChange', _url)
handleUrlChange(url) {
this.$set(this.iTableConfig, 'url', url)
this.$emit('urlChange', url)
this.forceRerender()
},
forceRerender() {

View File

@@ -12,14 +12,17 @@ export { default as FormGroupHeader } from './FormGroupHeader'
export { default as Hamburger } from './Hamburger'
export { default as ListTable } from './ListTable'
export { default as RelationCard } from './RelationCard'
export { default as Select2 } from './Select2'
export { default as Select2 } from './FormFields/Select2'
export { default as UploadKey } from './FormFields/UploadKey.vue'
export { default as AssetSelect } from './AssetSelect'
export { default as SvgIcon } from './SvgIcon'
export { default as TreeTable } from './TreeTable'
export { default as IBox } from './IBox'
export { default as QuickActions } from './QuickActions'
export { default as Switcher } from './Swicher'
export { default as Switcher } from './FormFields/Swicher'
export { default as SummaryCard } from './SummaryCard'
export { default as UploadField } from './UploadField'
export { default as AssetUserTable } from './AssetUserTable'
export { default as UploadField } from './FormFields/UploadField'
export { default as AccountListTable } from './AccountListTable/index'
export { default as AppAccountListTable } from './AppAccountListTable'
export { default as AssetRelationCard } from './AssetRelationCard'
export { default as MFAVerifyDialog } from './MFAVerifyDialog'

View File

@@ -1,5 +1,10 @@
{
"": "",
"accounts": {
"PleaseClickLeftAssetToViewAssetAccount": "资产账号列表,点击左侧资产进行查看",
"PleaseClickLeftApplicationToViewApplicationAccount": "应用账号列表,点击左侧应用进行查看",
"PleaseClickLeftAssetToViewGatheredUser": "收集用户列表,点击左侧资产进行查看"
},
"acl": {
"name": "名称",
"username": "用户名",
@@ -24,9 +29,12 @@
},
"applications": {
"": "",
"updateAccountMsg": "请更新系统用户的账号信息",
"associateApplication": "关联应用",
"RemoteApp": "远程应用",
"Database": "数据库",
"Cloud": "云",
"App": "应用",
"applicationsType": {
"chrome": "Chrome",
"mysql_workbench": "MySQL Workbench",
@@ -78,11 +86,15 @@
"DBInfo": "数据库信息"
},
"assets": {
"AppList": "应用列表",
"AssociateSystemUsers": "关联系统用户",
"AssociateAssets": "关联资产",
"AssociateNodes": "关联节点",
"Action": "动作",
"ActiveSelected": "激活所选",
"AdminUser": "管理用户",
"AdminUser": "特权用户",
"AdminUserDetail": "管理用户详情",
"AdminUserListHelpMessage": "管理用户是资产(被控服务器)上的 root,或拥有 NOPASSWD: ALL sudo 权限的用户 JumpServer 使用该用户来 `推送系统用户`、`获取资产硬件信息` 等。\n",
"AdminUserListHelpMessage": "<b>特权用户</b> 是资产已存在的, 并且拥有 高级权限 的系统用户, 如 root拥有 `NOPASSWD: ALL` sudo 权限的用户 JumpServer 使用该用户来 `推送系统用户`、`获取资产硬件信息` 等。",
"Asset": "资产",
"HardwareInfo": "硬件信息",
"AssetDetail": "资产详情",
@@ -93,11 +105,11 @@
"TestGatewayHelpMessage": "如果使用了nat端口映射请设置为ssh真实监听的端口",
"SshPort": "SSH 端口",
"AssetNumber": "资产编号",
"AssetUserList": "资产用户列表",
"AssetUserList": "账号列表",
"Assets": "资产",
"Auth": "认证",
"AccountList": "账号列表",
"AutoGenerateKey": "自动生成密钥",
"AutoGenerateKey": "自动生成",
"AutoPush": "自动推送",
"BasePlatform": "基础平台",
"Basic": "基本",
@@ -110,6 +122,7 @@
"CommandFilterRules": "命令过滤器规则",
"Comment": "备注",
"Cpu": "CPU",
"CommonUser": "普通用户",
"CreatedBy": "创建者",
"Database": "数据库",
"DateJoined": "创建日期",
@@ -151,9 +164,11 @@
"PriorityHelpMessage": "1-100, 1最低优先级100最高优先级。授权多个用户时高优先级的系统用户将会作为默认登录用户",
"Protocol": "协议",
"Protocols": "协议组",
"LoginOption": "登录选项",
"PublicIp": "公网IP",
"Push": "推送",
"PushSystemUserNow": "推送系统用户",
"PushAllSystemUsersToAsset": "推送所有系统用户到资产",
"QuickUpdate": "快速更新",
"Reachable": "可连接",
"Unreachable": "不可连接",
@@ -173,15 +188,17 @@
"PasswordHelpMessage": "密码或密钥密码",
"SystemUser": "系统用户",
"SystemUserDetail": "系统用户详情",
"SystemUserListHelpMessage": "系统用户是 JumpServer 跳转登录资产时使用的用户,可以理解为登录资产用户,如 websadba`ssh web@some-host`,而不是使用某个用户的用户名跳转登录服务器(`ssh xiaoming@some-host` 简单来说是用户使用自己的用户名登录 JumpServerJumpServer 使用系统用户登录资产。 系统用户创建时,如果选择了自动推送,JumpServer 使用 Ansible 自动推送系统用户到资产中,如果资产(交换机)不支持 Ansible请手动填写账号密码。\n",
"SystemUserListHelpMessage": "<b>系统用户</b> 是JumpServer 登录资产时使用的账号,如 root `ssh root@host`,而不是使用该用户名登录资产ssh admin@host)`;<br><b>特权用户</b> 是资产已存在的, 并且拥有 高级权限 的系统用户, JumpServer 使用该用户来 `推送系统用户`、`获取资产硬件信息` 等;</br><b>普通用户</b> 可以在资产上预先存在,也可以由 特权用户 来自动创建。",
"DynamicUsername": "动态用户名",
"SystemUsers": "系统用户",
"Test": "测试",
"TestAssetsConnective": "测试资产可连接性",
"TestAllSystemUsersConnective": "测试所有系统用户可连接性",
"TestConnection": "测试连接",
"Type": "类型",
"UnselectedAssets": "未选择资产或所选择的资产不支持SSH协议连接",
"UnselectedNodes": "未选择节点",
"UpdateAssetUserToken": "更新资产用户认证信息",
"UpdateAssetUserToken": "更新账号认证信息",
"Username": "用户名",
"UsernameHelpMessage": "用户名是动态的,登录资产时使用当前用户的用户名登录",
"Value": "值",
@@ -192,6 +209,7 @@
"sshKeyFingerprint": "SSH 指纹",
"ip": "IP",
"sshkey": "sshkey",
"SSHKey": "SSH 密钥",
"GroupsHelpMessage": "请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)",
"HomeHelpMessage": "默认家目录 /home/系统用户名: /home/username",
"Home": "家目录",
@@ -199,20 +217,31 @@
"ipDomain": "IP(域名)",
"HostProtocol": "主机协议",
"DatabaseProtocol": "数据库协议",
"OtherProtocol": "其它协议"
"OtherProtocol": "其它协议",
"PasswordOrToken": "密码 / 令牌"
},
"audits": {
"Hosts": "主机",
"RunUser": "运行用户",
"User": "用户",
"View": "查看"
"Username": "用户名",
"View": "查看",
"SystemUserName": "系统用户名"
},
"auth": {
"LoginRequiredMsg": "账号已退出,请重新登录",
"ReLogin": "重新登录"
},
"common": {
"Logging": "日志记录",
"Database": "数据库记录",
"Params": "参数",
"BasicInfo": "基本信息",
"DateUpdated": "更新日期",
"ApprovaLevel": "审批信息",
"MFAVerify": "验证 MFA",
"ViewSecret": "查看密码",
"ConnectWebSocketError": "连接 WebSocket 失败",
"Action": "动作",
"RequestTickets": "申请工单",
"Actions": "操作",
@@ -227,13 +256,21 @@
"UpdateAssetDetail": "配置更多信息",
"AddSuccessMsg": "添加成功",
"Auth": "认证",
"bind": "绑定",
"unbind": "解绑",
"PushSelected":"推送所选",
"PushSelectedSystemUsersToAsset": "推送所选系统用户到资产",
"TestSelected": "测试所选",
"TestSelectedSystemUsersConnective": "测试所选系统用户可连接性",
"BadRequestErrorMsg": "请求错误,请检查填写内容",
"BadRoleErrorMsg": "请求错误,无该操作权限",
"BadConflictErrorMsg": "正在刷新中,请稍后再试",
"Basic": "基本",
"PleaseAgreeToTheTerms": "请同意条款",
"BasicInfo": "基本信息",
"OpenId": "OpenId设置",
"Radius": "Radius设置",
"Cas": "Cas设置",
"other": "其它设置",
"ApplyInfo": "申请信息",
"Cancel": "取消",
"Close": "关闭",
@@ -266,6 +303,8 @@
"Info": "提示",
"MFAConfirm": "MFA 认证",
"MFARequireForSecurity": "为了安全请输入MFA",
"PasswordConfirm": "密码认证",
"PasswordRequireForSecurity": "为了安全请输入密码",
"Members": "成员",
"More": "更多",
"Message": "消息",
@@ -299,6 +338,7 @@
"View": "查看",
"Yes": "是",
"action": "动作",
"User": "用户",
"activateSelected": "激活所选",
"bulkDeleteErrorMsg": "批量删除失败: ",
"bulkDeleteSuccessMsg": "批量删除成功",
@@ -330,6 +370,7 @@
"Pending": "等待",
"Status": "状态",
"InputEmailAddress": "请输入正确的邮箱地址",
"Receivers": "接收人",
"imExport": {
"ExportAll": "导出所有",
"ExportOnlyFiltered": "仅导出搜索结果",
@@ -380,7 +421,8 @@
"SPECIAL_CHAR_REQUIRED": "须包含特殊字符",
"MIN_LENGTH_ERROR": "密码最小长度 {0} 位"
},
"lastCannotBeDeleteMsg": "最后一项,不能被删除"
"lastCannotBeDeleteMsg": "最后一项,不能被删除",
"InvalidJson": "不是合法 JSON"
},
"dashboard": {
"ActiveAsset": "近期被登录过",
@@ -494,6 +536,7 @@
"downloadFile": "下载文件",
"hostName": "主机名",
"isValid": "有效",
"fromTicket": "来自工单",
"isEffective": "起作用的",
"nodeCount": "节点数量",
"refreshFail": "刷新失败",
@@ -508,7 +551,7 @@
"systemUserCount": "系统用户数量",
"upDownload": "上传下载",
"uploadFile": "上传文件",
"clipboardCopyPaste":"复制粘贴",
"clipboardCopyPaste":"剪贴板复制粘贴",
"clipboardCopy":"剪切板复制",
"clipboardPaste":"剪切板粘贴",
"userCount": "用户数量",
@@ -517,7 +560,15 @@
},
"route": {
"": "",
"TicketFlow": "工单流",
"TicketFlowCreate": "创建审批流",
"TicketFlowUpdate": "更新审批流",
"Accounts": "账号管理",
"AssetAccount": "资产账号",
"ApplicationAccount": "应用账号",
"Ticket":"工单",
"SessionDetail": "会话详情",
"CommandConfirm": "命令复核",
"AdminUserCreate": "创建管理用户",
"AdminUserDetail": "管理用户详情",
"AdminUserList": "管理用户",
@@ -526,6 +577,7 @@
"AssetCreate": "创建资产",
"AssetDetail": "资产详情",
"AssetList": "资产列表",
"Session": "会话",
"AssetPermission": "资产授权",
"AssetPermissionCreate": "创建资产授权规则",
"AssetPermissionDetail": "资产授权详情",
@@ -610,7 +662,7 @@
"ApplicationPermissionUpdate": "更新应用授权规则",
"RemoteAppUpdate": "更新远程应用",
"ReplayStorageUpdate": "更新录像存储",
"SessionDetail": "会话详情",
"Detail": "详情",
"SessionOffline": "历史会话",
"SessionOnline": "在线会话",
"Sessions": "会话管理",
@@ -619,6 +671,7 @@
"SystemUserDetail": "系统用户详情",
"SystemUserList": "系统用户",
"SystemUserUpdate": "更新系统用户",
"AssetUserList": "资产用户",
"TaskDetail": "任务详情",
"TaskList": "任务列表",
"TaskMonitor": "任务监控",
@@ -626,6 +679,10 @@
"TicketDetail": "工单详情",
"TicketCreate": "创建工单",
"Tickets": "工单管理",
"Templates": "模版管理",
"TemplateDetail": "模版详情",
"TemplateCreate": "创建模版",
"TemplateUpdate": "更新模版",
"UserCreate": "创建用户",
"UserDetail": "用户详情",
"UserFirstLogin": "首次登录",
@@ -639,9 +696,15 @@
"UserUpdate": "更新用户",
"Users": "用户管理",
"WebFTP": "文件管理",
"WebTerminal": "Web终端"
"WebTerminal": "Web终端",
"Notifications": "通知",
"SiteMessageList": "站内信"
},
"sessions": {
"SetToDefaultStorage": "设置为默认存储",
"SetToDefault": "设为默认",
"SetSuccess": "设置成功",
"SetFailed": "设置失败",
"StorageConfiguration": "存储配置",
"accountKey": "账户密钥",
"accountName": "账户名称",
@@ -651,6 +714,7 @@
"target": "目标",
"bucket": "桶名称",
"command": "命令",
"Activity": "活动",
"commandStorage": "命令存储",
"comment": "备注",
"containerName": "容器名称",
@@ -702,6 +766,7 @@
"common": "普通"
},
"Monitor": "监控",
"XRDPNotSupport": "RDP 客户端会话, 暂不支持监控",
"sessionMonitor": "监控",
"TerminateTaskSendSuccessMsg": "终断任务已下发,请稍后刷新查看",
"helpText": {
@@ -713,6 +778,31 @@
}
},
"setting": {
"AlibabaCloud": "阿里云",
"TencentCloud": "腾讯云",
"Radius": "Radius",
"VerifySignTmpl": "验证码短信模板",
"Enable": "启用",
"AuthLimit": "登录限制",
"SMTP": "邮件服务器",
"Perm": "授权",
"Setting": "设置",
"Config": "配置",
"AuthMethod": "认证方式",
"SSO": "单点认证",
"AppAuth": "App认证",
"OtherAuth": "其它认证",
"Ops": "任务",
"OTP": "OTP(虚拟MFA)",
"JMSSSO": "SSO Token 登录",
"MessageSub": "消息订阅",
"Cleaning": "定期清理",
"Other": "其它设置",
"CASSetting": "CAS 配置",
"Auth": "认证设置",
"SyncSetting": "同步设置",
"Advanced": "高级设置",
"InsecureCommandNotifyToSubscription": "危险命令通知已升级到消息订阅中,支持更多通知方式",
"ApiKeyList": "API Key 列表",
"AssetCount": "资产数量",
"Basic": "基本设置",
@@ -720,17 +810,20 @@
"Create": "创建",
"Edition": "版本",
"Email": "邮件设置",
"EmailContent": "邮件内容设置",
"EmailContent": "邮件内容定制",
"Expired": "过期时间",
"Hostname": "主机名",
"ImportLicense": "导入许可证",
"ImportLicenseTip": "请导入许可证",
"Ldap": "LDAP设置",
"Ldap": "LDAP",
"License": "许可证",
"Senior": "高级",
"LicenseFile": "许可证文件",
"PasswordCheckRule": "密码校验规则",
"PasswordCheckRule": "密码复杂度校验",
"Security": "安全设置",
"SecuritySetting": "安全设置",
"SystemMessageSubscription": "系统消息订阅",
"insecureCommandEmailUpdate": "点我设置",
"SubscriptionID": "订阅授权ID",
"Terminal": "终端设置",
"all": "全部",
@@ -740,13 +833,15 @@
"authLdapSearchFilter": "用户过滤器",
"authLdapSearchOu": "用户OU",
"authLdapServerUri": "LDAP地址",
"authLdapUserAttrMap": "LDAP属性映射",
"authLdapUserAttrMap": "用户属性映射",
"authCASAttrMap": "用户属性映射",
"SignaturesAndTemplates": "Signatures and Templates",
"unselectedUser": "没有选择用户",
"auto": "自动",
"basicSetting": "基本设置",
"communityEdition": "社区版",
"consult": "咨询",
"createUserSetting": "创建用户设置",
"CreateUserSetting": "创建用户内容",
"emailCustomUserCreatedBody": "创建用户邮件的内容",
"emailCustomUserCreatedHonorific": "创建用户邮件的敬语",
"emailCustomUserCreatedSignature": "署名",
@@ -809,7 +904,7 @@
"import": "导入",
"importLdapUserTip": "请先提交LDAP配置再进行导入",
"importLdapUserTitle": "LDAP 用户列表",
"ldapBulkImport": "一键导入",
"ldapBulkImport": "用户导入",
"ldapConnectTest": "测试连接",
"ldapLoginTest": "测试登录",
"password": "密码",
@@ -844,12 +939,29 @@
"refreshLdapCache":"刷新Ldap缓存请稍后",
"LicenseExpired": "许可证已经过期",
"LicenseWillBe": "许可证即将在 ",
"Expire": " 过期"
},
"settings": {
"setting": "设置"
"Expire": " 过期",
"WeCom": "企业微信",
"DingTalk": "钉钉",
"dingTalkTest": "测试",
"weComTest": "测试",
"FeiShu": "飞书",
"SMS": "短信设置",
"feiShuTest": "测试",
"setting": "设置",
"SMSProvider": "短信服务商"
},
"tickets": {
"OneAssigneeType": "一级受理人类型",
"OneAssignee": "一级受理人",
"TwoAssigneeType": "二级受理人类型",
"TwoAssignee": "二级受理人",
"SuperAdmin": "超级管理员",
"OrgAdmin": "组织管理员",
"SuperOrgAdmin": "超级管理员+组织管理员",
"CustomUser": "自定义用户",
"ApprovalLevel": "审批级别",
"FlowDetail": "流程详情",
"PermissionName": "授权规则名称",
"Accept": "同意",
"AssignedMe": "待我审批",
"Assignee": "处理人",
@@ -866,12 +978,11 @@
"status": "状态",
"title": "标题",
"action": "动作",
"IPGroup": "IP 组",
"type": "类型",
"user": "用户",
"Status": "状态",
"Open": "待处理",
"OrgName":"组织名称",
"OrgName":"授权组织名称",
"AssignedInfo":"审批信息",
"OpenTicket": "创建工单",
"HandleTicket": "处理工单",
@@ -887,11 +998,18 @@
"Approved": "已同意",
"Rejected": "已拒绝",
"Closed": "已完成",
"StateClosed": "已关闭",
"helpText": {
"ips": "请输入逗号分割的IP地址组",
"fuzzySearch": "支持模糊搜索",
"application": "请输入逗号分割的应用名称组"
}
},
"ApplyRunUser": "申请运行的用户",
"ApplyRunAsset": "申请运行的资产",
"ApplyRunSystemUser": "申请运行的系统用户",
"ApplyRunCommand": "申请运行的命令",
"ApplyFromSession": "会话",
"ApplyFromCMDFilterRule": "命令过滤规则"
},
"tree": {
"AddAssetToNode": "添加资产到节点",
@@ -907,6 +1025,9 @@
"UpdateNodeAssetHardwareInfo": "更新节点资产硬件信息"
},
"users": {
"MessageSubscription": "消息订阅",
"AuthSettings": "认证配置",
"UserName": "姓名",
"Account": "账户",
"Authentication": "认证",
"Comment": "备注",
@@ -918,6 +1039,10 @@
"DatePasswordUpdated": "密码更新日期",
"DescribeOfGuide": "欢迎使用JumpServer堡垒机系统获取更多信息请点击",
"Email": "邮件",
"Phone": "手机号",
"WeCom": "企业微信",
"DingTalk": "钉钉",
"FeiShu": "飞书",
"FingerPrint": "指纹",
"FirstLogin": "首次登录",
"OrgUser": "组织用户",
@@ -927,6 +1052,9 @@
"Invite": "邀请",
"InviteUserInOrg": "邀请用户加入此组织",
"Guide": "向导",
"setWeCom": "设置企业微信认证",
"setDingTalk": "设置钉钉认证",
"setFeiShu": "设置飞书认证",
"HelpText": {
"MFAOfUserFirstLoginPersonalInformationImprovementPage": "启用多因子认证,使账号更加安全。<br/> 启用之后您将会在下次登录时进入多因子认证绑定流程;您也可以在(个人信息->快速修改->更改多因子设置)中直接绑定!",
"MFAOfUserFirstLoginUserGuidePage": "为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:设置复杂密码,并启用多因子认证)",
@@ -969,12 +1097,19 @@
"resetMFAWarningMsg": "你确定要重置用户的 MFA 吗?",
"resetMFAdSuccessMsg": "重置MFA成功, 用户可以重新设置MFA了",
"resetPassword": "重置密码",
"resetPasswordSuccessMsg": "发送邮件任务已提交, 用户稍后会收到重置密码邮件",
"resetPasswordSuccessMsg": "已向用户发送重置密码消息",
"resetPasswordWarningMsg": "你确定要发送重置用户密码的邮件吗",
"resetSSHKey": "重置SSH密钥",
"resetSSHKeySuccessMsg": "发送邮件任务已提交, 用户稍后会收到重置密钥邮件",
"resetSSHKeyWarningMsg": "你确定要发送重置用户的SSH Key的邮件吗?",
"resetWechat": "解绑企业微信",
"resetWechatLoginWarningMsg": "你确定要解绑用户的 企业微信 吗?",
"resetWechatLoginSuccessMsg": "重置成功, 用户可以重新绑定企业微信了",
"resetDingTalk": "解绑钉钉",
"resetDingTalkLoginWarningMsg": "你确定要解绑用户的 钉钉 吗?",
"resetDingTalkLoginSuccessMsg": "重置成功, 用户可以重新绑定钉钉了",
"send": "发送",
"unbind": "解绑",
"unblock": "解锁",
"unblockSuccessMsg": "解锁成功",
"unblockUser": "解锁用户"
@@ -993,26 +1128,56 @@
"remoteAppPermissionRules": "远程应用授权规则"
},
"dateLastLogin": "最后登录日期",
"needUpdatePasswordNextLogin": "下次登录须修改密码",
"UpdatePassword": "更新密码",
"SetPublicKey": "设置SSH公钥",
"passwordExpired": "密码过期了",
"passwordWillExpiredPrefixMsg": "密码即将在 ",
"passwordWillExpiredSuffixMsg": "天 后过期,请尽快修改您的密码。"
},
"notifications": {
"MessageType": "消息类型",
"Receivers": "接收人",
"Subscription": "消息订阅",
"ChangeReceiver": "修改消息接收人",
"Subject": "主题",
"Message": "消息",
"DeliveryTime": "发送时间",
"HasRead": "是否已读",
"Sender": "发送人",
"MarkAsRead": "标记已读",
"NoUnreadMsg": "暂无未读消息",
"SiteMessage": "站内信",
"SMS": "短信"
},
"xpack": {
"Admin": "管理员",
"Asset": "资产",
"Database": "数据库",
"AssetCount": "资产数量",
"Auditor": "审计员",
"ChangeAuthPlan": {
"AddAsset": "添加资产",
"AddNode": "添加节点",
"AddSystemUser": "添加系统用户",
"Asset": "资产",
"Database": "数据库",
"DatabaseId": "数据库Id",
"AppAmount": "应用数量",
"SystemUserAmount": "系统用户数量",
"SystemUser": "系统用户",
"SystemUserId": "系统用户Id",
"AssetAmount": "资产数量",
"AssetAndNode": "资产和节点",
"ChangeAuthPlan": "改密计划",
"ChangeAuthPlanCreate": "创建改密计划",
"ChangeAuthPlanUpdate": "更新改密计划",
"AssetChangeAuthPlan": "资产改密计划",
"AppChangeAuthPlan": "应用改密计划",
"AssetChangeAuthPlanCreate": "创建资产改密计划",
"AppChangeAuthPlanCreate": "创建应用改密计划",
"AssetChangeAuthPlanUpdate": "更新资产改密计划",
"AppChangeAuthPlanUpdate": "更新应用改密计划",
"SymbolSet": "特殊符号集合",
"SymbolSetHelpText": "请输入此类型数据库支持的特殊符号集合,若生成的随机密码中有此类数据库不支持的特殊字符,改密计划将会失败",
"CyclePerform": "周期执行",
"DateJoined": "创建日期",
"DateStart": "开始日期",
@@ -1036,6 +1201,7 @@
"NodeAmount": "节点数量",
"PasswordLength": "密码长度",
"PasswordStrategy": "密码策略",
"SecretKeyStrategy": "密钥策略",
"RegularlyPerform": "定期执行",
"Result": "结果",
"Retry": "重试",
@@ -1046,8 +1212,13 @@
"Username": "用户名"
},
"Cloud": {
"ServerAccountKey": "服务账号密钥",
"IPNetworkSegment": "IP网段",
"Aliyun": "阿里云",
"Qcloud": "腾讯云",
"QingyunPrivatecloud": "青云私有云",
"HuaweiPrivatecloud": "华为私有云",
"GCP": "谷歌云",
"AWS_China": "AWS(中国)",
"AWS_Int": "AWS(国际)",
"HuaweiCloud": "华为云",
@@ -1075,7 +1246,7 @@
"Name": "名称",
"Account":"账户",
"Node": "节点",
"AdminUser":"管理用户",
"AdminUser":"特权用户",
"Periodic":"执行周期",
"PeriodicPerform":"定时执行",
"RegularlyPerform": "定期执行",
@@ -1096,6 +1267,9 @@
"Log": "日志",
"DeleteReleasedAssets": "删除已释放资产"
},
"Template": {
"Template": "模版管理"
},
"Corporation": "公司",
"Edition": "版本",
"Execute": "执行",
@@ -1105,7 +1279,9 @@
"GatherUserList": "收集用户",
"GatherUserTaskCreate": "创建任务",
"GatherUserTaskList": "任务列表",
"GatherUserTaskUpdate": "更新任务"
"GatherUserTaskUpdate": "更新任务",
"GatherUserTaskDetail": "任务详情",
"GatherUserTaskExecutionList": "任务执行列表"
},
"Import": "导入",
"ImportLicense": "导入许可证",
@@ -1135,7 +1311,7 @@
"users_amount": "用户数量",
"groups_amount": "用户组数量",
"assets_amount": "资产数量",
"admin_users_amount": "管理用户数量",
"admin_users_amount": "特权用户数量",
"system_users_amount": "系统用户数量",
"applications_amount": "应用数量",
"asset_perms_amount": "资产授权数量",

View File

@@ -1,5 +1,10 @@
{
"": "",
"accounts": {
"PleaseClickLeftAssetToViewAssetAccount": "Asset account list, please click on the assets on the left to view",
"PleaseClickLeftApplicationToViewApplicationAccount": "Application account list, please click on the application on the left to view",
"PleaseClickLeftAssetToViewGatheredUser": "Gathered user list, please click on the assets on the left to view"
},
"acl": {
"name": "Name",
"username": "Username",
@@ -23,9 +28,12 @@
},
"applications": {
"": "",
"updateAccountMsg": "Please update system user account info",
"associateApplication": "Associate application",
"RemoteApp": "Remote app",
"Database": "Database",
"Cloud": "Cloud",
"App": "Application",
"applicationsType": {
"chrome": "Chrome",
"mysql_workbench": "MySQL Workbench",
@@ -77,16 +85,22 @@
"DBInfo": "Database Info"
},
"assets": {
"AppList": "Application list",
"AssociateSystemUsers": "Associate system users",
"AssociateAssets": "Associate assets",
"AssociateNodes": "Associate nodes",
"Action": "Action",
"ActiveSelected": "Active selected",
"AdminUser": "Admin user",
"ReplaceNodeAssetsAdminUser":"Replace node assets admin user with this",
"AdminUserDetail": "Admin user detail",
"DynamicUsername": "Dynamic username",
"AdminUserListHelpMessage": "Admin users are asset (charged server) on the root, or have NOPASSWD: ALL sudo permissions users, JumpServer users of the system using the user to `push system user`, `get assets hardware information`, etc.\n",
"Asset": "Asset",
"HardwareInfo": "Hardware info",
"Hardware": "Hardware",
"AccountList": "Account list",
"LoginOption": "Login option",
"AssetDetail": "Asset detail",
"AssetList": "Asset list",
"AssetListHelpMessage": "The left side is the asset tree, right click to create, delete, and change the tree node, authorization asset is also organized as a node, and the right side is the asset under that node\n",
@@ -110,6 +124,7 @@
"CommandFilterRules": "Command filter rules",
"Comment": "Comment",
"Cpu": "Cpu",
"CommonUser": "Common user",
"CreatedBy": "Created by",
"Database": "Database",
"DateJoined": "Date joined",
@@ -153,6 +168,7 @@
"PublicIp": "Public ip",
"Push": "Push",
"PushSystemUserNow": "Push system user now",
"PushAllSystemUsersToAsset": "Push all system users to asset",
"QuickUpdate": "Quick update",
"Reachable": "Reachable",
"Unreachable": "Unreachable",
@@ -168,14 +184,15 @@
"Rules": "Rules",
"SFTPHelpMessage": "SFTP root dir, tmp, home or custom",
"SerialNumber": "Serial number",
"SudoHelpMessage": "Use comma split multi command, ex: /bin/whoami,/bin/ifconfig",
"SudoHelpMessage": "Use comma split multi command, ex: /bin/whoami, /bin/ifconfig",
"PasswordHelpMessage": "Password or private key password",
"SystemUser": "System user",
"SystemUserDetail": "System user detail",
"SystemUserListHelpMessage": "System user is JumpServer jump login assets used by the users, can be understood as the user login assets, such as web, sa, the dba (` ssh web@some-host `), rather than using a user the username login server jump (` ssh xiaoming@some-host `); In simple terms, users log into JumpServer using their own username, and JumpServer uses system users to log into assets. When system users are created, if you choose auto push JumpServer to use Ansible push system users into the asset, if the asset (Switch) does not support ansible, please manually fill in the account password.\n",
"SystemUserListHelpMessage": "<b>System user</b> is the account JumpServer used to log into the asset, such as using root `ssh root@host`, rather than the current user usernamessh admin@host)`;<br><b>Admin user</b> is the account that already exists on an asset, and have privileged permissions, JumpServer using this create common system user, and gather hardware Etc;</br><b>Common user</b> can pre-exist assets or created automatically by the admin user.",
"SystemUsers": "System users",
"Test": "Test",
"TestAssetsConnective": "Test assets connective",
"TestAllSystemUsersConnective": "Test all system users connective",
"TestConnection": "Test connection",
"Type": "Type",
"UnselectedAssets": "No asset selected or the selected asset does not support SSH protocol connection",
@@ -198,12 +215,15 @@
"ipDomain": "IP(Domain)",
"HostProtocol": "Host Protocol",
"DatabaseProtocol": "Database Protocol",
"Other Protocol": "Database Protocol"
"OtherProtocol": "Other Protocol",
"PasswordOrToken": "Password / Token"
},
"audits": {
"Hosts": "Host",
"RunUser": "Run user",
"User": "User",
"Username": "Username",
"SystemUserName": "System username",
"View": "View"
},
"auth": {
@@ -211,6 +231,12 @@
"ReLogin": "Re-Login"
},
"common": {
"Logging": "Logging",
"Database": "Database",
"Params": "Params",
"MFAVerify": "Verify MFA",
"ViewSecret": "View secret",
"ConnectWebSocketError": "Connect Websocket failed",
"Nothing": "Nothing",
"Action": "Action",
"CustomCol":"Custom table col",
@@ -225,6 +251,9 @@
"Add": "Add",
"PleaseAgreeToTheTerms": "Please agree to the terms",
"PushSelected":"Push selected",
"PushSelectedSystemUsersToAsset": "Push selected system users to asset",
"TestSelected": "Test selected",
"TestSelectedSystemUsersConnective": "Test selected system users connective",
"UpdateAssetDetail": "Update more detail",
"AddSuccessMsg": "Add success",
"Auth": "Authorization",
@@ -241,7 +270,7 @@
"Confirm": "Confirm",
"Create": "Create",
"CreatedBy": "Created by",
"CrontabHelpTips": "eg: Every Sunday 03:05 run <5 3 * * 0> <br>Tips:Using 5 digits linux crontab expressions<min hour day month week> (<a href='https://tool.lu/crontab/' target='_blank'>Online tools</a>) <br>Note:If both Regularly perform and Cycle perform are set,give priority to Regularly perform",
"CrontabHelpTips": "eg: Every Sunday 03:05 run <5 3 * * 0> <br>Tips:Using 5 digits linux crontab expressions<min hour day month week> (<a href='https://tool.lu/crontab/' target='_blank'>Online tools</a>) <br>Note:If both Regularly perform and Cycle perform are set, give priority to Regularly perform",
"DateEnd": "End date",
"Resource": "Resource",
"DateLast24Hours": "Last 24 hours",
@@ -265,6 +294,8 @@
"Info": "Info",
"MFAConfirm": "MFA Confirm",
"MFARequireForSecurity": "MFA required for security",
"PasswordConfirm": "Password Confirm",
"PasswordRequireForSecurity": "Password required for security",
"Members": "Members",
"More": "More",
"Message": "Message",
@@ -276,11 +307,12 @@
"Other": "Other",
"Others": "Others",
"Push": "Push",
"Receivers": "Receivers",
"QuickUpdate": "Quick update",
"RemoveSuccessMsg": "Remove success",
"Reset": "Reset",
"Search": "Search",
"MFAErrorMsg": "MFA Errorplease check",
"MFAErrorMsg": "MFA Error, please check",
"InputEmailAddress": "Please enter your email address",
"Select": "Select",
"SelectFile": "Select file",
@@ -291,6 +323,8 @@
"TestSuccessMsg": "Test Success",
"To": "To",
"Update": "Update",
"bind": "Bind",
"unbind": "Unbind",
"Upload": "Upload",
"Clone": "Clone",
"Username": "Username",
@@ -378,7 +412,8 @@
"SPECIAL_CHAR_REQUIRED": "Special char required",
"MIN_LENGTH_ERROR": "Password minimum length {}"
},
"lastCannotBeDeleteMsg": "The last one can't be delete"
"lastCannotBeDeleteMsg": "The last one can't be delete",
"InvalidJson": "Not a valid json format"
},
"dashboard": {
"ActiveAsset": "Asset active",
@@ -492,6 +527,7 @@
"downloadFile": "Download file",
"hostName": "Hostname",
"isValid": "Validity",
"fromTicket": "From ticket",
"isEffective": "Effective",
"nodeCount": "Node count",
"refreshFail": "Refresh fail",
@@ -515,29 +551,34 @@
},
"route": {
"": "",
"SessionDetail": "SessionDetail",
"Accounts": "Accounts",
"AssetAccount": "Asset account",
"ApplicationAccount": "Application account",
"Ticket": "Tickets",
"CommandConfirm": "Command confirm",
"AdminUserCreate": "Admin user create",
"AdminUserDetail": "Admin user detail",
"AdminUserList": "Admin users",
"AdminUserList": "Admin Users",
"AdminUserUpdate": "Admin user update",
"Applications": "Applications",
"AssetCreate": "Asset create",
"AssetDetail": "Asset detail",
"AssetList": "Assets",
"AssetPermission": "Asset permissions",
"AssetPermission": "Asset Permissions",
"AssetPermissionCreate": "Asset permissions create",
"AssetPermissionDetail": "Asset permissions detail",
"AssetPermissionUpdate": "Asset permissions update",
"AssetUpdate": "Asset update",
"Assets": "Assets",
"Audits": "Audits",
"BatchCommand": "Batch command",
"BatchCommandLog": "Batch command log",
"BatchCommand": "Batch Command",
"BatchCommandLog": "Batch Command Log",
"CeleryTaskLog": "Celery task log",
"CommandExecutions": "CommandExecutions ",
"CommandFilterCreate": "Command filter create",
"CommandFilterDetail": "Command filter detail",
"CommandFilterList": "Command filters",
"CommandFilterList": "Command Filters",
"CommandFilterRulesCreate": "Command filter rules create",
"CommandFilterRulesUpdate": "Command filter rules update",
"CommandFilterUpdate": "Command filter update",
@@ -546,7 +587,7 @@
"CreateCommandStorage": "Create command storage",
"CreateReplayStorage": "Create replay storage",
"Dashboard": "Dashboard",
"DatabaseApp": "Database apps",
"DatabaseApp": "Database Apps",
"DatabaseAppCreate": "Database app create",
"DatabaseAppDetail": "Database app detail",
"DatabaseAppPermission": "Databases permissions",
@@ -554,7 +595,7 @@
"DatabaseAppPermissionDetail": "Databases permissions detail",
"DatabaseAppPermissionUpdate": "Databases permissions update",
"DatabaseAppUpdate": "Database app update",
"KubernetesApp": "Kubernetes apps",
"KubernetesApp": "Kubernetes Apps",
"KubernetesAppCreate": "Kubernetes app create",
"KubernetesAppDetail": "Kubernetes app detail",
"KubernetesAppPermission": "Kubernetes permissions",
@@ -562,13 +603,13 @@
"KubernetesAppPermissionDetail": "Kubernetes permissions detail",
"KubernetesAppPermissionUpdate": "Kubernetes permissions update",
"KubernetesAppUpdate": "Kubernetes app update",
"Acl": "Access control",
"Acl": "Access Control",
"UserAclList": "User acl list",
"UserAclCreate": "User acl create",
"UserAclUpdate": "User acl update",
"UserAclLists": "User acl lists",
"UserAclDetail": "User acl detail",
"AssetAclList": "Asset acl list",
"AssetAclList": "Asset Acl",
"AssetAclCreate": "Asset acl create",
"AssetAclUpdate": "Asset acl update",
"AssetAclDetail": "Asset acl detail",
@@ -576,29 +617,29 @@
"DomainDetail": "Domain detail",
"DomainList": "Domains",
"DomainUpdate": "Domain update",
"FileManager": "File manager",
"FtpLog": "FTP logs",
"FileManager": "File Manager",
"FtpLog": "FTP Logs",
"GatewayCreate": "Gateway create",
"GatewayUpdate": "Gateway update",
"JobCenter": "Jobcenter",
"LabelCreate": "Label create",
"LabelList": "Labels",
"LabelUpdate": "Label update",
"LoginLog": "Login logs",
"MyApps": "My apps",
"MyAssets": "My assets",
"OperateLog": "Operation logs",
"PasswordChangeLog": "Password update logs",
"LoginLog": "Login Logs",
"MyApps": "My Apps",
"MyAssets": "My Assets",
"OperateLog": "Operation Logs",
"PasswordChangeLog": "Password Update Logs",
"Perms": "Permissions",
"PersonalInformationImprovement": "PersonalInformationImprovement",
"PlatformCreate": "Platform create",
"PlatformDetail": "Platform detail",
"PlatformList": "Platforms",
"PlatformUpdate": "Platform update",
"RemoteApp": "Remote apps",
"RemoteApp": "Remote Apps",
"RemoteAppDetail": "Remote app detail",
"RemoteAppPermission": "Remote apps permissions",
"ApplicationPermission": "Application permissions",
"ApplicationPermission": "Application Permissions",
"RemoteAppPermissionCreate": "Remote apps permission create",
"RemoteAppPermissionDetail": "Remote apps permissions detail",
"RemoteAppPermissionUpdate": "Remote app permission update",
@@ -608,18 +649,19 @@
"ApplicationPermissionUpdate": "Application permission update",
"RemoteAppUpdate": "Remote app update",
"ReplayStorageUpdate": "Replay storage update",
"SessionDetail": "Sessions detail",
"Detail": "Detail",
"Activity": "Activity",
"SessionOffline": "Sessions offline",
"SessionOnline": "Sessions online",
"Sessions": "Sessions",
"Settings": "Settings",
"SystemUserCreate": "System user create",
"SystemUserDetail": "System user detail",
"SystemUserList": "System users",
"SystemUserList": "System Users",
"SystemUserUpdate": "System user update",
"TaskDetail": "Tasks detail",
"TaskList": "Tasks",
"TaskMonitor": "Task monitor",
"TaskMonitor": "Task Monitor",
"Terminal": "Terminal",
"TicketDetail": "Ticket detail",
"TicketCreate": "Ticket create",
@@ -629,7 +671,7 @@
"UserFirstLogin": "UserFirstLogin",
"UserGroupCreate": "User group create",
"UserGroupDetail": "User group detail",
"UserGroupList": "User groups",
"UserGroupList": "User Groups",
"UserGroupUpdate": "User group update",
"UserGuide": "UserGuide",
"UserList": "Users",
@@ -637,9 +679,15 @@
"UserUpdate": "User update",
"Users": "Users",
"WebFTP": "WebFTP",
"WebTerminal": "Web terminal"
"WebTerminal": "Web Terminal",
"Notifications": "Notifications",
"SiteMessageList": "Site message"
},
"sessions": {
"SetToDefaultStorage": "Set to default storage",
"SetToDefault": "Set to default",
"SetSuccess": "Set success",
"SetFailed": "Set failed",
"StorageConfiguration": "Storage configuration",
"accountKey": "Account key",
"accountName": "Account name",
@@ -649,6 +697,7 @@
"target": "Target",
"bucket": "Bucket",
"command": "Command",
"Activity": "Activity",
"commandStorage": "Command storage",
"comment": "Comment",
"containerName": "Container name",
@@ -700,10 +749,11 @@
"common": "common"
},
"Monitor": "Monitor",
"XRDPNotSupport": "RDP Client session not support now",
"sessionMonitor": "Session Monitor",
"TerminateTaskSendSuccessMsg": "Terminate task has been send, Please check later",
"helpText": {
"esUrl": "Tip: If you have multiple hosts, use comma (,) to split (eg: http://www.jumpserver.a.com,http://www.jumpserver.b.com)",
"esUrl": "Tip: If you have multiple hosts, use comma (, ) to split (eg: http://www.jumpserver.a.com, http://www.jumpserver.b.com)",
"esIndex":"Es provides the default index: jumpserver",
"esDocType": "Es provides the default document type: command",
"s3Endpoint": "S3: http://s3.{REGION_NAME}.amazonaws.com<br>S3(China): http://s3.{REGION_NAME}.amazonaws.com.cn<br>Example: http://s3.cn-north-1.amazonaws.com.cn",
@@ -711,6 +761,29 @@
}
},
"setting": {
"SMSProvider": "SMS provider",
"SMS": "SMS setting",
"AlibabaCloud": "Alibaba cloud",
"TencentCloud": "Tencent cloud",
"VerifySignTmpl": "Verification code template",
"Radius": "Radius",
"Enable": "Enable",
"Perm": "Permission",
"SMTP": "SMTP server",
"Setting": "Setting",
"AuthMethod": "Auth methods",
"AuthLimit": "Auth limit",
"Ops": "Task",
"OTP": "OTP(MFA)",
"MessageSub": "Message subscription",
"Cleaning": "Period clean",
"Perms": "授权",
"CASSetting": "CAS setting",
"Other": "More...",
"Auth": "Auth",
"SyncSetting": "Sync setting",
"Advanced": "Advanced",
"InsecureCommandNotifyToSubscription": "Insecure command notification setting, change to system message subscription, support more notify method",
"ApiKeyList": "Api key list",
"AssetCount": "Asset count",
"Basic": "Basic setting",
@@ -730,6 +803,8 @@
"Security": "Security setting",
"SecuritySetting": "Security setting",
"SubscriptionID": "Subscription ID",
"SystemMessageSubscription": "System messages",
"insecureCommandEmailUpdate": "Setting",
"Terminal": "Terminal setting",
"all": "All",
"authLdap": "Enable LDAP auth",
@@ -739,12 +814,13 @@
"authLdapSearchOu": "User OU",
"authLdapServerUri": "LDAP server",
"authLdapUserAttrMap": "User attr map",
"authCASAttrMap": "User attr map",
"unselectedUser": "Unselected user",
"auto": "Auto",
"basicSetting": "Basic setting",
"communityEdition": "Community edition",
"consult": "Consult",
"createUserSetting": "Create User setting",
"CreateUserSetting": "Create User setting",
"emailCustomUserCreatedBody": "Create user email content",
"emailCustomUserCreatedHonorific": "Create user honorific",
"emailCustomUserCreatedSignature": "Signature",
@@ -767,7 +843,7 @@
"ApiKeyList": "The API key is used to sign the request header. The header of each request is different. Please refer to the usage documentation",
"authLdapSearchFilter": "Choice may be (cn|uid|sAMAccountName)=%(user)s)",
"authLdapSearchOu": "Use | split User OUs",
"authLdapUserAttrMap": "User attr map present how to map LDAP user attr to jumpserver, username,name,email is jumpserver attr",
"authLdapUserAttrMap": "User attr map present how to map LDAP user attr to jumpserver, username, name, email is jumpserver attr",
"emailCustomUserCreatedBody": "Tips:When creating a user, send the content of the email",
"emailCustomUserCreatedHonorific": "Tips: When creating a user, send the honorific of the email (eg:Hello)",
"emailCustomUserCreatedSignature": "Tips: Email signature (eg:jumpserver)",
@@ -839,12 +915,18 @@
"refreshLdapCache":"Refreshing Ldap cache ",
"LicenseExpired": "License expired",
"LicenseWillBe": "License will expire at ",
"Expire": ""
},
"settings": {
"Expire": "",
"WeCom": "WeCom",
"DingTalk": "DingTalk",
"dingTalkTest": "Test",
"weComTest": "Test",
"FeiShu": "FeiShu",
"feiShuTest": "Test",
"setting": "Setting"
},
"tickets": {
"PermissionName": "Permission name",
"Accept": "Accept",
"AssignedMe": "Assigned me",
"Assignee": "Assignee",
@@ -886,7 +968,13 @@
"ips": "Enter the IP address group, separated by commas",
"fuzzySearch": "Support for fuzzy search",
"application": "Enter the application group, separated by commas"
}
},
"ApplyRunUser": "Apply run user",
"ApplyRunAsset": "Apply run asset",
"ApplyRunSystemUser": "Apply run system user",
"ApplyRunCommand": "Apply run command",
"ApplyFromSession": "Session",
"ApplyFromCMDFilterRule": "Command filter rule"
},
"tree": {
"AddAssetToNode": "Add asset to node",
@@ -902,6 +990,9 @@
"UpdateNodeAssetHardwareInfo": "Update node asset hardware information"
},
"users": {
"MessageSubscription": "Message Subscription",
"AuthSettings": "Auth settings",
"UserName": "Name",
"Account": "Account",
"Existing":"Existing",
"Authentication": "Account",
@@ -911,9 +1002,16 @@
"DateJoined": "Date joined",
"DateLastLogin": "Date last login",
"DatePasswordLastUpdated": "Date password last updated",
"setWeCom": "Set wecom login",
"setDingTalk": "Set dingtalk login",
"setFeiShu": "Set feishu login",
"DatePasswordUpdated": "Date password updated",
"DescribeOfGuide": "Welcome to JumpServer. Click here for more information",
"Email": "Email",
"Phone": "Phone",
"WeCom": "WeCom",
"DingTalk": "DingTalk",
"FeiShu": "FeiShu",
"FingerPrint": "Fingerprint",
"FirstLogin": "First login",
"InviteUser": "Invite user",
@@ -965,12 +1063,16 @@
"resetMFAWarningMsg": "This will reset the user MFA setting, user can reset it",
"resetMFAdSuccessMsg": "Reset MFA success",
"resetPassword": "Reset password",
"resetPasswordSuccessMsg": "An e-mail has been sent to the user`s mailbox",
"resetPasswordSuccessMsg": "A password reset message has been sent to the user",
"resetPasswordWarningMsg": "This will reset the user password and send a reset mail",
"resetSSHKey": "Reset SSH key",
"resetSSHKeySuccessMsg": "An e-mail has been sent to the user`s mailbox",
"resetSSHKeyWarningMsg": "This will reset the user public key and send a reset mail",
"resetWechat": "Reset Wechat",
"resetWechatLoginWarningMsg": "This will reset the user Wechat setting, user can reset it",
"resetWechatLoginSuccessMsg": "Reset Wechat success",
"send": "Send",
"unbind": "Unbind",
"unblock": "Unblock",
"unblockSuccessMsg": "Account has unblocked",
"unblockUser": "Unblock login"
@@ -988,26 +1090,55 @@
"ApplicationPermissionRules": "Application permission rules",
"remoteAppPermissionRules": "Remote app permission rules"
},
"UpdatePassword": "",
"needUpdatePasswordNextLogin": "Update password next login",
"UpdatePassword": "Update password",
"SetPublicKey": "Set public key",
"UpdatePublicKey": "",
"passwordExpired": "Password expired",
"passwordWillExpiredPrefixMsg": "The password will expire in ",
"passwordWillExpiredSuffixMsg": " days.Please change your password as soon as possible."
},
"notifications": {
"MessageType": "Message Type",
"Receivers": "Receivers",
"Subscription": "Subscription",
"ChangeReceiver": "Change Receivers",
"Subject": "Subject",
"Message": "Message",
"DeliveryTime": "Delivery time",
"HasRead": "Has read",
"Sender": "Sender",
"MarkAsRead": "Mark as read",
"NoUnreadMsg": "No unread messages",
"SiteMessage": "Site messages",
"SMS": "SMS"
},
"xpack": {
"Admin": "Admin",
"Asset": "Asset",
"Database": "Database",
"AssetCount": "Asset count",
"Auditor": "Auditor",
"ChangeAuthPlan": {
"AddAsset": "Add asset",
"AddNode": "Add node",
"AddSystemUser": "Add systemuser",
"Asset": "Asset",
"Database": "Database",
"DatabaseId": "Database Id",
"SystemUser": "SystemUser",
"SystemUserId": "SystemUser Id",
"AssetAmount": "Asset",
"AssetAndNode": "Asset and Node",
"ChangeAuthPlan": "Change auth plan",
"ChangeAuthPlanCreate": "Create change auth plan",
"ChangeAuthPlanUpdate": "Update change auth plan",
"ChangeAuthPlan": "Change Auth Plan",
"AssetChangeAuthPlan": "Asset Change Auth Plan",
"AppChangeAuthPlan": "App Change Auth Plan",
"AssetChangeAuthPlanCreate": "Create Asset change auth plan",
"AppChangeAuthPlanCreate": "Create App change auth plan",
"AssetChangeAuthPlanUpdate": "Update Asset change auth plan",
"AppChangeAuthPlanUpdate": "Update App change auth plan",
"SymbolSet": "Special symbol set",
"SymbolSetHelpText": "Please enter the special symbol set supported by this type of database. If there are special characters in the generated random password that are not supported by this type of database, the password change plan will fail",
"CyclePerform": "Cycle perform",
"DateJoined": "Date joined",
"DateStart": "Date start",
@@ -1041,8 +1172,13 @@
"Username": "Username"
},
"Cloud": {
"ServerAccountKey": "Server Account Key",
"IPNetworkSegment": "Ip Network Segment",
"Aliyun": "Ali Cloud",
"Qcloud": "Tencent Cloud",
"QingyunPrivatecloud": "Qingyun Private Cloud",
"HuaweiPrivatecloud": "Huawei Private Cloud",
"GCP": "Google Cloud Platform",
"AWS_China": "AWS(China)",
"AWS_Int": "AWS(International)",
"HuaweiCloud": "Huawei Cloud",
@@ -1055,7 +1191,7 @@
"AccountUpdate": "Update account",
"AccountDetail": "Account detail",
"Cloud": "Cloud center",
"CloudCenter": "Cloud center",
"CloudCenter": "Cloud Center",
"Provider": "Provider",
"Validity": "Validity",
"IsAlwaysUpdateHelpTips": "Whether the asset information, including Hostname, IP, Platform, and AdminUser, is updated synchronously each time a synchronization task is performed",
@@ -1096,16 +1232,18 @@
"Execute": "Execute",
"Expired": "Expired",
"GatherUser": {
"GatherUser": "Gather user",
"GatherUser": "Gather User",
"GatherUserList": "Gather user",
"GatherUserTaskCreate": "Create gather user task",
"GatherUserTaskList": "Gather user task list",
"GatherUserTaskUpdate": "Update gather user task"
"GatherUserTaskUpdate": "Update gather user task",
"GatherUserTaskDetail": "Gather user detail",
"GatherUserTaskExecutionList": "Gather user task execution list"
},
"Import": "Import",
"ImportLicense": "Import license",
"ImportLicenseTip": "Please Import License",
"InterfaceSettings": "Interface setting",
"InterfaceSettings": "Interface Setting",
"License": "License",
"LicenseDetail": "License detail",
"SystemMonitor": "System Monitor",
@@ -1121,7 +1259,7 @@
"Organization": {
"OrganizationCreate": "Create organization",
"OrganizationDetail": "Org detail",
"OrganizationList": "Organlizations",
"OrganizationList": "Organizations",
"OrganizationUpdate": "Update org",
"OrganizationMembership": "Organization membership",
"DeleteOrgTitle":"Please ensure that the following information in the organization has been deleted",

View File

@@ -15,6 +15,7 @@
</template>
<script>
import AutoDataForm from '@/components/AutoDataForm'
import { getUpdateObjURL } from '@/utils/common'
export default {
name: 'GenericCreateUpdateForm',
components: {
@@ -111,7 +112,7 @@ export default {
}
},
// 获取提交的方法
getMethod: {
submitMethod: {
type: Function,
default: function() {
const params = this.$route.params
@@ -129,36 +130,31 @@ export default {
const params = this.$route.params
let url = this.url
if (params.id) {
url = `${url}${params.id}/`
url = getUpdateObjURL(url, params.id)
}
return url
}
},
onPerformSuccess: {
emitPerformSuccessMsg: {
type: Function,
default(res, method, vm, addContinue) {
default(method, res, addContinue) {
let msg = method === 'post' ? this.createSuccessMsg : this.updateSuccessMsg
if (addContinue) {
msg = this.saveSuccessContinueMsg
}
let msgLinkName = this.$t('common.Resource')
let msgLinkName = ''
if (res.name) {
msgLinkName = res.name
} else if (res.hostname) {
msgLinkName = res.hostname
}
const detailRoute = this.objectDetailRoute
detailRoute['params'] = { 'id': res.id }
const route = this.getNextRoute(res, method)
this.$emit('submitSuccess', res)
const h = this.$createElement
this.$log.debug('router is: ', detailRoute)
if (this.hasDetailInMsg) {
this.$message({
message: h('p', null, [
h('el-link', {
on: {
click: () => this.$router.push(detailRoute)
click: () => this.$router.push(this.objectDetailRoute)
},
style: { 'vertical-align': 'top' }
}, msgLinkName),
@@ -174,6 +170,18 @@ export default {
} else {
this.$message.success(msg)
}
}
},
onPerformSuccess: {
type: Function,
default(res, method, vm, addContinue) {
const route = this.getNextRoute(res, method)
if (!(route.params && route.params.id)) {
route['params'] = { 'id': res.id }
}
this.$emit('submitSuccess', res)
this.emitPerformSuccessMsg(method, res, addContinue)
if (!addContinue) {
setTimeout(() => this.$router.push(route), 100)
}
@@ -215,9 +223,10 @@ export default {
},
computed: {
method() {
return this.getMethod(this)
return this.submitMethod(this)
},
iUrl() {
// 更新或创建的url
return this.getUrl()
},
iHasSaveContinue() {
@@ -230,7 +239,7 @@ export default {
if (this.hasReset != null) {
return this.hasReset
}
return this.method === 'put'
return this.isUpdateMethod()
}
},
async created() {
@@ -246,6 +255,9 @@ export default {
}
},
methods: {
isUpdateMethod() {
return ['put', 'patch'].indexOf(this.method.toLowerCase()) > -1
},
handleSubmit(values, formName, addContinue) {
let handler = this.onSubmit || this.defaultOnSubmit
handler = handler.bind(this)
@@ -261,11 +273,11 @@ export default {
},
async getFormValue() {
const cloneFrom = this.$route.query['clone_from']
if (this.method !== 'put' && !cloneFrom) {
if (!this.isUpdateMethod() && !cloneFrom) {
return Object.assign(this.form, this.initial)
}
let object = this.object
if (!object) {
if (!object || Object.keys(object).length === 0) {
if (cloneFrom) {
this.$log.debug('Clone from: ', cloneFrom)
const url = `${this.url}${cloneFrom}/`
@@ -282,6 +294,7 @@ export default {
if (object) {
object = _.cloneDeep(object)
this.$emit('update:object', object)
this.$emit('getObjectDone', object)
}
return object
},

View File

@@ -175,8 +175,12 @@ export default {
},
defaultUpdate() {
const id = this.$route.params.id
const routeName = this.validActions.updateRoute
this.$router.push({ name: routeName, params: { id: id }})
let route = this.validActions.updateRoute
if (typeof route === 'string') {
route = { name: route, params: {}}
}
route.params.id = id
this.$router.push(route)
},
getObject() {
const url = this.validActions.detailApiUrl

View File

@@ -1,5 +1,5 @@
<template>
<ListTable ref="ListTable" v-bind="iAttrs" v-on="$listeners" />
<ListTable ref="ListTable" v-bind="$attrs" v-on="$listeners" />
</template>
<script>
@@ -11,16 +11,15 @@ export default {
ListTable
},
computed: {
...mapGetters(['currentOrgIsRoot']),
iAttrs() {
const attrs = _.cloneDeep(this.$attrs)
const canCreate = _.get(attrs, 'header-actions.canCreate', null)
this.$log.debug('Can create: ', canCreate)
if (canCreate === null && this.currentOrgIsRoot) {
_.set(attrs, 'header-actions.canCreate', false)
}
this.$log.debug('List table Attrs: ', attrs)
return attrs
...mapGetters(['currentOrgIsRoot'])
},
created() {
const headerActions = this.$attrs['header-actions'] || {}
if (headerActions.canCreate === undefined && this.currentOrgIsRoot) {
_.set(this.$attrs, 'header-actions.canCreate', false)
}
if (headerActions.hasImport === undefined && this.currentOrgIsRoot) {
_.set(this.$attrs, 'header-actions.hasImport', false)
}
}
}

View File

@@ -1,11 +1,8 @@
<template>
<Page>
<el-alert v-if="helpMessage" type="success"> {{ helpMessage }} </el-alert>
<Page v-bind="$attrs">
<TreeTable
ref="TreeTable"
:table-config="tableConfig"
:header-actions="iHeaderActions"
:tree-setting="treeSetting"
v-bind="$attrs"
v-on="$listeners"
>
<template #table>
@@ -27,23 +24,16 @@ export default {
components: {
Page, TreeTable
},
props: {
...TreeTable.props,
helpMessage: {
type: String,
default: null
}
},
computed: {
...mapGetters(['currentOrg']),
iHeaderActions() {
const attrs = _.cloneDeep(this.headerActions)
const canCreate = _.get(attrs, 'canCreate', null)
// this.$log.debug('Current org: ', this.currentOrg)
if (canCreate === null && this.currentOrg && this.currentOrg.is_root) {
_.set(attrs, 'canCreate', false)
}
return attrs
...mapGetters(['currentOrgIsRoot'])
},
created() {
const headerActions = this.$attrs['header-actions'] || {}
if (headerActions.canCreate === undefined && this.currentOrgIsRoot) {
_.set(this.$attrs, 'header-actions.canCreate', false)
}
if (headerActions.hasImport === undefined && this.currentOrgIsRoot) {
_.set(this.$attrs, 'header-actions.hasImport', false)
}
},
methods: {

View File

@@ -1,7 +1,7 @@
<template>
<Dialog
:title="this.$t('common.updateSelected')"
:visible.sync="dialogSetting.dialogVisible"
:visible.sync="iVisible"
width="70%"
top="1vh"
:show-cancel="false"
@@ -45,13 +45,13 @@ export default {
type: Array,
default: () => ([])
},
dialogSetting: {
type: Object,
default: () => ({})
},
formSetting: {
type: Object,
default: () => ({})
},
visible: {
type: Boolean,
default: false
}
},
data: function() {
@@ -62,6 +62,16 @@ export default {
iFormSetting: {}
}
},
computed: {
iVisible: {
set(val) {
this.$emit('update:visible', val)
},
get() {
return this.visible
}
}
},
mounted() {
const defaultFormSetting = this.getDefaultFormSetting()
this.iFormSetting = Object.assign({}, this.formSetting, defaultFormSetting)
@@ -80,7 +90,7 @@ export default {
getDefaultFormSetting() {
const vm = this
return {
getMethod: () => 'post',
submitMethod: () => 'post',
cleanFormValue: function(value) {
const filterValue = {}
Object.keys(value).filter((key) => vm.checkedFields.includes(key)).forEach((key) => {
@@ -100,7 +110,7 @@ export default {
this.$axios.patch(url, validValues).then((res) => {
vm.$emit('update')
this.$message.success(msg)
vm.dialogSetting.dialogVisible = false
this.iVisible = false
}).catch(error => {
this.$emit('submitError', error)
const response = error.response

View File

@@ -2,13 +2,10 @@
<div class="sidebar-logo-container" :class="{'collapse':collapse}">
<transition name="sidebarLogoFade">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo">
<h1 v-else class="sidebar-title">{{ title }}</h1>
<img :src="logoSrc" class="sidebar-logo">
</router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img :src="logoSrc" class="sidebar-logo-text">
<!-- <img v-else-if="logoText" :src="logoText" class="sidebar-logo-text">-->
<!-- <h1 class="sidebar-title">{{ title }}</h1>-->
<img :src="logoTextSrc" class="sidebar-logo-text">
</router-link>
</transition>
</div>
@@ -26,10 +23,6 @@ export default {
},
data() {
return {
title: 'JumpServer',
logoText: require('@/assets/img/logo-text.png'),
logo: require('@/assets/img/logo.png'),
xpackData: {}
}
},
computed: {
@@ -37,16 +30,14 @@ export default {
'publicSettings'
]),
// eslint-disable-next-line vue/return-in-computed-property
logoTextSrc() {
return this.publicSettings.LOGO_URLS.logo_index
},
logoSrc() {
if (this.publicSettings.LOGO_URLS.logo_index !== '/static/img/logo_text.png') {
return this.publicSettings.LOGO_URLS.logo_index
} else {
return this.logoText
}
return this.publicSettings.LOGO_URLS.logo_logout
}
},
created() {
}
}
</script>

View File

@@ -15,7 +15,7 @@
<el-option
v-for="item in userAdminOrgList"
:key="item.id"
selected="item.id == currentOrg.id"
:selected="item.id === currentOrg.id"
:label="item.name"
:value="item.id"
/>
@@ -48,10 +48,8 @@ export default {
},
methods: {
needShow() {
const otherOrgs = this.userAdminOrgList.filter(org => {
return !org.is_root && !org.is_default
})
return !this.isCollapse && otherOrgs.length > 0 && this.inAdminPage
const hasValidLicense = this.$store.getters.hasValidLicense
return !this.isCollapse && this.inAdminPage && hasValidLicense
},
changeOrg(orgId) {
orgUtil.changeOrg(orgId)

View File

@@ -82,7 +82,6 @@ export default {
case 'userPage':
if (this.currentOrgUsePagePerm) {
this.$store.dispatch('users/setCurrentRole', rolec.USER)
// console.log('Switch to: ', rolec.USER)
window.location.href = `/ui/`
}
break
@@ -96,11 +95,6 @@ export default {
}
},
logout() {
// Clean Status
const statusList = ['currentOrg', 'currentRole', 'jms_current_org', 'jms_current_role', 'sidebarStatus', 'X-JMS-ORG', 'activeTab']
for (const i in statusList) {
this.$cookie.delete(statusList[i])
}
}
}
}

View File

@@ -12,7 +12,7 @@
<script>
import Dialog from '@/components/Dialog'
import ListTable from '@/components/ListTable'
import { DateFormatter, ShowKeyFormatter } from '@/components/ListTable/formatters'
import { DateFormatter, ShowKeyFormatter } from '@/components/TableFormatters'
export default {
name: 'ApiKey',
components: {

View File

@@ -10,7 +10,6 @@
</template>
<script>
export default {
name: 'Language',
data() {
@@ -47,11 +46,22 @@ export default {
}
},
mounted() {
if (this.currentLang.code !== this.$i18n.locale) {
this.changeLangTo(this.currentLang)
}
this.changeLang()
this.changeMomentLang()
},
methods: {
changeLang() {
if (this.currentLang.code !== this.$i18n.locale) {
this.changeLangTo(this.currentLang)
}
},
changeMomentLang() {
if (this.currentLang.code.indexOf('en') > -1) {
this.$moment.locale('en')
} else {
this.$moment.locale('zh-cn')
}
},
changeLangTo(item) {
this.$i18n.locale = item.code
localStorage.setItem('lang', item.code)

View File

@@ -0,0 +1,274 @@
<template>
<div>
<el-badge :value="unreadMsgCount" :hidden="unreadMsgCount === 0" :max="99" size="mini" type="primary">
<a style="color: #606266 !important; width: 30px" @click="toggleDrawer">
<i class="el-icon-message" style="font-size: 18px" />
</a>
</el-badge>
<el-drawer
:visible.sync="show"
:before-close="handleClose"
:modal="false"
:title="$t('notifications.SiteMessage')"
custom-class="site-msg"
size="25%"
@open="getMessages"
>
<div v-if="unreadMsgCount !== 0" class="msg-list">
<div
v-for="msg of messages"
:key="msg.id"
class="msg-item"
:class="msg['has_read'] ? 'msg-read' : 'msg-unread'"
@mouseover="hoverMsgId = msg.id"
@mouseleave="hoverMsgId = ''"
@click="showMsgDetail(msg)"
>
<div class="msg-item-head">
<span class="msg-item-head-type">
<i :class="msg.has_read ? 'fa-envelope-open-o' : 'fa-envelope'" class="fa msg-icon" />
{{ msg.subject }}
</span>
<span v-if="hoverMsgId !== msg.id || msg.has_read" class="msg-item-head-time">
{{ formatDate(msg.date_created) }}
</span>
<div v-else class="msg-item-read-btn" @click.stop="markAsRead(msg)">
<a>{{ $t('notifications.MarkAsRead') }}</a>
</div>
</div>
<div class="msg-item-txt">
<span v-html="msg.message" />
</div>
</div>
</div>
<div v-else class="no-msg">
{{ $t('notifications.NoUnreadMsg') }}
</div>
</el-drawer>
<Dialog
v-if="msgDetailVisible"
:visible.sync="msgDetailVisible"
:title="''"
:close-on-click-modal="false"
:confirm-title="$t('notifications.MarkAsRead')"
@confirm="markAsRead(currentMsg)"
@cancel="cancelRead"
>
<div class="msg-detail">
<div class="msg-detail-head">
<h3>{{ currentMsg.subject }}</h3>
<h5>
<span class="msg-detail-time">{{ formatDate(currentMsg.date_created) }}</span>
</h5>
</div>
<div class="msg-detail-txt">
<span v-html="currentMsg.message" />
</div>
</div>
</Dialog>
</div>
</template>
<script>
import { toSafeLocalDateStr } from '@/utils/common'
import Dialog from '@/components/Dialog'
export default {
name: 'SiteMessages',
components: { Dialog },
data() {
return {
show: false,
messages: [],
hoverMsgId: '',
msgDetailVisible: false,
currentMsg: null,
unreadMsgCount: 0
}
},
mounted() {
this.enablePullMsgCount()
},
methods: {
handleClose() {
this.show = false
},
toggleDrawer() {
this.show = !this.show
},
showMsgDetail(msg) {
this.currentMsg = msg
this.msgDetailVisible = true
},
getMessages() {
const url = '/api/v1/notifications/site-message/?offset=0&limit=15&has_read=false'
this.$axios.get(url).then(resp => {
this.messages = [...resp.results]
this.unreadMsgCount = resp.count
})
},
formatDate(s) {
if (!s) {
return ''
}
const d = new Date(s)
const now = new Date()
if (now.getTime() - d.getTime() > (3600 * 24 * 7) * 1000) {
return toSafeLocalDateStr(s)
} else {
return this.$moment(d).fromNow()
}
},
markAsRead(msg) {
const url = `/api/v1/notifications/site-message/mark-as-read/`
this.$axios.patch(url, { ids: [msg.id] }).then(res => {
this.msgDetailVisible = false
this.getMessages()
}).catch(err => {
this.$message(err.detail)
})
},
cancelRead() {
this.msgDetailVisible = false
},
enablePullMsgCount() {
const scheme = document.location.protocol === 'https:' ? 'wss' : 'ws'
const port = document.location.port ? ':' + document.location.port : ''
const url = '/ws/notifications/site-msg/'
const wsURL = scheme + '://' + document.location.hostname + port + url
const ws = new WebSocket(wsURL)
ws.onopen = (event) => {
this.$log.debug('Websocket connected: ', event)
}
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data)
this.$log.debug('Data: ', data)
const unreadCount = data['unread_count']
if (unreadCount !== undefined) {
this.unreadMsgCount = unreadCount
}
} catch (e) {
this.$log.debug('Recv site message error')
}
}
ws.onerror = (error) => {
this.$message.error(this.$t('common.ConnectWebSocketError'))
this.$log.debug('site message ws error: ', error)
}
}
}
}
</script>
<style lang="scss" scoped>
.el-badge ::v-deep .el-badge__content.is-fixed{
top:10px;
}
.msg-list {
padding: 0 25px 20px;
}
>>> .site-msg {
.el-drawer__header {
border-bottom: solid 1px rgb(231, 234, 239);
margin-bottom: 0;
padding-top: 10px;
font-size: 16px;
}
.el-drawer__body {
overflow-y: auto;
}
}
.msg-item {
border-bottom: solid 1px rgb(231, 234, 239);
padding: 15px 0 10px;
position: relative;
border-bottom: 1px solid #ddd;
cursor: pointer;
&:hover {
background-color: #f2f2f2;
padding: 15px 20px 10px;
margin: 0 -20px;
border-bottom: 1px solid #fff;
}
.msg-icon {
font-size: 13px;
line-height: 13px;
}
&.msg-unread {
.msg-item-txt {
font-weight: bolder;
}
}
}
.msg-item-head {
line-height: 20px;
color: #888;
font-size: 12px;
&:after {
clear: both;
content: ".";
display: block;
height: 0;
overflow: hidden;
}
.msg-item-head-type {
float: left;
width: 240px;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
white-space: nowrap;
}
.msg-item-head-time {
float: right;
}
.msg-item-read-btn {
float: right;
}
}
.msg-item-txt {
overflow: hidden;
color: #000;
padding: 4px 0 0;
line-height: 21px;
max-height: 21px;
display: -webkit-box;
font-size: 12px;
display: block;
}
.msg-detail {
padding-left: 20px;
.msg-detail-time {
font-weight: 400;
font-size: 12px;
line-height: 1.1;
}
.msg-detail-txt {
margin-bottom: 20px;
line-height: 25px;
}
}
.no-msg {
padding-top: 20px;
text-align: center;
}
>>> :focus{ outline:0; }
</style>

View File

@@ -3,30 +3,26 @@
<div class="navbar-header">
<hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
</div>
<div class="navbar-right">
<div class="header-item">
<ul class="navbar-right">
<li class="header-item header-icon">
<SiteMessages />
</li>
<li class="header-item" style="margin-left: 10px">
<Help />
</div>
<div class="header-item">
</li>
<li class="header-item">
<Language />
</div>
<div
v-if="
publicSettings.TICKETS_ENABLED
&& publicSettings.XPACK_LICENSE_IS_VALID
&& !isOrgAuditor
"
class="header-item"
>
</li>
<li v-if="showTickets" class="header-item">
<Tickets />
</div>
<div class="header-item">
</li>
<li class="header-item">
<WebTerminal />
</div>
<div class="header-item header-profile">
</li>
<li class="header-item header-profile">
<AccountDropdown />
</div>
</div>
</li>
</ul>
</div>
</template>
@@ -34,6 +30,7 @@
import { mapGetters } from 'vuex'
import Hamburger from '@/components/Hamburger'
import AccountDropdown from './AccountDropdown'
import SiteMessages from './SiteMessages'
import Help from './Help'
import Language from './Language'
import WebTerminal from './WebTerminal'
@@ -48,7 +45,8 @@ export default {
Language,
Help,
Tickets,
WebTerminal
WebTerminal,
SiteMessages
},
data() {
return {
@@ -60,13 +58,17 @@ export default {
]),
isOrgAuditor() {
return rolc.getRolesDisplay(this.currentOrgRoles).includes('OrgAuditor') || rolc.getRolesDisplay(this.currentOrgRoles).includes('Auditor')
},
showTickets() {
return this.publicSettings.TICKETS_ENABLED &&
this.publicSettings.XPACK_LICENSE_IS_VALID &&
!this.isOrgAuditor
}
},
methods: {
toggleSideBar() {
this.$store.dispatch('app/toggleSideBar')
}
}
}
</script>
@@ -91,12 +93,19 @@ export default {
.navbar-right {
float: right;
margin-right: 10px;
}
.header-item {
line-height: 50px;
display: inline-block;
padding-right: 20px;
.header-item {
line-height: 50px;
display: inline-block;
padding-right: 10px;
padding-left: 10px;
}
.header-icon {
&:hover {
background-color: #e6e6e6;
}
}
}
.breadcrumb-container {
@@ -108,5 +117,9 @@ export default {
.el-header {
background-color: #ffffff;
}
ul {
margin: 0;
}
</style>

View File

@@ -8,10 +8,23 @@
</template>
<div>
<el-tabs v-if="submenu.length > 0" slot="submenu" v-model="iActiveMenu" class="page-submenu" @tab-click="handleTabClick">
<el-tabs
v-if="submenu.length > 0"
slot="submenu"
v-model="iActiveMenu"
class="page-submenu"
@tab-click="handleTabClick"
>
<template v-for="item in submenu">
<el-tab-pane :key="item.name" :label-content="item.labelContent" :name="item.name">
<el-tab-pane
v-if="checkShow(item)"
:key="item.name"
:label-content="item.labelContent"
:name="item.name"
:disabled="item.disabled"
>
<span slot="label">
<i v-if="item.icon" class="fa " :class="item.icon" />
{{ item.title }}
<slot name="badge" :tab="item.name" />
</span>
@@ -67,6 +80,13 @@ export default {
this.iActiveMenu = this.getPropActiveTab()
},
methods: {
checkShow(item) {
let hidden = item.hidden
if (typeof hidden === 'function') {
hidden = hidden()
}
return !hidden
},
handleTabClick(tab) {
this.$emit('tab-click', tab)
this.$emit('update:activeMenu', tab.name)
@@ -80,37 +100,39 @@ export default {
},
getPropActiveTab() {
let activeTab = ''
let tabObj = null
const activeTabs = [
const preActiveTabs = [
this.$route.query[ACTIVE_TAB_KEY],
this.$cookie.get(ACTIVE_TAB_KEY),
this.activeMenu
]
for (activeTab of activeTabs) {
tabObj = this.tabIndices[activeTab]
if (tabObj !== undefined) {
return activeTab
for (const preTab of preActiveTabs) {
for (const tabName in this.tabIndices) {
if (preTab && tabName && preTab.toLowerCase() === tabName.toLowerCase()) {
return tabName
}
}
}
return this.submenu[0].name
activeTab = this.submenu[0].name
return activeTab
}
}
}
</script>
<style scoped>
.page-submenu >>> .el-tabs__header {
background-color: white;
margin-left: -25px;
padding-left: 25px;
margin-right: -25px;
padding-right: 25px;
margin-top: -30px;
}
.page-submenu >>> .el-tabs__nav-wrap {
position: static;
}
.page-submenu >>> .el-tabs__header {
background-color: white;
margin-left: -25px;
padding-left: 25px;
margin-right: -25px;
padding-right: 25px;
margin-top: -30px;
}
.page-submenu >>> .el-tabs__nav-wrap {
position: static;
}
</style>

View File

@@ -7,6 +7,7 @@ export { default as GenericDetailPage } from './GenericDetailPage'
export { default as TabPage } from './TabPage'
export { default as Footer } from './Footer'
export { default as GenericListPage } from './GenericListPage'
export { default as GenericListTable } from './GenericListTable'
export { default as GenericCreateUpdatePage } from './GenericCreateUpdatePage'
export { default as GenericCreateUpdateForm } from './GenericCreateUpdateForm'
export { default as GenericUpdateFormDialog } from './GenericUpdateFormDialog'

View File

@@ -40,8 +40,13 @@ Vue.config.productionTip = false
import VueCookie from 'vue-cookie'
Vue.use(VueCookie)
window.$cookie = VueCookie
import VueMoment from 'vue-moment'
Vue.use(VueMoment)
const moment = require('moment')
require('moment/locale/zh-cn')
Vue.use(require('vue-moment'), {
moment
})
// logger
import VueLogger from 'vuejs-logger'
import loggerOptions from './utils/logger'

165
src/router/accounts.js Normal file
View File

@@ -0,0 +1,165 @@
import i18n from '@/i18n/i18n'
import empty from '@/layout/empty'
export default [
{
path: 'asset-accounts',
component: empty,
meta: { title: i18n.t('route.AssetAccount') },
redirect: '',
children: [
{
path: '',
name: 'AssetAccountList',
component: () => import('@/views/accounts/AssetAccount/AssetAccountList'),
meta: { title: i18n.t('route.AssetAccount') }
}
]
},
{
path: 'application-accounts',
component: empty,
redirect: '',
meta: { title: i18n.t('route.AssetAccount') },
children: [
{
path: '',
name: 'ApplicationAccountList',
component: () => import('@/views/accounts/ApplicationAccount/ApplicationAccountList'),
meta: { title: i18n.t('route.ApplicationAccount') }
}
]
},
{
path: 'gathered-user',
component: empty,
redirect: '',
meta: { title: i18n.t('xpack.GatherUser.GatherUserList') },
children: [
{
path: '',
component: () => import('@/views/accounts/GatheredUser/index'),
name: 'GatherUserListIndex',
meta: { title: i18n.t('xpack.GatherUser.GatherUser'), activeMenu: '/accounts/gathered-user' }
},
{
path: '',
component: () => import('@/views/accounts/GatheredUser/GatheredUserList'),
name: 'GatherUserList',
hidden: true,
meta: { title: i18n.t('xpack.GatherUser.GatherUserList'), activeMenu: '/accounts/gathered-user' }
},
{
path: 'tasks',
component: () => import('@/views/accounts/GatheredUser/TaskList'),
name: 'GatherUserTaskList',
meta: { title: i18n.t('xpack.GatherUser.GatherUserTaskList'), activeMenu: '/accounts/gathered-user' },
hidden: true
},
{
path: 'tasks/:id',
component: () => import('@/views/accounts/GatheredUser/TaskDetail/index'),
name: 'GatherUserTaskDetail',
meta: { title: i18n.t('xpack.GatherUser.GatherUserTaskDetail'), activeMenu: '/accounts/gathered-user' },
hidden: true
},
{
path: 'tasks/create',
component: () => import('@/views/accounts/GatheredUser/TaskCreateUpdate'),
name: 'GatherUserTaskCreate',
meta: { title: i18n.t('xpack.GatherUser.GatherUserTaskCreate'), activeMenu: '/accounts/gathered-user' },
hidden: true
},
{
path: 'tasks/:id/update',
component: () => import('@/views/accounts/GatheredUser/TaskCreateUpdate'),
name: 'GatherUserTaskUpdate',
meta: { title: i18n.t('xpack.GatherUser.GatherUserTaskUpdate'), action: 'update', activeMenu: '/accounts/gathered-user' },
hidden: true
}
]
},
{
path: 'change-auth-plan',
component: empty,
redirect: '',
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlan') },
children: [
{
path: '',
component: () => import('@/views/accounts/ChangeAuthPlan/index.vue'),
name: 'ChangeAuthPlanIndex',
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlan'), activeMenu: '/accounts/change-auth-plan' }
},
{
path: 'plan',
component: () => import('@/views/accounts/ChangeAuthPlan/AssetChangeAuthPlan/ChangeAuthPlanList.vue'),
name: 'AssetChangeAuthPlanList',
meta: { title: i18n.t('xpack.ChangeAuthPlan.AssetChangeAuthPlan'), activeMenu: '/accounts/change-auth-plan' },
hidden: true
},
{
path: 'plan/create',
component: () => import('@/views/accounts/ChangeAuthPlan/AssetChangeAuthPlan/ChangeAuthPlanCreateUpdate.vue'),
name: 'AssetChangeAuthPlanCreate',
meta: { title: i18n.t('xpack.ChangeAuthPlan.AssetChangeAuthPlanCreate'), activeMenu: '/accounts/change-auth-plan', action: 'create' },
hidden: true
},
{
path: 'plan/:id/update',
component: () => import('@/views/accounts/ChangeAuthPlan/AssetChangeAuthPlan/ChangeAuthPlanCreateUpdate.vue'),
name: 'AssetChangeAuthPlanUpdate',
meta: { title: i18n.t('xpack.ChangeAuthPlan.AssetChangeAuthPlanUpdate'), activeMenu: '/accounts/change-auth-plan', action: 'update' },
hidden: true
},
{
path: 'plan/:id',
component: () => import('@/views/accounts/ChangeAuthPlan/AssetChangeAuthPlan/ChangeAuthPlanDetail/index.vue'),
name: 'AssetChangeAuthPlanDetail',
meta: { title: i18n.t('xpack.ChangeAuthPlan.AssetChangeAuthPlan'), activeMenu: '/accounts/change-auth-plan' },
hidden: true
},
{
path: 'plan-execution/:id',
component: () => import('@/views/accounts/ChangeAuthPlan/AssetChangeAuthPlan/ChangeAuthPlanDetail/ChangeAuthPlanExecution/ChangeAuthPlanExecutionDetail/index.vue'),
name: 'ChangeAuthPlanExecutionDetail',
meta: { title: i18n.t('xpack.ChangeAuthPlan.ExecutionDetail'), activeMenu: '/accounts/change-auth-plan' },
hidden: true
},
{
path: 'app-plan',
component: () => import('@/views/accounts/ChangeAuthPlan/AppChangeAuthPlan/AppChangeAuthPlanList.vue'),
name: 'AppChangeAuthPlanList',
meta: { title: i18n.t('xpack.ChangeAuthPlan.AppChangeAuthPlan'), activeMenu: '/accounts/change-auth-plan' },
hidden: true
},
{
path: 'app-plan/create',
component: () => import('@/views/accounts/ChangeAuthPlan/AppChangeAuthPlan/AppChangeAuthPlanCreateUpdate.vue'),
name: 'AppChangeAuthPlanCreate',
meta: { title: i18n.t('xpack.ChangeAuthPlan.AppChangeAuthPlanCreate'), activeMenu: '/accounts/change-auth-plan', action: 'create' },
hidden: true
},
{
path: 'app-plan/:id',
component: () => import('@/views/accounts/ChangeAuthPlan/AppChangeAuthPlan/ChangeAuthPlanDetail/index.vue'),
name: 'AppChangeAuthPlanDetail',
meta: { title: i18n.t('xpack.ChangeAuthPlan.AppChangeAuthPlan'), activeMenu: '/accounts/change-auth-plan' },
hidden: true
},
{
path: 'app-plan/:id/update',
component: () => import('@/views/accounts/ChangeAuthPlan/AppChangeAuthPlan/AppChangeAuthPlanCreateUpdate.vue'),
name: 'AppChangeAuthPlanUpdate',
meta: { title: i18n.t('xpack.ChangeAuthPlan.AppChangeAuthPlanUpdate'), activeMenu: '/accounts/change-auth-plan', action: 'update' },
hidden: true
},
{
path: 'app-plan-execution/:id',
component: () => import('@/views/accounts/ChangeAuthPlan/AppChangeAuthPlan/ChangeAuthPlanDetail/AppChangeAuthPlanExecution/ChangeAuthPlanExecutionDetail/index.vue'),
name: 'AppChangeAuthPlanExecutionDetail',
meta: { title: i18n.t('xpack.ChangeAuthPlan.ExecutionDetail'), activeMenu: '/accounts/change-auth-plan' },
hidden: true
}
]
}
]

View File

@@ -38,57 +38,72 @@ export default [
]
},
{
path: 'database-apps',
name: 'DatabaseAppList',
component: () => import('@/views/applications/DatabaseApp/DatabaseAppList'),
meta: { title: i18n.t('route.DatabaseApp') }
path: 'databases',
component: empty,
redirect: '',
meta: { title: i18n.t('route.DatabaseApp') },
children: [
{
path: '',
name: 'DatabaseAppList',
component: () => import('@/views/applications/DatabaseApp/DatabaseAppList'),
meta: { title: i18n.t('route.DatabaseApp') }
},
{
path: 'create',
name: 'DatabaseAppCreate',
component: () => import('@/views/applications/DatabaseApp/DatabaseAppCreateUpdate'),
meta: { title: i18n.t('route.DatabaseAppCreate'), activeMenu: '/applications/databases', action: 'create' },
hidden: true
},
{
path: ':id/update',
name: 'DatabaseAppUpdate',
component: () => import('@/views/applications/DatabaseApp/DatabaseAppCreateUpdate'),
meta: { title: i18n.t('route.DatabaseAppUpdate'), activeMenu: '/applications/databases', action: 'update' },
hidden: true
},
{
path: ':id',
name: 'DatabaseAppDetail',
component: () => import('@/views/applications/DatabaseApp/DatabaseAppDetail/index'),
meta: { title: i18n.t('route.DatabaseAppDetail'), activeMenu: '/applications/databases' },
hidden: true
}
]
},
{
path: 'database-apps/create',
name: 'DatabaseAppCreate',
component: () => import('@/views/applications/DatabaseApp/DatabaseAppCreateUpdate'),
meta: { title: i18n.t('route.DatabaseAppCreate'), activeMenu: '/applications/database-apps', action: 'create' },
hidden: true
},
{
path: 'database-apps/:id/update',
name: 'DatabaseAppUpdate',
component: () => import('@/views/applications/DatabaseApp/DatabaseAppCreateUpdate'),
meta: { title: i18n.t('route.DatabaseAppUpdate'), activeMenu: '/applications/database-apps', action: 'update' },
hidden: true
},
{
path: 'database-apps/:id',
name: 'DatabaseAppDetail',
component: () => import('@/views/applications/DatabaseApp/DatabaseAppDetail/index'),
meta: { title: i18n.t('route.DatabaseAppDetail'), activeMenu: '/applications/database-apps' },
hidden: true
},
{
path: 'kubernetes-apps',
name: 'KubernetesAppList',
component: () => import('@/views/applications/KubernetesApp/KubernetesAppList'),
meta: { title: i18n.t('route.KubernetesApp') }
},
{
path: 'kubernetes-apps/create',
name: 'KubernetesAppCreate',
component: () => import('@/views/applications/KubernetesApp/KubernetesAppCreateUpdate'),
meta: { title: i18n.t('route.KubernetesAppCreate'), activeMenu: '/applications/kubernetes-apps', action: 'create' },
hidden: true
},
{
path: 'kubernetes-apps/:id/update',
name: 'KubernetesAppUpdate',
component: () => import('@/views/applications/KubernetesApp/KubernetesAppCreateUpdate'),
meta: { title: i18n.t('route.KubernetesAppUpdate'), activeMenu: '/applications/kubernetes-apps', action: 'update' },
hidden: true
},
{
path: 'kubernetes-apps/:id',
name: 'KubernetesAppDetail',
component: () => import('@/views/applications/KubernetesApp/KubernetesAppDetail/index'),
meta: { title: i18n.t('route.KubernetesAppDetail'), activeMenu: '/applications/kubernetes-apps' },
hidden: true
path: 'kubernetes',
component: empty,
meta: { title: i18n.t('route.KubernetesApp') },
children: [
{
path: '',
name: 'KubernetesAppList',
component: () => import('@/views/applications/KubernetesApp/KubernetesAppList'),
meta: { title: i18n.t('route.KubernetesApp') }
},
{
path: 'create',
name: 'KubernetesAppCreate',
component: () => import('@/views/applications/KubernetesApp/KubernetesAppCreateUpdate'),
meta: { title: i18n.t('route.KubernetesAppCreate'), activeMenu: '/applications/kubernetes', action: 'create' },
hidden: true
},
{
path: ':id/update',
name: 'KubernetesAppUpdate',
component: () => import('@/views/applications/KubernetesApp/KubernetesAppCreateUpdate'),
meta: { title: i18n.t('route.KubernetesAppUpdate'), activeMenu: '/applications/kubernetes', action: 'update' },
hidden: true
},
{
path: ':id',
name: 'KubernetesAppDetail',
component: () => import('@/views/applications/KubernetesApp/KubernetesAppDetail/index'),
meta: { title: i18n.t('route.KubernetesAppDetail'), activeMenu: '/applications/kubernetes' },
hidden: true
}
]
}
]

View File

@@ -101,41 +101,6 @@ export default [
}
]
},
{
path: 'admin-users',
component: empty,
redirect: '',
meta: { title: i18n.t('route.AdminUserList') },
children: [
{
path: '',
name: 'AdminUserList',
component: () => import('@/views/assets/AdminUser/AdminUserList'),
meta: { title: i18n.t('route.AdminUserList'), activeMenu: '/assets/admin-users' }
},
{
path: 'create',
component: () => import('@/views/assets/AdminUser/AdminUserCreateUpdate.vue'), // Parent router-view
name: 'AdminUserCreate',
meta: { title: i18n.t('route.AdminUserCreate'), activeMenu: '/assets/admin-users' },
hidden: true
},
{
path: ':id/update',
component: () => import('@/views/assets/AdminUser/AdminUserCreateUpdate.vue'), // Parent router-view
name: 'AdminUserUpdate',
meta: { title: i18n.t('route.AdminUserUpdate'), activeMenu: '/assets/admin-users' },
hidden: true
},
{
path: ':id',
component: () => import('@/views/assets/AdminUser/AdminUserDetail/index.vue'), // Parent router-view
name: 'AdminUserDetail',
meta: { title: i18n.t('route.AdminUserDetail'), activeMenu: '/assets/admin-users' },
hidden: true
}
]
},
{
path: 'system-users',
component: empty,
@@ -145,20 +110,20 @@ export default [
{
path: '',
name: 'SystemUserList',
component: () => import('@/views/assets/SystemUser/SystemUserList.vue'),
component: () => import('@/views/assets/SystemUser/SystemUserList/index.vue'),
meta: { title: i18n.t('route.SystemUserList'), activeMenu: '/assets/system-users' }
},
{
path: 'create',
name: 'SystemUserCreate',
component: () => import('@/views/assets/SystemUser/SystemUserCreateUpdate.vue'),
component: () => import('@/views/assets/SystemUser/SystemUserCreateUpdate/index.vue'),
meta: { title: i18n.t('route.SystemUserCreate'), activeMenu: '/assets/system-users' },
hidden: true
},
{
path: ':id/update',
name: 'SystemUserUpdate',
component: () => import('@/views/assets/SystemUser/SystemUserCreateUpdate.vue'),
component: () => import('@/views/assets/SystemUser/SystemUserCreateUpdate/index.vue'),
meta: { title: i18n.t('route.SystemUserUpdate'), activeMenu: '/assets/system-users' },
hidden: true
},

View File

@@ -37,6 +37,7 @@ import TicketsRoutes from './tickets'
import AuditsRoutes from './audits'
import commonRoutes from './common'
import aclRoutes from './acl'
import AccountRoutes from './accounts'
/**
* constantRoutes
@@ -110,6 +111,18 @@ export const allRoleRoutes = [
meta: { title: i18n.t('route.Applications'), icon: 'th' },
children: ApplicationsRoute
},
{
path: '/accounts',
component: Layout,
redirect: '/accounts/asset-accounts/',
name: 'Accounts',
meta: {
licenseRequired: true,
title: i18n.t('route.Accounts'),
icon: 'address-book'
},
children: AccountRoutes
},
{
path: '/perms/',
component: Layout,

View File

@@ -5,24 +5,32 @@ import { BASE_URL } from '@/utils/common'
export default [
{
path: 'session',
name: 'SessionList',
component: () => import('@/views/sessions/SessionList/index'),
meta: { title: i18n.t('route.Sessions'), permissions: [rolec.PERM_AUDIT] }
path: 'sessions',
component: empty,
redirect: '',
meta: { title: i18n.t('route.Sessions'), permissions: [rolec.PERM_AUDIT] },
children: [
{
path: '',
name: 'SessionList',
component: () => import('@/views/sessions/SessionList/index'),
meta: { title: i18n.t('route.Sessions'), permissions: [rolec.PERM_AUDIT] }
},
{
path: ':id',
name: 'SessionDetail',
component: () => import('@/views/sessions/SessionDetail/index'),
meta: { title: i18n.t('route.SessionDetail'), activeMenu: '/terminal/sessions' },
hidden: true
}
]
},
{
path: 'command',
path: 'commands',
name: 'CommandList',
component: () => import('@/views/sessions/CommandList'),
meta: { title: i18n.t('route.Commands'), permissions: [rolec.PERM_AUDIT] }
},
{
path: 'sessions/:id',
name: 'SessionDetail',
component: () => import('@/views/sessions/SessionDetail/index'),
meta: { title: i18n.t('route.SessionDetail'), activeMenu: '/terminal/session', permissions: [rolec.PERM_AUDIT] },
hidden: true
},
{
path: `${BASE_URL}/luna/?_=${Date.now()}`,
name: 'WebTerminal',
@@ -66,37 +74,41 @@ export default [
},
{
path: 'storages',
name: 'Storage',
component: () => import('@/views/sessions/Storage/index'),
component: empty,
meta: { activeMenu: '/terminal/terminal', permissions: [rolec.PERM_SUPER] },
hidden: true
},
{
path: 'replay-storage/create',
name: 'CreateReplayStorage',
component: () => import('@/views/sessions/ReplayStorageCreateUpdate'),
meta: { title: i18n.t('route.CreateReplayStorage'), activeMenu: '/terminal/terminal', permissions: [rolec.PERM_SUPER] },
hidden: true
},
{
path: 'command-storage/create',
name: 'CreateCommandStorage',
component: () => import('@/views/sessions/CommandStorageCreateUpdate'),
meta: { title: i18n.t('route.CreateCommandStorage'), activeMenu: '/terminal/terminal', permissions: [rolec.PERM_SUPER] },
hidden: true
},
{
path: 'replay-storage/:id/update',
name: 'ReplayStorageUpdate',
component: () => import('@/views/sessions/ReplayStorageCreateUpdate'),
meta: { title: i18n.t('route.ReplayStorageUpdate'), activeMenu: '/terminal/terminal', permissions: [rolec.PERM_SUPER] },
hidden: true
},
{
path: 'command-storage/:id/update',
name: 'CommandStorageUpdate',
component: () => import('@/views/sessions/CommandStorageCreateUpdate'),
meta: { title: i18n.t('route.CommandStorageUpdate'), activeMenu: '/terminal/terminal', permissions: [rolec.PERM_SUPER] },
hidden: true
redirect: '',
hidden: true,
children: [
{
path: '',
name: 'Storage',
component: () => import('@/views/sessions/Storage/index'),
meta: { activeMenu: '/terminal/terminal' }
},
{
path: 'replay-storage/create',
name: 'CreateReplayStorage',
component: () => import('@/views/sessions/ReplayStorageCreateUpdate'),
meta: { title: i18n.t('route.CreateReplayStorage'), activeMenu: '/terminal/terminal' }
},
{
path: 'replay-storage/:id/update',
name: 'ReplayStorageUpdate',
component: () => import('@/views/sessions/ReplayStorageCreateUpdate'),
meta: { title: i18n.t('route.ReplayStorageUpdate'), activeMenu: '/terminal/terminal' }
},
{
path: 'command-storage/create',
name: 'CreateCommandStorage',
component: () => import('@/views/sessions/CommandStorageCreateUpdate'),
meta: { title: i18n.t('route.CreateCommandStorage'), activeMenu: '/terminal/terminal' }
},
{
path: 'command-storage/:id/update',
name: 'CommandStorageUpdate',
component: () => import('@/views/sessions/CommandStorageCreateUpdate'),
meta: { title: i18n.t('route.CommandStorageUpdate'), activeMenu: '/terminal/terminal' }
}
]
}
]

View File

@@ -1,4 +1,5 @@
import i18n from '@/i18n/i18n'
import empty from '@/layout/empty'
export default [
{
path: 'tickets',
@@ -48,5 +49,43 @@ export default [
component: () => import('@/views/tickets/RequestApplicationPerm/Detail/index'),
meta: { title: i18n.t('route.TicketDetail'), activeMenu: '/tickets/tickets' },
hidden: true
},
{
path: 'tickets/command-confirm/:id',
name: 'CommandConfirmDetail',
component: () => import('@/views/tickets/CommandConfirm/Detail/index'),
meta: { title: i18n.t('route.CommandConfirm'), activeMenu: '/tickets/tickets' },
hidden: true
},
{
path: 'flows',
name: 'TicketFlowList',
component: empty,
redirect: '',
meta: { title: i18n.t('route.TicketFlow'), icon: 'check-square-o', activeMenu: '/tickets/tickets' },
hidden: true,
children: [
{
path: 'create',
name: 'TicketFlowCreate',
component: () => import('@/views/tickets/TicketFlow/FlowCreateUpdate'),
meta: { title: i18n.t('route.TicketFlowCreate') },
hidden: true
},
{
path: ':id/update',
name: 'TicketFlowUpdate',
component: () => import('@/views/tickets/TicketFlow/FlowCreateUpdate'),
meta: { title: i18n.t('route.TicketFlowUpdate') },
hidden: true
}
]
},
{
path: 'tickets/flow/:id',
name: 'FlowDetail',
component: () => import('@/views/tickets/TicketFlow/Detail/index'),
meta: { title: i18n.t('route.TicketFlow'), activeMenu: '/tickets/tickets' },
hidden: true
}
]

View File

@@ -65,7 +65,7 @@ export default [
path: '',
name: 'CommandExecutions',
component: () => import('@/views/ops/CommandExecution'),
meta: { title: i18n.t('route.CommandExecutions'), icon: 'terminal', permissions: [rolec.PERM_USE] }
meta: { title: i18n.t('route.BatchCommand'), icon: 'terminal', permissions: [rolec.PERM_USE] }
}
]
},
@@ -124,6 +124,13 @@ export default [
meta: { title: i18n.t('route.TicketDetail'), activeMenu: '/tickets/tickets', permissions: [rolec.PERM_USE] },
hidden: true
},
{
path: 'tickets/command-confirm/:id',
name: 'CommandConfirmDetail',
component: () => import('@/views/tickets/CommandConfirm/Detail/index'),
meta: { title: i18n.t('route.CommandConfirm'), activeMenu: '/tickets/tickets', permissions: [rolec.PERM_USE] },
hidden: true
},
{
path: 'tickets/:id',
name: 'TicketDetail',
@@ -155,7 +162,7 @@ export default [
children: [
{
path: `${BASE_URL}/koko/elfinder/sftp/`,
meta: { title: i18n.t('route.WebFTP'), icon: 'file', activeMenu: '/assets', permissions: [rolec.PERM_USE] }
meta: { title: i18n.t('route.FileManager'), icon: 'file', activeMenu: '/assets', permissions: [rolec.PERM_USE] }
}
]
}

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