Compare commits

...

866 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
Jiangjie.Bai
d8566d2f9e Merge pull request #753 from jumpserver/dev
v2.9.0 发版
2021-04-15 21:05:14 +08:00
fit2bot
d74da503c8 fix: 修复不能禁用的问题 (#751)
perf: 优化系统用户创建

Co-authored-by: ibuler <ibuler@qq.com>
2021-04-15 19:41:07 +08:00
Orange
e1d8e4aea6 fix: 修复v2.9测试中的bugs 2021-04-15 06:40:30 -05:00
Orange
d21559599f fix: 修复系统设置邮件bugs 2021-04-15 06:39:42 -05:00
Orange
f8cadb545f fix: 修复首页面板链接跳转有误的问题
Closes https://github.com/jumpserver/jumpserver/issues/5850
2021-04-15 06:39:07 -05:00
Orange
2bc4b53159 Merge pull request #747 from jumpserver/pr@dev@fix_create_asset_node_lost
fix(assets): 修复选中资产创建时,没有默认选中节点的问题
2021-04-15 16:19:51 +08:00
ibuler
7f28cc0aad fix(assets): 修复选中资产创建时,没有默认选中节点的问题 2021-04-15 15:39:26 +08:00
Orange
7e95e38d24 Merge pull request #744 from jumpserver/dev
v2.9.0 rc3
2021-04-14 18:55:07 +08:00
Orange
57bafc01e3 fix: 修改翻译 2021-04-14 18:54:34 +08:00
Orange
834033f2fd fix: 隐藏终端详情中的端口数据
Closes https://github.com/jumpserver/trello/issues/986
2021-04-14 05:30:25 -05:00
Orange
9a41ccbdd7 fix: 禁止全局组织中批量更新用户组 2021-04-14 05:30:04 -05:00
ibuler
3793370c9c fix: 修复导入id重复的bug 2021-04-14 05:29:38 -05:00
Orange
4486dc55a7 Merge pull request #740 from jumpserver/dev
v2.9.0 rc2
2021-04-13 19:30:56 +08:00
fit2bot
bdb63b865a fix: 修复v2.9 bugs (#739)
* fix: 修复资产列表协议组显示的问题

* fix: 全局组织批量更新用户禁止更新用户组

* fix: 修复网关无法克隆的Bug

* fix: 修复平台列表更新bug

* fix: 修复翻译问题

* fix: 修复更新管理用户账户秘钥的问题

Co-authored-by: Orange <orangemtony@gmail.com>
2021-04-13 19:30:24 +08:00
fit2bot
2d17b48b86 fix(import): 修复导入编辑,空格无法编辑的问题 (#735)
fix: 修复点击节点导入出问题的bug

perf(import): 优化使用分页导入

Co-authored-by: ibuler <ibuler@qq.com>
2021-04-13 14:19:07 +08:00
老广
67091d5a22 Merge pull request #733 from jumpserver/dev
Merge Dev v2.9
2021-04-08 06:24:13 -05:00
liuboF2c
798c4ca64e feat:支持配置全局组织的显示名称 (#731)
Co-authored-by: liubo <liubo@fit2cloud.com>
2021-04-08 13:57:26 +08:00
fit2bot
eddd27e95d perf: 优化 csv/xlsx 导入 (#725)
* perf: 优化导入csv

* perf: 优化导入

stash

perf: 优化导入

perf: 更新导入

perf: 优化导入

feat: 完成导入优化

perf: 修复bug

* perf: 继续优化导入,性能提高三倍

Co-authored-by: ibuler <ibuler@qq.com>
2021-04-08 10:09:58 +08:00
dependabot[bot]
12ffa363c1 chore(deps): bump lodash from 4.17.15 to 4.17.19 (#213)
* chore: 更新Submodule指向

* fix: 修复Build失败的问题

* fix(preload): 开启Preload

开启preload,提高首屏加载速度

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: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-07 15:51:21 +08:00
dependabot[bot]
cd79246f0d build(deps): bump http-proxy from 1.18.0 to 1.18.1 (#368)
* build(deps): bump http-proxy from 1.18.0 to 1.18.1

Bumps [http-proxy](https://github.com/http-party/node-http-proxy) from 1.18.0 to 1.18.1.
- [Release notes](https://github.com/http-party/node-http-proxy/releases)
- [Changelog](https://github.com/http-party/node-http-proxy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/http-party/node-http-proxy/compare/1.18.0...1.18.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-07 15:50:29 +08:00
dependabot[bot]
94583e2156 build(deps): bump ini from 1.3.5 to 1.3.7 (#545)
* build(deps): bump ini from 1.3.5 to 1.3.7

Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.7.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.7)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-07 15:44:08 +08:00
dependabot[bot]
ca602a8052 build(deps): bump axios from 0.18.1 to 0.21.1 (#567)
* build(deps): bump axios from 0.18.1 to 0.21.1

Bumps [axios](https://github.com/axios/axios) from 0.18.1 to 0.21.1.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v0.21.1/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.18.1...v0.21.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-07 15:43:11 +08:00
dependabot[bot]
5bdc4e4e3a build(deps): bump elliptic from 6.5.2 to 6.5.4 (#649)
* build(deps): bump elliptic from 6.5.2 to 6.5.4

Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.2 to 6.5.4.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.2...v6.5.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-07 15:41:53 +08:00
dependabot[bot]
da1b73d3fd build(deps): bump y18n from 3.2.1 to 3.2.2 (#721)
* build(deps): bump y18n from 3.2.1 to 3.2.2

Bumps [y18n](https://github.com/yargs/y18n) from 3.2.1 to 3.2.2.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)
2021-04-07 15:35:50 +08:00
Orange
4bc4012520 feat: 支持首页PDF导出,优化打印样式 2021-04-07 15:32:28 +08:00
Orange
b8f1cb7a8e feat: 支持首页PDF导出,优化打印样式 2021-04-07 15:32:28 +08:00
liubo
ee6a3c6d68 feat: 支持添加nutanix云账号 2021-04-07 15:19:26 +08:00
Orange
be176ad408 perf: 用户来源不是本地时禁用更新密码 2021-04-07 11:17:50 +08:00
Orange
73c17fccbe perf: 优化翻译批量命令选择资产时提示资产不支持SSH协议;连接 2021-04-07 11:04:40 +08:00
Orange
30c1284a41 fix: 完善页面邮箱地址校验规则
Closes https://github.com/jumpserver/trello/issues/933
2021-04-07 11:03:09 +08:00
Orange
191900381a fix: 修复资产更多信息更新失败的问题 2021-04-07 11:02:08 +08:00
Orange
91e04a8d18 perf: 优化编译命令 2021-03-29 19:29:06 +08:00
Orange
1b223f0486 fix: 修复命令存储更新失败的问题 2021-03-29 19:27:58 +08:00
Orange
22eb78339e Merge pull request #719 from jumpserver/pr@dev@perf_status
perf: 优化系统监控页面
2021-03-29 19:23:00 +08:00
ibuler
5831cb326c perf: 优化系统监控页面 2021-03-29 16:59:33 +08:00
Orange
52a4c1824f fix: 修复创建K8S系统用户的权限问题 2021-03-23 17:55:06 +08:00
Orange
c155e5a59b perf: 优化全局组织下禁止用户更新用户组 2021-03-23 17:54:15 +08:00
Orange
ff90e56763 fix: 修复创建远程应用默认Path丢失的问题 2021-03-23 17:50:54 +08:00
Orange
14000317b9 fix: 恢复RDP系统用户user_group字段 2021-03-23 17:49:52 +08:00
Orange
a24fab51af Merge pull request #702 from jumpserver/dev
Merge v2.8.1
2021-03-19 19:06:18 +08:00
Orange
4184401432 fix: 修复LDAP属性映射的字段显示问题 2021-03-19 17:40:40 +08:00
Orange
dc59836a66 fix: 修复更多操作数量为空时仍然展示的Bug 2021-03-19 15:08:50 +08:00
Orange
ba6433a585 Merge pull request #699 from jumpserver/dev
v2.8真的发版
2021-03-18 20:47:40 +08:00
fit2bot
b88c90bb75 fix: 全局组织系统用户详情页中禁用添加资产 (#694)
* fix: 去掉组织创建保存并继续按钮

* fix: 全局组织系统用户详情页中禁用添加资产

* fix: 全局组织系统用户详情页中禁用添加资产

* fix: 修复bugs

* fix: 修复bugs

Co-authored-by: Orange <orangemtony@gmail.com>
2021-03-18 20:41:42 +08:00
ibuler
4eb5155aed perf: 优化has valid license 写法 2021-03-18 20:39:37 +08:00
ibuler
dd4aed9cf4 perf: 优化用户中角色显示问题 2021-03-18 20:39:10 +08:00
Bai
2274b65d83 feat: 添加云管中心账号 Azure 国际 2021-03-18 20:20:42 +08:00
ibuler
e0a927c7e1 perf: 去掉more actions 2021-03-18 20:11:24 +08:00
Jiangjie.Bai
d294111e9e Merge pull request #693 from jumpserver/dev
v2.8 发版 (rc6)
2021-03-18 17:44:46 +08:00
ibuler
d6fef086c0 fix: 修复一些license验证问题 2021-03-18 16:12:26 +08:00
Orange
cd6418ef4b fix: 全局组织禁用批量更新及修复翻译问题 2021-03-18 16:12:06 +08:00
Orange
00909b364d fix: 全局组织禁用批量更新和从节点移除 2021-03-18 16:12:06 +08:00
ibuler
cdd3df1562 perf: 优化 relacation card 在root组织下应该disabled的问题 2021-03-18 14:39:02 +08:00
Orange
081f43887b fix: 去掉组织创建保存并继续按钮 2021-03-18 14:09:09 +08:00
Orange
95ba08afa6 fix: 修复LDAP显示隐藏问题 2021-03-18 13:40:50 +08:00
ibuler
88aa17550b perf: 优化 nested field error 2021-03-18 12:39:32 +08:00
Orange
8c3337f581 fix: 补全登录控制更新URl 2021-03-18 10:51:57 +08:00
ibuler
6d0d9650b4 perf: 禁用 detail 更新
perf: terminal 可以更新
2021-03-18 10:51:23 +08:00
Orange
86285c12bd Merge pull request #684 from jumpserver/dev
v2.8-rc4 发版
2021-03-17 20:19:11 +08:00
fit2bot
89b96a4542 fix: 修复前端显示Bugs (#679)
* fix: 修复前端显示Bugs

* fix: 修复引用取值问题

* fix: 还原更新设置

Co-authored-by: Orange <orangemtony@gmail.com>
2021-03-17 20:18:39 +08:00
Orange
707bf497b0 perf: 不可用节点添加提示 2021-03-17 20:12:58 +08:00
Orange
7a0ef53d78 perf: 不可用节点添加提示 2021-03-17 20:12:58 +08:00
Orange
2a0626e4f0 fix: 增加RDP自动推送 2021-03-17 20:11:56 +08:00
Orange
a760d0eeaa fix: 优化应用表单克隆功能 2021-03-17 20:11:07 +08:00
ibuler
f7ccbde502 perf(orgs): 优化用户组织选择,避免在没有启用xpack情况下,切换到root组织中 2021-03-17 19:57:44 +08:00
ibuler
8b350ba819 perf: 优化requet错误日志 2021-03-17 16:08:01 +08:00
Orange
f87d605e13 fix: 修复组织ID变更为000000000002 2021-03-17 16:04:20 +08:00
ibuler
4a38cb7168 fix: 修复hasBulkDelete的问题 2021-03-17 10:48:55 +08:00
Jiangjie.Bai
1ebb8dda0a Merge pull request #675 from jumpserver/dev
Merge v2.8 rc4
2021-03-16 20:49:31 +08:00
Orange
2f9b33898f perf: 增加表格Url初始状态校验 2021-03-16 20:47:44 +08:00
fit2bot
14b854a872 fix: 移除多余的clone (#673)
* fix: 移除多余的clone

* fix: 移除多余的clone

* fix: 移除多余的更多操作

* fix: 移除多余的更多操作

Co-authored-by: Orange <orangemtony@gmail.com>
2021-03-16 19:18:07 +08:00
ibuler
235131fd81 fix(assets): 修复 root 组织节点可以创建的问题 2021-03-16 19:12:54 +08:00
Orange
9b1ba09404 fix: 统一修复批量移除url问题 2021-03-16 19:11:47 +08:00
ibuler
6fc8a43e34 perf: 优化创建storage,使用新的通用组件
perf: 优化工单创建使用新的组件
2021-03-16 19:11:42 +08:00
Orange
324db2fdae fix: 修复 cellValue 取值问题 2021-03-16 19:10:25 +08:00
ibuler
3367363445 perf(orgs): 优化在 root 组织下,可以 克隆和创建的问题
perf: 优化 update, root组织下不能编辑
2021-03-16 18:31:34 +08:00
ibuler
5f6846fa47 perf: 优化创建acl左侧路由高亮 2021-03-16 15:25:55 +08:00
老广
d15292ad0e Merge pull request #665 from jumpserver/dev
Merge V2.8 Rc3
2021-03-15 20:00:09 +08:00
fit2bot
65cd456ae9 fix: 修复密码匣子包含克隆的问题 (#659)
Co-authored-by: Orange <orangemtony@gmail.com>
2021-03-15 19:40:02 +08:00
fit2bot
1748ae760a fix: 修复ACL展示Bugs (#660)
* fix: 修复ACL展示Bugs

* fix: 修复资产ACL更新异常

Co-authored-by: Orange <orangemtony@gmail.com>
2021-03-15 19:39:29 +08:00
Orange
7dc5ec8fa7 fix: 删除管理用户详情页的冗余快捷功能 2021-03-15 19:34:07 +08:00
老广
543e0f7aa7 Merge pull request #663 from jumpserver/pr@dev@fix_system_user
fix: 修复系统用户详情选择协议的Bugs
2021-03-15 19:32:48 +08:00
ibuler
4b152bf9bf fix: 修复root组织可以创建的问题
fix: 修复全局组织可以clone的问题
2021-03-15 19:31:27 +08:00
老广
0afc160b56 Merge pull request #664 from jumpserver/pr@dev@fix_table_action_row_id
fix: 批量修改表格Action取值为Row.id
2021-03-15 19:31:05 +08:00
ibuler
79c89676a6 perf: 优化移除用户和删除用户的行为 2021-03-15 19:30:27 +08:00
Orange
78fa90c9f8 fix: 批量修改表格Action取值为Row.id 2021-03-15 19:21:40 +08:00
Orange
c1ed466b8b fix: 修复系统用户详情选择协议的Bugs 2021-03-15 19:01:08 +08:00
Eric_Lee
35b8181589 Merge pull request #657 from jumpserver/dev
v2.8 rc2
2021-03-12 18:26:43 +08:00
Orange
c172056998 fix: 修复默认优先级 2021-03-12 18:24:20 +08:00
Orange
752e3a7a28 fix: 修复默认优先级 2021-03-12 18:24:20 +08:00
Orange
4935c32bb9 fix: 修改flower路径 2021-03-12 18:10:55 +08:00
老广
7e184a4061 Merge pull request #654 from jumpserver/dev
v2.8 准备发版
2021-03-11 21:19:28 +08:00
fit2bot
30a7063999 perf: 优化各页面列控制 (#652)
perf: 优化页面列表col

Co-authored-by: ibuler <ibuler@qq.com>
2021-03-11 20:24:03 +08:00
ibuler
c94a451df9 fix: 修复 root 组织 下可以创建的问题 2021-03-11 20:21:08 +08:00
fit2bot
f6aab29ecc perf: 增加ACL控制页面 (#651)
* perf: 增加ACL控制页面

* fix: 修改页面

* fix: 修改页面

* fix: 修改页面

* fix: 修改页面

* fix: update

* fix: 完善路由条件

* fix: 完善默认值

* fix: 完善翻译

* fix: 完善表单默认值

* fix: 完善表单默认值

Co-authored-by: Orange <orangemtony@gmail.com>
2021-03-11 20:11:19 +08:00
fit2bot
3ad157848a refactor: 重构命令记录结构 (#622)
* refactor: 重构命令记录结构

* fix: 调整tree构建字段

* fix: 修复首次打开页面报错的问题

* fix: 修复命令记录Tree刷新的问题

* fix: 添加时间戳排序

* fix: 删除多余的Consolelog

Co-authored-by: Orange <orangemtony@gmail.com>
2021-03-11 20:09:37 +08:00
xinwen
e9c54d7eeb feat: 资产授权规则添加是否起作用过滤条件 2021-03-10 15:44:54 +08:00
ibuler
b8b19fed53 perf: 优化嵌套表单的问题 2021-03-10 01:04:39 +08:00
Orange
8dbc7a404f fix: 修复Tag搜索会出现重复项的问题 2021-03-09 18:22:59 +08:00
Orange
a9e95fd705 fix: 增加资产列表可显示字段 2021-03-09 18:21:13 +08:00
Orange
a127b872cc fix: 修复组织管理样式错误 2021-03-09 14:11:03 +08:00
Orange
01aa92adc0 feat: 添加SSH指纹字段显示 2021-03-09 14:07:10 +08:00
fit2bot
0d31318fd5 fix: 修复用户页面无权限显示的问题 (#638)
* fix: 修复用户页面无权限显示的问题

* fix: 修复用户页面无权限显示的问题

Co-authored-by: Orange <orangemtony@gmail.com>
2021-03-09 14:06:28 +08:00
Orange
a595d28a5b fix: 修复显示空更多操作列表的Bug 2021-03-09 12:37:03 +08:00
fit2bot
5d973944ea fix: 修复Dropdown Menu的回调异常 (#642)
* fix: 修复Dropdown Menu的回调异常

* perf: 优化创建

Co-authored-by: Orange <orangemtony@gmail.com>
Co-authored-by: ibuler <ibuler@qq.com>
2021-03-09 12:35:44 +08:00
ibuler
1d008330a1 feat(settings): 支持自定义忘记密码的 url 2021-03-08 16:15:38 +08:00
ibuler
337ff47806 perf: 继续优化action group 2021-03-05 18:53:27 +08:00
fit2bot
e453a9a740 perf: 创建系统用户时需选择协议 (#631)
* perf: 创建系统用户时需选择协议

* feat: 添加action groups

* perf: 优化创建的按钮

Co-authored-by: Orange <orangemtony@gmail.com>
Co-authored-by: ibuler <ibuler@qq.com>
2021-03-05 15:01:04 +08:00
Orange
e4ec8565f0 fix: 修复平台列表默认可删除的错误 (#634)
* fix: 修复平台列表默认可删除的错误
2021-03-05 14:09:59 +08:00
Orange
22904ba421 fix: 修复Default组织ID的问题 2021-03-05 13:59:56 +08:00
Orange
5371faf019 fix: 更新云账号字段 2021-03-04 11:31:28 +08:00
Orange
7c6a3340ad fix: 修复工单匹配Meta字段 2021-03-04 11:05:45 +08:00
Orange
166e66ff9e fix: 优化带Meta的表单数据获取与提交的问题 2021-03-04 11:05:45 +08:00
Orange
1c39d33d43 fix: 修复表格获取ID异常的问题 2021-03-03 19:07:56 +08:00
Orange
159c6d8208 fix: 修复表格获取ID异常的问题 2021-03-03 19:07:56 +08:00
ibuler
917d95cc7b fix: 修复编辑出问题的bug
perf: 优化嵌套的form

perf: 优化嵌套的form

perf: 优化其那套form

perf: 优化nestfield, 但报错存在问题

perf: 优化
2021-03-03 14:48:41 +08:00
fit2bot
6fde735cbd perf: 优化全局组织 (#621)
* feat: 默认组织改为实体组织

* perf: 优化在root组织下,应不能创建

* perf: 优化全局组织

* perf: 优化组织选择

Co-authored-by: ibuler <ibuler@qq.com>
2021-03-03 14:47:44 +08:00
Orange
4ee32dd51b fix: 修复应用授权克隆报错 2021-03-03 13:51:22 +08:00
ibuler
19dc6aa5a0 fix: 修复编辑出问题的bug 2021-03-01 17:12:17 +08:00
fit2bot
8c7f08a971 refactor: 重构云同步账号模块 (#624)
* refactor: cloud (v0.1)

* refactor: 重构云同步账号模块

* fix: 干掉多余的代码

Co-authored-by: Bai <bugatti_it@163.com>
Co-authored-by: Orange <orangemtony@gmail.com>
2021-03-01 15:01:31 +08:00
ibuler
8df3841040 perf(applications): 去掉remote app中的网域 2021-03-01 11:07:48 +08:00
fit2bot
a4a14fecdd refactor: 重构录像存储模块 (#620)
* refactor: 重构录像存储模块

* refactor: 重构录像存储模块

* refactor: 重构录像存储模块

Co-authored-by: Orange <orangemtony@gmail.com>
2021-02-26 14:54:25 +08:00
Orange
52616fead9 fix: 修复RelationCard展示问题 2021-02-26 14:52:11 +08:00
fit2bot
95d0afc5cb feat: 添加模块自定义表格列 (#618)
* feat: 添加资产管理模块自定义表格列

* feat: 添加应用管理模块自定义表格列

* feat: 添加授权模块自定义表格列

* feat: 添加会话模块自定义表格列

Co-authored-by: Orange <orangemtony@gmail.com>
2021-02-26 14:51:14 +08:00
xinwen
07c36e717e refactor: 调整校对节点资产数量 url 2021-02-23 17:29:10 +08:00
ibuler
de393cd2b6 perf(ops): 优化命令执行的输出 2021-02-20 11:09:47 +08:00
Orange
1203941e6b perf: 表格配置存储到localStorage中 2021-02-08 00:28:13 -06:00
Orange
cfa8fcf352 perf: 允许用户修改Source 2021-02-08 00:27:17 -06:00
ibuler
8bfd6b8654 perf: 优化table下拉的配色 2021-02-05 14:49:06 +08:00
ibuler
9840396a6f feat: 修复min的问题 2021-01-29 11:05:07 +08:00
ibuler
309d9379b9 feat: 修改过滤逻辑 2021-01-29 11:05:07 +08:00
Orange
a15ce0b77f feat: 添加自定义表格列功能 2021-01-29 11:05:07 +08:00
Orange
06e80fe75f feat: 添加自定义表格列功能 2021-01-29 11:05:07 +08:00
fit2bot
009be1be83 fix: 临时调整因为Chrome的兼容性问题导致的日期计算错误 (#605)
* fix: 临时调整因为Chrome的兼容性问题导致的日期计算错误

* fix: 修复工单字段展示问题

Co-authored-by: Orange <orangemtony@gmail.com>
2021-01-28 19:29:55 +08:00
Orange
eb20b32fcf fix: 修复工单列表字段展示错误 2021-01-26 04:08:56 -06:00
ibuler
9b19d862f6 perf: 优化ldap配置 2021-01-26 18:01:54 +08:00
ibuler
38b21357b7 perf(settings): 修改翻译 2021-01-26 18:01:54 +08:00
ibuler
c40bd0a9ab perf: 完成终端和安全setting 2021-01-26 18:01:54 +08:00
ibuler
48a7310739 perf(settings): 优化配置页面 2021-01-26 18:01:54 +08:00
Jiangjie.Bai
2c69b36291 Merge pull request #598 from jumpserver/dev
Dev
2021-01-21 15:35:46 +08:00
Orange
d267cd1f5e fix: 移除提交应用申请工单时多余的字段 2021-01-21 15:33:02 +08:00
Orange
807e3a407a fix: 移除重复的权限提醒 2021-01-21 15:33:02 +08:00
Orange
1ba790e680 fix: 调整工单主机名字段变成主机名组 2021-01-21 15:33:02 +08:00
Orange
44b701edbc fix: 修复命令过滤规则创建提示有详情的问题 2021-01-21 15:33:02 +08:00
fit2bot
8619ab8bca fix: 修改测试产生的bugs (#596)
* fix: 修改创建录像存储的条件为必填

* fix: 修改最后一次执行的log类型为Ansible

* fix: 修复批量测试资产可连接性的权限问题

Co-authored-by: Orange <orangemtony@gmail.com>
2021-01-21 12:43:36 +08:00
Jiangjie.Bai
79e92fa46b Merge pull request #595 from jumpserver/dev
Dev
2021-01-20 19:27:59 +08:00
fit2bot
f19c863440 feat: 添加管理用户列表 (#588)
* feat: 添加管理用户列表

* fix: 整理管理用户详情页面

* fix: 整理管理用户详情页面

Co-authored-by: Orange <orangemtony@gmail.com>
2021-01-20 19:07:00 +08:00
fit2bot
fff8b79a45 fix: 修正测试产生的Bugs (#593)
* fix: 调整登录复核工单字段

* fix: 移除创建网关的详情链接展示

* fix: 调整我发起的工单的API字段

* fix: 移除工单申请表单的非必选字段

* fix: 修正创建密码匣子时表单错误

* fix: 调整申请应用时推荐应用对应字段

* fix: 移除旧版本请求

* fix: 优化内部变量写法

* fix: 干掉旧刷新方法

* fix: 优化写法

Co-authored-by: Orange <orangemtony@gmail.com>
2021-01-20 17:32:52 +08:00
fit2bot
cf810b3d3e revert: 回滚表格自定义列功能 (#594)
* revert: 回滚表格自定义列功能

* fix: 优化写法

Co-authored-by: Orange <orangemtony@gmail.com>
2021-01-20 17:12:04 +08:00
fit2bot
f58e37a76a feat: 添加批量测试资产可连接性功能 (#590)
* feat: 添加批量测试资产可连接性功能

* feat: 添加批量测试资产可连接性功能

Co-authored-by: Orange <orangemtony@gmail.com>
2021-01-20 14:56:03 +08:00
ibuler
5889e20aae fix(perms): 修复应用详情中移除系统用户的错误 2021-01-20 14:39:00 +08:00
fit2bot
7caa2c8264 fix: 禁止用户更新用户名与登录名相同选项 (#591)
* fix: 禁止用户更新用户名与登录名相同选项

Co-authored-by: Orange <orangemtony@gmail.com>
2021-01-20 11:27:29 +08:00
Jiangjie.Bai
865388dedc Merge pull request #589 from jumpserver/dev
Dev
2021-01-19 19:52:52 +08:00
Orange
35c1077eed fix: 修复新版工单的Bugs 2021-01-19 19:50:35 +08:00
Orange
487e199995 fix: 修复资产列表无克隆创建的bug 2021-01-19 03:31:23 -06:00
fit2bot
f584e96675 fix: 暂时移除应用的克隆创建 (#587)
Co-authored-by: Orange <orangemtony@gmail.com>
2021-01-19 17:29:17 +08:00
fit2bot
1f4f1d3712 fix: 调整Session过期检查 10mins -> 30s (#586)
* fix: 调整Session过期检查 10mins -> 30s

Co-authored-by: Orange <orangemtony@gmail.com>
2021-01-19 17:23:02 +08:00
fit2bot
08facb1eda fix: 添加WS链接类型 (#585)
* fix: 添加WS链接类型


Co-authored-by: Orange <orangemtony@gmail.com>
2021-01-19 17:12:52 +08:00
Jiangjie.Bai
34cb9424d4 Merge pull request #582 from jumpserver/dev
Dev
2021-01-17 17:59:42 +08:00
fit2bot
36767cd265 feat: 添加组织详情统计 (#580)
* feat: 添加组织详情统计

* feat: 添加组织详情统计

Co-authored-by: Orange <orangemtony@gmail.com>
2021-01-13 20:48:25 +08:00
Orange
86b9fc8f5a fix: 修复工单详情问题 2021-01-13 19:05:50 +08:00
Orange
49054e5dc0 fix: 修复工单详情问题 2021-01-13 19:05:50 +08:00
Orange
bac7cef23c fix: 修复工单详情问题 2021-01-13 19:05:50 +08:00
Orange
4013ea6212 fix: 修复表单生成问题 2021-01-13 18:51:13 +08:00
Orange
37153ebe1d fix: 修复用户界面加载问题 2021-01-13 18:41:51 +08:00
Orange
da35d9be25 修改版权时间 2021-01-12 17:16:33 +08:00
Orange
c3c24b0ad1 feat: 创建资产授权时按协议过滤资产 2021-01-12 17:16:17 +08:00
Orange
98da517724 fix: 移除命令过滤规则的克隆功能 2021-01-12 17:16:04 +08:00
Orange
f002c7f917 feat: 优化重构工单 2021-01-12 15:07:42 +08:00
Orange
0d4e4324ce feat: 重构申请工单系统 2021-01-12 15:07:42 +08:00
Orange
fdeab46970 feat: 重构申请工单系统 2021-01-12 15:07:42 +08:00
Orange
5acbdd5679 feat: 修改录像存储和会话存储接口 2021-01-12 15:07:25 +08:00
Orange
3a64120241 fix: 移除多余的Console.log 2021-01-12 15:04:31 +08:00
Orange
81d1cbf3a1 perf: 修复表格生成Error并记录页码数据 2021-01-12 15:04:31 +08:00
Orange
cec17bbef8 Merge pull request #537 from jumpserver/pr@dev@feat_custom_col_list
feat: 允许用户自定义表格列显示功能
2021-01-12 02:10:29 +08:00
Orange
fbc3373e1b Merge branch 'dev' into pr@dev@feat_custom_col_list 2021-01-12 02:10:02 +08:00
老广
b4a935ab15 Merge pull request #563 from jumpserver/pr@dev@fix_test_storage_notify
fix: 统一测试存储时的命令提醒
2021-01-06 14:33:14 +08:00
老广
7fbff42067 Merge pull request #560 from jumpserver/pr@dev@fix_remove_unuse_func
fix: 移除未使用的函数
2021-01-06 14:08:36 +08:00
老广
5077fec5a8 Merge pull request #564 from jumpserver/pr@dev@fix_create_ticeket
fix: 修复创建工单提示信息
2021-01-06 11:20:27 +08:00
老广
c4619af96f Merge pull request #566 from jumpserver/pr@dev@fix_tree_rightclick
fix: 修复资产树超过屏幕,右键菜单显示不全问题
2021-01-06 11:03:30 +08:00
Orange
025d0abeae fix: 修复资产树超过屏幕,右键菜单显示不全问题 2021-01-05 16:45:22 +08:00
Orange
5735a591ba feat: 允许用户自定义表格列显示功能 2020-12-30 20:08:58 +08:00
Orange
3b664ee1dc feat: 允许用户自定义表格列显示功能 2020-12-30 19:59:23 +08:00
Orange
a3f6de330e fix: 修复创建工单提示信息 2020-12-25 11:15:14 +08:00
Orange
cbc67a5a4c fix: 统一测试存储时的命令提醒 2020-12-23 15:53:44 +08:00
Orange
dec0593907 fix: 移除未使用的函数 2020-12-21 18:23:23 +08:00
Orange
1ed432b1e2 feat: 添加资产编号字段 2020-12-21 14:32:24 +08:00
Jiangjie.Bai
b65664f9c4 Merge pull request #557 from jumpserver/dev
Merge Dev
2020-12-17 18:13:49 +08:00
Orange
f64def0bec fix: 修改箭头颜色 2020-12-17 18:13:09 +08:00
Orange
06f6202bc4 fix: Filter使用后端搜索 2020-12-17 18:13:09 +08:00
Jiangjie.Bai
6fa7800d6b Merge pull request #554 from jumpserver/dev
Chore: Merge Dev
2020-12-17 15:14:11 +08:00
ibuler
54aa252c20 fix(some): 修复没有clone的列表 2020-12-17 15:12:53 +08:00
ibuler
c083f6c4a4 fix(clone): 修复一下clone丢失的问题 2020-12-17 15:12:53 +08:00
Orange
73bb854ebb fix: 去除批量移除, 去除简单写法 2020-12-17 15:03:28 +08:00
Orange
d6f9df277e fix: 修复样式异常和Bug 2020-12-17 15:03:28 +08:00
Jiangjie.Bai
ba78e33f89 Merge pull request #551 from jumpserver/dev
chore: Merge master from dev
2020-12-16 18:42:40 +08:00
Orange
09ef15cff0 fix: 修复工单提示报错 2020-12-16 17:54:38 +08:00
Orange
62d520e625 fix: 修复工单提示报错 2020-12-16 17:54:38 +08:00
Orange
f07a857813 fix: 修复命令过滤器添加系统用户问题 2020-12-16 17:54:38 +08:00
Orange
2699d5e8eb fix: 禁用命令存储文档类型修改 2020-12-16 17:54:38 +08:00
Orange
fb398ca3e4 调整页面errorMessage 2020-12-16 17:54:38 +08:00
Orange
3230c37318 fix: 调整页面保存设置, 优化页面Bug 2020-12-16 17:54:38 +08:00
Jiangjie.Bai
bc258a7ff8 Merge pull request #549 from jumpserver/dev
chore: Merge master from dev
2020-12-15 20:33:14 +08:00
Orange
64d610e282 fix: 修改系统监控页面 2020-12-15 20:32:04 +08:00
Orange
4d95461b5c fix: 添加组件负载状态 2020-12-15 20:32:04 +08:00
Orange
f364c8fdf9 fix: 批量修复2.6版本测试产生的Bug 2020-12-15 20:32:04 +08:00
Orange
88dc2d9271 Merge pull request #547 from jumpserver/dev
fix: 批量修复2.6版本测试产生的Bug
2020-12-14 19:05:38 +08:00
Orange
3aced25da4 fix: 批量修复2.6版本测试产生的Bug 2020-12-14 19:00:16 +08:00
老广
fcf142b696 Merge pull request #544 from jumpserver/dev
Dev
2020-12-11 19:33:54 +08:00
fit2bot
2123037897 fix: 添加组件总量显示 (#543)
Co-authored-by: Orange <orangemtony@gmail.com>
2020-12-11 19:30:58 +08:00
Orange
f5bc2842ec fix: 修复日期显示问题及用户应用授权更新路由问题 2020-12-11 19:28:26 +08:00
Orange
2c95e5f10b fix: 移除中断心跳间隔设置并添加认证方式字段 2020-12-11 19:28:08 +08:00
Jiangjie.Bai
b3ff9c5bcb Merge pull request #539 from jumpserver/dev
chore(merge): dev 合并到 master
2020-12-10 23:47:30 +08:00
Orange
905e5e00b1 fix: 去掉多余JS引用 2020-12-10 23:43:43 +08:00
Orange
81db3d86fa feat: 添加系统监控页面 2020-12-10 23:43:43 +08:00
Orange
ea15515264 feat: 系统用户详情页面增加资产分页面,增加指定资产推送功能 2020-12-10 20:54:14 +08:00
fit2bot
4048a000c7 feat: 添加表格过滤字段选项 (#527)
* feat: 添加表格过滤字段选项

* fix: 如果已有Filter的情况下去掉过滤

Co-authored-by: Orange <orangemtony@gmail.com>
2020-12-10 17:52:12 +08:00
xinwen
a60693c41c perf(asset): 资产树,右击增加计算节点数量的菜单,可以让后台去计算 #527 2020-12-10 17:51:10 +08:00
Orange
06d6c54db8 feat: 允许用户自定义表格列显示功能 2020-12-10 14:26:17 +08:00
fit2bot
57d5c893d3 fix: 修复自动生成表格字段冲突问题 (#536)
Co-authored-by: Orange <orangemtony@gmail.com>
2020-12-09 16:32:52 +08:00
fit2bot
435ce24c75 fix: 修复删除source字段引起的无法更新密码的问题 (#534)
Co-authored-by: Orange <orangemtony@gmail.com>
2020-12-09 16:21:59 +08:00
Orange
4a757bb6bc fix: 不允许用户修改source字段 2020-12-08 20:56:11 +08:00
fit2bot
32fa4f0b11 feat: 资产允许编辑更多信息 (#530)
* feat: 资产允许编辑更多信息

* feat: 资产允许编辑更多资产信息

* feat: 资产允许编辑更多资产信息

* fix: 修改组件名称

Co-authored-by: Orange <orangemtony@gmail.com>
2020-12-08 20:25:54 +08:00
Orange
9f12e1aa18 perf: 禁用应用的导入导出 2020-12-08 11:12:00 +08:00
jym503558564
6165709747 perf(assetBatchOperation): 资产模块添加批量删除操作 2020-12-08 11:10:15 +08:00
fit2bot
0ad1eef196 feat: 添加xlsx格式支持 (#526)
* feat: 添加xlsx格式支持

* feat: 添加xlsx格式支持

* feat: 添加xlsx格式支持

* feat: 添加xlsx格式支持

Co-authored-by: Orange <orangemtony@gmail.com>
2020-12-07 15:22:56 +08:00
Orange
d2d07555b5 fix: 修复批量更新终端存储时附带的保存并继续添加 2020-12-07 14:06:48 +08:00
Orange
7f60224c6d fix: 修复会话时间显示问题 2020-12-07 14:04:26 +08:00
Orange
bbf502c85d fix: 调整select2组件默认长度 2020-12-07 14:03:30 +08:00
Orange
e6aaa52506 perf: 恢复Web终端入口 2020-11-26 12:38:28 +08:00
ibuler
70affacfde fix(list): 修复列表克隆的bug 2020-11-26 12:37:28 +08:00
Orange
40a8da5e58 fix: 修复批量更新组件的问题 2020-11-23 14:00:34 +08:00
Orange
24266bb929 fix: 修复批量批量更新组件的问题 2020-11-22 16:53:01 +08:00
Jiangjie.Bai
14286b961e Merge pull request #514 from jumpserver/dev
Merge dev
2020-11-18 11:49:17 +08:00
Orange
0498db9a8f fix: 修复应用授权过滤系统用户的问题及系统用户创建的问题 2020-11-18 11:47:19 +08:00
ibuler
266d107ffd fix(clone): 修复会话页面产生的clone的问题 2020-11-18 10:55:13 +08:00
ibuler
4f2a9c0c6c fix(clone): 修复会话页面产生的clone的问题 2020-11-18 10:35:40 +08:00
Jiangjie.Bai
affb0ec2bb Merge pull request #509 from jumpserver/dev
Merge Dev
2020-11-17 19:45:03 +08:00
Orange
3075d50357 fix: 修复任务列表字段显示问题 2020-11-17 19:30:36 +08:00
Orange
e49da02c4d fix: 修复RemoteApp详情点击更新失败的问题 2020-11-17 19:07:20 +08:00
Orange
7df5736354 fix: 修改默认版本号以及添加修改应用详情页添加系统用户bug 2020-11-17 18:33:14 +08:00
ibuler
98886149f9 perf(tickets): 优化工单备注 2020-11-17 18:13:40 +08:00
ibuler
abb98d55b9 fix(tickets): 修复工单备注的说明 2020-11-17 18:13:40 +08:00
ibuler
f9c979af88 fix(table): 修复克隆出现在不应该出现的表单上 2020-11-17 18:13:15 +08:00
ibuler
89018a2258 fix(assets|users): 修复批量更新上保存并提交 close #469 2020-11-17 18:13:15 +08:00
Orange
a0c29563ca fix: 显示License过期提示 2020-11-17 16:57:29 +08:00
Orange
21223fddea perf: 优化用户详情中授权的应用列表 2020-11-17 16:54:58 +08:00
Orange
254a2b58cc fix: 用户来源不是本地时禁用更新密码和更新SSH 2020-11-17 16:44:42 +08:00
fit2bot
777c371070 fix: 修复工单提交时必须输入系统用户的提醒 (#503)
* fix: 修复工单提交时必须输入系统用户的提醒

* fix: 去掉多余的console.log

Co-authored-by: Orange <orangemtony@gmail.com>
2020-11-17 16:33:40 +08:00
Orange
0cba2b3116 fix: 修复删除应用的提示框 2020-11-17 16:31:57 +08:00
Orange
c27dd0baef fix: 修复翻译缺失和更新系统用户的表单问题 2020-11-17 16:18:15 +08:00
Orange
990aebefdd Merge pull request #495 from jumpserver/dev
Dev
2020-11-12 16:14:36 +08:00
Orange
6f84312dbe fix: 修改改密计划密码默认长度 2020-11-12 14:26:23 +08:00
Orange
d4c12fb38f fix: 修复CSS和监控禁用逻辑问题 2020-11-12 12:16:49 +08:00
Orange
1bb94824df perf: 添加可中断API字段 2020-11-11 19:21:30 +08:00
Orange
5772430761 fix: 修复翻译和样式Bug 2020-11-11 19:20:53 +08:00
ibuler
790941f361 fix(common): 修复通用form的值的错误 2020-11-11 14:28:43 +08:00
fit2bot
a9ce01ac0e feat: 云管同步增加Azure模块 (#488)
* feat: 云管同步增加Azure模块


Co-authored-by: Orange <orangemtony@gmail.com>
2020-11-10 17:02:20 +08:00
fit2bot
1cdc406e70 fix: 修复生成的表格,不该拥有克隆的,有了克隆 (#490)
* fix: 修复生成的表格,不该拥有克隆的,有了克隆

* fix: 修复生成的表格,不该拥有克隆的,有了克隆

Co-authored-by: ibuler <ibuler@qq.com>
2020-11-10 15:32:11 +08:00
Orange
cb60660272 fix: 修复应用授权翻译 2020-11-10 10:28:30 +08:00
fit2bot
8625e21077 feat(createupdate): 修改和创建的msg增加detail连接 (#487)
* feat(form): 增加可以连续增加的功能

* feat(createupdate): 修改和创建的msg增加detail连接

Co-authored-by: ibuler <ibuler@qq.com>
Co-authored-by: Orange <orangemtony@gmail.com>
2020-11-09 15:37:26 +08:00
fit2bot
2251a1653e feat(all): 增加clone创建 (#485)
* perf(lang): 优化语言切换

* perf: 优化命令获取

* feat(all): 增加clone创建

* fix: 修改排序

Co-authored-by: ibuler <ibuler@qq.com>
Co-authored-by: Orange <orangemtony@gmail.com>
2020-11-09 15:35:43 +08:00
ibuler
ff1debcbce feat(form): 增加可以连续增加的功能 2020-11-09 12:41:03 +08:00
Orange
17e5564cd7 fix: 修复用户界面字段显示问题 2020-11-08 20:40:18 -06:00
fit2bot
615576b3fd perf(passwordExpireTip): 添加密码过期提醒 (#483)
* perf(passwordExpireTip): 初步实现密码过期提醒

* perf(passwordExpireTip): 添加密码过期提醒

Co-authored-by: jym503558564 <503558564@qq.com>
2020-11-09 10:39:27 +08:00
fit2bot
c0d3fbb47a fix: 修复权限认证问题 (#482)
* fix: 修复权限认证问题

* fix: 修复权限认证问题

Co-authored-by: Orange <orangemtony@gmail.com>
2020-11-05 18:59:08 +08:00
ibuler
09075b13b7 perf: 优化命令获取 2020-11-05 18:57:27 +08:00
ibuler
4e92c1a77c perf(lang): 优化语言切换 2020-11-05 18:57:27 +08:00
fit2bot
7cc49bc907 fix: 添加Tree加载重试组件 (#477)
* fix: 添加409错误信息

* fix(perms): 用户授权树重建冲突时弹出提示信息,并保留之前的树

* fix: 添加Tree加载重试组件

Co-authored-by: Orange <orangemtony@gmail.com>
2020-11-05 10:50:38 +08:00
jym503558564
2e2b5bf873 perf(opsTask): 支持批量删除任务信息 2020-11-05 09:53:21 +08:00
Orange
66b1d17dd2 fix: 用户界面隐藏左侧侧边栏web终端入口 2020-11-05 09:52:32 +08:00
Orange
ee26e47c4d fix: 修复CSS样式错误 2020-11-05 09:51:36 +08:00
Orange
aedf6d2158 fix: 修复组织url拼写错误 2020-11-03 18:15:06 +08:00
Orange
6e848e65b4 feat: RDP系统用户增加AD域名字段 2020-11-03 16:13:21 +08:00
Orange
5c0108906c feat: RDP系统用户增加AD域名字段 2020-11-03 16:13:21 +08:00
fit2bot
6c1f8ec8f7 feat: 整理重构应用模块及应用授权模块 (#465)
* feat: 整理重构应用模块及应用授权模块

Co-authored-by: Orange <orangemtony@gmail.com>
2020-11-03 14:28:36 +08:00
Orange
dda36d2b40 fix: 关闭查询保存参数 2020-10-30 17:48:13 +08:00
peijianbo
1abf30c347 feat:危险命令告警 2020-10-28 21:50:24 -05:00
fit2bot
697b5a3d13 perf(permTranslate): 修改翻译 (#462)
* perf(permTranslate): 修改授权一些翻译

* perf(permTranslate): 修改翻译

Co-authored-by: jym503558564 <503558564@qq.com>
2020-10-29 10:49:29 +08:00
jym503558564
74e4c3397e perf(tableWidth): 修改数据库应用列表的宽度 2020-10-28 21:48:34 -05:00
Orange
c227bf59a6 fix: 修复删除节点下资产授权报错的问题 2020-10-28 21:47:56 -05:00
jym503558564
0092f6d6d7 perf(sessionDetail): 修改session详情中的字段翻译 2020-10-28 21:47:31 -05:00
Orange
747477b27c perf: 隐藏左侧边栏Web终端入口 2020-10-28 21:47:00 -05:00
Orange
4943dab50c perf: 去除多余的console.log() 2020-10-28 21:46:28 -05:00
jym503558564
5cac3ee1f7 perf(changeAuthPlan): 改密计划详情中的资产选择,禁选已存在的资产 2020-10-21 12:51:38 +08:00
jym503558564
fa5a227aff perf(renameNode): 修复重命名节点,同名时有两条错误提示信息的问题 2020-10-21 12:51:22 +08:00
jym503558564
792e8595b8 perf(formatterPassword): 统一密码组件 2020-10-21 12:50:55 +08:00
jym503558564
9d62614ff4 perf(formatterPassword): 统一管理用户、系统用户密码组建 2020-10-21 12:50:55 +08:00
jym503558564
48c0f6e8c6 perf(licenseTip): 修复license过期提醒,开源版本出现split提示问题 2020-10-21 12:50:29 +08:00
jym503558564
31b17b384d perf(safari): 修复表格兼容safari,不错位 2020-10-21 12:49:42 +08:00
jym503558564
858d7a9d6f perf(genericCreateUpdate): 修改漏传attr 2020-10-21 12:49:21 +08:00
jym503558564
48b6c48581 perf(userDetail): 统一用户授权详情列表的宽度 2020-10-21 12:48:54 +08:00
八千流
38be9dd367 Merge pull request #443 from jumpserver/pr@dev@perf_userpage_k8s_detail
feat: 用户详情页面添加K8S应用
2020-10-16 09:07:30 +08:00
Orange
7e5570ad72 feat: 用户详情页面添加K8S应用 2020-10-15 18:54:23 +08:00
Orange
16476caa1e Merge pull request #441 from jumpserver/dev
chore: dev to master
2020-10-15 13:05:35 +08:00
八千流
6a5c28ac26 Merge pull request #442 from jumpserver/pr@dev@fix_command_execution
fix: 修复命令执行选择资产报错问题
2020-10-15 13:02:57 +08:00
Orange
5335faa789 fix: 修复命令执行选择资产报错问题 2020-10-15 13:00:27 +08:00
老广
fd1ee6ef7d Merge pull request #439 from jumpserver/pr@dev@fix_badge_hidden
fix: 如果数量为0 隐藏工单Badge
2020-10-14 22:57:40 -05:00
老广
52d8c34bbf Merge pull request #440 from jumpserver/pr@dev@asset_disabled_style
fix: 调整未激活资产展示样式
2020-10-14 22:57:10 -05:00
Orange
9eac41c0c3 fix: 如果数量为0 隐藏工单Badge 2020-10-15 11:45:10 +08:00
Orange
6881316203 fix: 调整未激活资产展示样式 2020-10-15 11:40:11 +08:00
Orange
d4ee8379e8 fix: 如果数量为0 隐藏工单Badge 2020-10-15 10:37:40 +08:00
老广
f64e877491 Merge pull request #435 from jumpserver/dev
chore: Merge Dev to Master
2020-10-14 07:51:05 -05:00
Orange
f46a63cfcf feat: 禁止未激活资产链接 2020-10-14 07:50:34 -05:00
Orange
65a71df10e fix: 在Default组织下隐藏隐藏邀请用户按钮 2020-10-14 19:05:28 +08:00
jym503558564
fd6b0532ba perf(OrgMember): 修复添加组织成员有更多按钮出现的问题 2020-10-14 18:49:11 +08:00
八千流
e02d05a327 Merge pull request #434 from jumpserver/pr@dev@hidden_userpage_tickets
fix: 隐藏用户界面工单
2020-10-14 17:59:24 +08:00
Orange
23740cdce0 fix: 隐藏用户界面工单 2020-10-14 17:57:59 +08:00
Orange
a50f224227 fix: 修复工单数量为0不显示工单按钮的问题 2020-10-14 17:45:47 +08:00
Orange
f8ec327f11 Merge pull request #432 from jumpserver/dev
chore: merge dev to master
2020-10-14 16:04:36 +08:00
ibuler
1d76e037a4 fix: 修复用户邀请 2020-10-14 15:28:58 +08:00
jym503558564
98f5f38694 perf(licenseTip): 管理员有license过期提醒,其它用户无license过期提醒 2020-10-14 15:10:32 +08:00
Orange
b78b95e67a Merge pull request #419 from jumpserver/dev
chore: merge dev to master
2020-10-13 18:53:29 +08:00
Orange
5f60130952 fix: 修复改密计划创建时定期任务创建冲突的问题 2020-10-13 05:49:40 -05:00
Orange
5a82931fc2 fix: 修复时间选择器显示宽度问题 2020-10-13 05:49:00 -05:00
jym503558564
ad49e3250b perf(licenseExpiredTip): 去掉系统设置里面license过期提醒 2020-10-13 17:57:49 +08:00
jym503558564
7ef95f4567 perf(licenseExporeTip): 初步实现license过期提醒 2020-10-13 17:57:49 +08:00
Orange
25a9d21fd7 fix: 修改Tree的刷新Url 2020-10-13 03:06:00 -05:00
Orange
b849df1dc1 fix: 修复表格的时间过滤问题 2020-10-13 03:00:31 -05:00
Orange
1519ccb8e2 fix: 修改添加用户API 2020-10-12 05:40:14 -05:00
fit2bot
47e88e7bb4 perf(org): 添加组织成员合并成一个card (#422)
* perf(org): 添加组织成员合并成一个card

* perf(org): 添加成员后,object需重新刷新才能重新获取新值

Co-authored-by: jym503558564 <503558564@qq.com>
2020-10-12 10:55:56 +08:00
Orange
c86aef999c fix: 调整badge位置 2020-10-11 21:45:41 -05:00
jym503558564
2f9d7ab826 perf(protocol): 批量更新协议组给默认值 2020-10-11 21:41:42 -05:00
fit2bot
b4311b8a59 perf(permSelectAssetCard): 优化授权详情页中的添加资产,将已添加的资产disable掉 (#405)
* perf(permSelectAssetCard): 优化授权详情页中的添加资产,将已添加的资产disable掉

* perf(permSelectAssetCard): 修改全选时,禁用也被选择的bug

* perf(assetSelect): 修改名称

Co-authored-by: jym503558564 <503558564@qq.com>
2020-10-09 11:05:42 +08:00
Jiangjie.Bai
04a97a9923 Merge pull request #418 from jumpserver/dev
Merge Dev
2020-10-09 10:56:15 +08:00
jym503558564
c7624f9092 perf(relationCard): 优化relationCard以及添加组织成员翻译 2020-10-08 21:08:02 -05:00
fit2bot
fc29fc6c6d feat: 工单入口移动至Header栏 (#417)
* feat: 工单入口移动至Header栏

* feat: 工单入口移动至Header栏

Co-authored-by: Orange <orangemtony@gmail.com>
2020-10-09 10:00:23 +08:00
ibuler
7afc501db5 perf: 修改长度 2020-09-30 03:45:31 -05:00
Orange
8ed5672e95 fix: 修复工单审批时系统用户多选参数配置问题 2020-09-29 19:02:13 +08:00
Orange
951c9f56c5 fix: 修复工单审批时系统用户多选参数配置问题 2020-09-29 19:02:13 +08:00
Orange
2c0e079aa2 fix: 修复工单审批时系统用户多选参数配置问题 2020-09-29 16:35:11 +08:00
jym503558564
a8e7ea9c80 perf(ticket): 优化授权工单,支持填写系统用户,审批时系统用户支持选多个 2020-09-29 15:13:10 +08:00
Orange
30143f833a feat: 添加邀请用户进入组织功能 (#411)
* feat: 添加邀请用户进入组织功能
2020-09-29 15:10:48 +08:00
ibuler
ad88daef9a chore: merge xpack dev pr 2020-09-29 14:26:05 +08:00
ibuler
9eb80eb6ca chore: add package 2020-09-29 14:26:05 +08:00
Orange
112de6e81c feat: 添加403请求拦截器
Closes https://github.com/jumpserver/trello/issues/238
2020-09-28 16:51:57 +08:00
Orange
d01f903a9e fix: 临时禁用树节点移动
Closes https://github.com/jumpserver/lina/issues/383
2020-09-28 16:50:39 +08:00
Orange
af6d0aff7c fix: 使用http-equiv功能禁止index.html缓存 2020-09-28 16:49:02 +08:00
Orange
edf8621e8f fix: 修复版本更新Index.html不刷新的问题 2020-09-28 16:49:02 +08:00
Orange
eeb15c624a fix: 修复组织名称更新列表未刷新的问题 2020-09-28 16:48:15 +08:00
Orange
79e2d49a3d fix: 修复页面title设置不生效问题
Closes https://github.com/jumpserver/trello/issues/302
2020-09-28 16:47:22 +08:00
Orange
628395e447 fix: 修复命令存储更新问题 2020-09-24 15:54:23 +08:00
八千流
6cb6e6444b Merge pull request #394 from jumpserver/pr@dev@fix_command_filter
feat: 修复命令过滤详情添加系统用户的问题
2020-09-22 15:09:19 +08:00
八千流
1b9aa23761 Merge pull request #397 from jumpserver/pe@dev@fix_change_orgs_url
fix: 修复更改组织时路径跳转的问题
2020-09-22 15:08:56 +08:00
jym503558564
537c385ecf perf(licenseExpired): 优化license过期提醒 2020-09-22 15:06:39 +08:00
jym503558564
c0f7d6e7ff perf(translate): 修改界面设置的一些字段翻译 2020-09-22 15:05:41 +08:00
jym503558564
78df96f888 feat(batchUpdateProtocols): 新增批量更新协议组 2020-09-22 15:05:08 +08:00
jym503558564
829957090d fix(gateway): 修复测试网关端口数据类型:String改为Int类型 2020-09-22 15:04:42 +08:00
jym503558564
026c8f37ea perf(navHeader): 在页面右上角添加web终端 2020-09-22 15:04:02 +08:00
Orange
97340c6aac feat: 修复更改组织时路径跳转的问题 2020-09-18 15:18:55 +08:00
Orange
bbe54eae48 fix: 修复Build失败的问题 2020-09-18 11:43:19 +08:00
Orange
9137207055 feat: 修复命令过滤详情添加系统用户的问题 2020-09-17 15:10:30 +08:00
Jiangjie.Bai
0ef30ef651 Merge pull request #392 from jumpserver/dev
Merge Dev
2020-09-17 12:11:23 +08:00
Orange
5ae8a6c9e4 fix: 禁用命令记录列表导出选择项 2020-09-17 12:10:46 +08:00
Orange
ace1dcd0b8 fix: 修复切换组织时权限展示问题
Closes https://github.com/jumpserver/trello/issues/332
2020-09-17 11:06:40 +08:00
Jiangjie.Bai
5df487d6bd Merge pull request #390 from jumpserver/dev
chore: 去掉submodule
2020-09-15 17:21:32 +08:00
ibuler
6e548749e1 Removed submodule 2020-09-15 16:26:11 +08:00
ibuler
99885d9f28 chore: 去掉submodule 2020-09-15 16:16:11 +08:00
Orange
83163e11e3 Merge pull request #387 from jumpserver/dev
Merge dev
2020-09-15 16:02:49 +08:00
Orange
8c191fee67 Merge pull request #386 from jumpserver/pr@dev@feat_hidden_tickets
perf: 优化license的路由判断
2020-09-15 15:17:32 +08:00
ibuler
ff9862fa06 perf: 优化license的路由判断 2020-09-15 15:14:07 +08:00
ibuler
db0cea7051 feat: 可以设置隐藏工单 2020-09-15 14:55:38 +08:00
Orange
305c713a57 Merge pull request #382 from jumpserver/dev
chore: 更新Xpack版本
2020-09-08 20:35:35 +08:00
Orange
8a5f93e268 chore: 更新Xpack版本 2020-09-08 20:34:19 +08:00
Jiangjie.Bai
c4d262150b Merge pull request #374 from jumpserver/dev
Merge dev to master
2020-09-08 20:28:47 +08:00
Jiangjie.Bai
94f161f7e6 Merge pull request #380 from jumpserver/pr@dev@merge_Master
修复Merge冲突
2020-09-08 20:26:52 +08:00
Orange
2c5bfb3f4c 修复Merge冲突 2020-09-08 20:24:35 +08:00
Orange
8e12837a77 fix: 修复请求计时器时间问题 2020-09-08 20:22:29 +08:00
ibuler
4385d84f01 feat: 请求时刷新session age 2020-09-08 20:15:42 +08:00
Orange
214bb28c4c chore: 更新Submodule指向 2020-09-08 20:14:56 +08:00
Orange
14c2285ac8 Merge pull request #375 from jumpserver/pr@master@update_xpack_brench
chore: 更新Submodule指向
2020-09-08 20:11:41 +08:00
Orange
18cbe578f0 chore: 更新Submodule指向 2020-09-08 20:09:09 +08:00
fit2bot
e19ded8365 bug(userDetail): 修复用户详情删除其某条授权规则失败的问题 (#371)
Co-authored-by: jym503558564 <503558564@qq.com>
2020-09-08 19:20:24 +08:00
fit2bot
0150008075 fix: 修复备注过长的显示问题 (#373)
Closes https://github.com/jumpserver/lina/issues/341

Co-authored-by: Orange <orangemtony@gmail.com>
2020-09-08 19:19:20 +08:00
Orange
b0bca65cab fix: 修复工单列表提交的问题
Closes https://github.com/jumpserver/trello/issues/312
2020-09-08 12:17:49 +08:00
jym503558564
cb95b9ba4f perf(assetCreateUpdate): 优化资产创建或更新页面的ip字段翻译 2020-09-07 20:05:25 +08:00
jym503558564
4a840288c5 pref(assetSelect): 优化资产选择表单字段翻译 2020-09-07 20:05:25 +08:00
ibuler
11b1d6638d perf(xpack): 优化license导入 2020-09-07 20:04:07 +08:00
Bai
2225807a36 feat(tickets): 工单添加comment字段 2020-09-07 20:01:22 +08:00
Bai
a8baca81d5 feat(i18n): 添加云同步实例任务的hostname_strategy翻译 2020-09-07 17:52:05 +08:00
jym503558564
30161c7178 perf(pref_ticket_badge): 优化badge的背景色 2020-09-07 17:41:33 +08:00
jym503558564
e222d147f6 pref(tickerBadge): 在ticket页面添加badge 2020-09-07 17:41:33 +08:00
jym503558564
3ddc41707c pref(storage): 修改录像存储的配置 2020-09-03 12:51:47 +08:00
jym503558564
a245055150 pref(storageHelpText): 添加录像存储endpoint提示 2020-09-03 12:51:47 +08:00
Orange
ba5fdf2027 fix: 修复LDAP部分问题 2020-09-03 12:50:50 +08:00
jym503558564
e1999e5ce8 pref(textarea): 优化textarea的minRows为3 2020-09-02 14:23:22 +08:00
jym503558564
be4e0b5e35 pref(formRequired): 统一form required 2020-09-02 14:22:50 +08:00
八千流
82c381b80d Merge pull request #359 from jumpserver/revert-356-pr@dev@pref_ticket_comment
Revert "pref(ticket): 优化工单评论显示,我的回复显示在右边,其它回复均显示在左边"
2020-09-02 10:16:53 +08:00
老广
7d71aa96b9 Revert "pref(ticket): 优化工单评论显示,我的回复显示在右边,其它回复均显示在左边 (#356)"
This reverts commit cea03df4eb.
2020-09-02 10:15:02 +08:00
Orange
93408e52c1 feat: 导入组件时按钮显示加载状态
Closes https://github.com/jumpserver/trello/issues/87
2020-09-01 11:45:59 +08:00
fit2bot
cea03df4eb pref(ticket): 优化工单评论显示,我的回复显示在右边,其它回复均显示在左边 (#356)
* pref(ticket): 优化工单评论显示,我的回复显示在右边,其它回复均显示在左边

* pref(ticket): 优化评论显示

* pref(ticket): 修改方法名

Co-authored-by: jym503558564 <503558564@qq.com>
2020-09-01 11:34:43 +08:00
fit2bot
72ee5f60b9 pref(permission): 资产授权详情页添加动作字段 (#353)
* pref(permission): 资产授权详情页添加动作字段

* pref(permission): 修改翻译

Co-authored-by: jym503558564 <503558564@qq.com>
2020-09-01 11:14:58 +08:00
jym503558564
f6a8e5634b pref(userProfile): 优化ssh公钥textarea字段为自适应高度 2020-09-01 11:00:00 +08:00
Orange
868e77c983 feat: 添加测试网关弹窗
Closes https://github.com/jumpserver/trello/issues/191
2020-09-01 10:57:57 +08:00
jym503558564
2a3fd42ca1 pref(storage): 优化存储表单页面 2020-08-31 13:45:11 +08:00
jym503558564
596a26bfb6 pref(session): 优化会话列表字段翻译 2020-08-31 13:40:19 +08:00
Orange
2fc8cea9ef fix: 修复K8S系统用户更新时令牌是必填项的问题
Closes https://github.com/jumpserver/trello/issues/276
2020-08-28 18:03:20 +08:00
Orange
6f8a5c2bfc fix: 授权Actions组件默认折叠
Closes https://github.com/jumpserver/trello/issues/287
2020-08-28 18:00:46 +08:00
Orange
260901351f 修复Core设置SECURITY_VIEW_AUTH_NEED_MFA失效的问题 2020-08-28 17:59:45 +08:00
Orange
66845e58db 修复Core设置SECURITY_VIEW_AUTH_NEED_MFA失效的问题 2020-08-28 17:59:45 +08:00
OrangeM21
0e9e549bea fix: 修改样例中的端口号 2020-08-20 16:51:46 +08:00
OrangeM21
785611414e fix: 修改样例中的端口号 2020-08-20 16:50:38 +08:00
OrangeM21
19267ee001 fix: 更新Xpack代码 2020-08-20 01:42:47 -05:00
老广
65326916ca chore: merge dev to master (#338)
* fix(systemUser): 修复创建k8s系统用户的问题 (#315)

Co-authored-by: jym503558564 <503558564@qq.com>

* fix(csrfToken): 更新CsrfToken的获取方式,改为从Cookie中获取

* pref(i18n): 修改工单详情页的字段翻译

* fix(systemUserCreate): 优化系统用户创建页面

* fix: 通用导出组件提供默认选项

Closes https://github.com/jumpserver/trello/issues/253

* fix: 修复工单列表的状态展示

Closes https://github.com/jumpserver/trello/issues/198

* fix: 修复工单关闭请求问题

* update

* update

* update

* update

* pref(K8s): 统一Kubernetes翻译

* pref(settings): 优化系统设置提示英文的问题

* fix: 优化创建系统用户时表单联动

Closes https://github.com/jumpserver/trello/issues/180

* fix: 修改翻译

其他 => 其它

Closes https://github.com/jumpserver/trello/issues/181

* fix: 创建用户字段显示隐藏问题

* update

* update

* update

* fix: 修改添加组织表单

* fix: 更新密码后刷新表单

* fix: 修改Xpack指向最新代码

* fix: 修改工单备注提交结构

* fix: 调整工单翻译以及角色顺序

* fix: 更新翻译

* fix: 更新翻译

* fix: 修复Safari时间显示问题

Closes https://github.com/jumpserver/trello/issues/237

* Update common.js

Co-authored-by: fit2bot <68588906+fit2bot@users.noreply.github.com>
Co-authored-by: jym503558564 <503558564@qq.com>
Co-authored-by: OrangeM21 <orangemtony@gmail.com>
2020-08-20 14:42:11 +08:00
fit2bot
0b925ccf33 pref(xpack): 修改lina master xpack 指向 (#339)
Co-authored-by: jym503558564 <503558564@qq.com>
2020-08-20 14:12:37 +08:00
Orange
58505d2b50 Update common.js 2020-08-19 23:42:17 -05:00
OrangeM21
ecae504a80 fix: 修复Safari时间显示问题
Closes https://github.com/jumpserver/trello/issues/237
2020-08-19 23:42:17 -05:00
OrangeM21
844bc5b44f fix: 更新翻译 2020-08-19 22:22:39 -05:00
OrangeM21
c1dcf82fbd fix: 更新翻译 2020-08-19 22:22:39 -05:00
OrangeM21
336406ddff fix: 调整工单翻译以及角色顺序 2020-08-19 21:51:41 -05:00
OrangeM21
81e8e650bf fix: 修改工单备注提交结构 2020-08-19 09:12:39 -05:00
OrangeM21
af6308e1b3 fix: 修改Xpack指向最新代码 2020-08-19 09:11:34 -05:00
OrangeM21
aae552f374 fix: 更新密码后刷新表单 2020-08-19 08:48:51 -05:00
OrangeM21
b4c1ee786a fix: 修改添加组织表单 2020-08-19 08:48:26 -05:00
OrangeM21
61da88114d update 2020-08-19 07:38:14 -05:00
OrangeM21
279859ce81 update 2020-08-19 07:38:14 -05:00
OrangeM21
820bb075a3 update 2020-08-19 07:38:14 -05:00
OrangeM21
d96bd76ca9 fix: 创建用户字段显示隐藏问题 2020-08-19 07:38:14 -05:00
OrangeM21
7c56c889f2 fix: 修改翻译
其他 => 其它

Closes https://github.com/jumpserver/trello/issues/181
2020-08-19 07:37:25 -05:00
OrangeM21
3547fb26ad fix: 优化创建系统用户时表单联动
Closes https://github.com/jumpserver/trello/issues/180
2020-08-19 07:36:57 -05:00
jym503558564
ac1363b377 pref(settings): 优化系统设置提示英文的问题 2020-08-19 07:36:11 -05:00
jym503558564
6b5c90ee86 pref(K8s): 统一Kubernetes翻译 2020-08-19 07:35:39 -05:00
OrangeM21
8164fa57ef update 2020-08-19 07:34:31 -05:00
OrangeM21
96c9f229e2 update 2020-08-19 07:34:31 -05:00
OrangeM21
58313f5fe0 update 2020-08-19 07:34:31 -05:00
OrangeM21
738a9c3da1 update 2020-08-19 07:34:31 -05:00
OrangeM21
0f10ed9ffc fix: 修复工单关闭请求问题 2020-08-19 07:34:31 -05:00
OrangeM21
294e05cb06 fix: 修复工单列表的状态展示
Closes https://github.com/jumpserver/trello/issues/198
2020-08-19 07:32:19 -05:00
OrangeM21
1598dcbfbc fix: 通用导出组件提供默认选项
Closes https://github.com/jumpserver/trello/issues/253
2020-08-19 07:31:00 -05:00
jym503558564
cf2d6a47c2 fix(systemUserCreate): 优化系统用户创建页面 2020-08-19 07:30:31 -05:00
jym503558564
c8fb334dae pref(i18n): 修改工单详情页的字段翻译 2020-08-19 07:27:55 -05:00
OrangeM21
93dba0bbee fix(csrfToken): 更新CsrfToken的获取方式,改为从Cookie中获取 2020-08-19 07:27:30 -05:00
fit2bot
2832d876fd fix(systemUser): 修复创建k8s系统用户的问题 (#315)
Co-authored-by: jym503558564 <503558564@qq.com>
2020-08-17 10:33:19 +08:00
jym503558564
5cd89cee6a fix(xpack): 更新master xpack 指向 2020-08-14 14:38:29 +08:00
老广
2f69861361 chore: merge to master
Merge Dev to master
2020-08-14 12:26:03 +08:00
fit2bot
8f51d9b0ea fix(remoteApp): 创建mysql_workbench表单添加端口字段 (#307)
Co-authored-by: jym503558564 <503558564@qq.com>
2020-08-13 17:06:22 +08:00
OrangeM21
3a2b6d79fb fix: 去掉API请求的加载条 2020-08-13 15:36:23 +08:00
fit2bot
67ede69685 fix(xpack): 修改Xpack指向 (#305)
Co-authored-by: jym503558564 <503558564@qq.com>
2020-08-13 12:43:50 +08:00
fit2bot
17c4c9b2ef feat(sytemUser): 优化系统用户页面,新增home和groups字段 (#293)
* feat(sytemUser): 优化系统用户页面,新增home和groups字段

* feat(systemUser): 优化创建系统用户页面

* feat(systemUsers): 修改用户组字段

* feat(systemUser): 修改用户组翻译

* fix(systemUser): 去掉必填

* fix(systemUser): 系统用户表单页面,当用户名与用户相同,则username不是必填

Co-authored-by: jym503558564 <503558564@qq.com>
2020-08-12 18:19:32 +08:00
fit2bot
0319d43942 perf(kubernetes): 优化kubernetes更新页面翻译 (#304)
Co-authored-by: jym503558564 <503558564@qq.com>
2020-08-12 17:57:19 +08:00
jym503558564
dcd088fd58 perf(kubernetes): 优化kubernetes页面 2020-08-12 14:31:23 +08:00
jym503558564
9875ded710 perf(listTable): 优化ip字段宽度,以及修改kubernetes apps翻译 2020-08-12 14:31:23 +08:00
jym503558564
e26cd95ef9 perf(detailCard): 优化详情页bool值字段的显示 2020-08-12 12:22:53 +08:00
fit2bot
1bb8e8c709 fix: 创建K8S系统用户时允许填写username (#301)
* fix: 创建K8S系统用户时允许填写username


Co-authored-by: OrangeM21 <orangemtony@gmail.com>
2020-08-11 19:51:44 +08:00
OrangeM21
09617fa606 feat: 添加用户界面K8S应用 2020-08-11 16:45:39 +08:00
fit2bot
62a6d11332 perf: 优化用户列表和用户创建的角色 (#291)
* perf: 优化用户角色

Closes https://github.com/orgs/jumpserver/projects/1

* perf: 优化用户角色

Closes https://github.com/orgs/jumpserver/projects/1

Co-authored-by: OrangeM21 <orangemtony@gmail.com>
2020-08-11 11:06:22 +08:00
fit2bot
8f00dbf23e feat(kubenetes): 添加kubenetes应用 (#292)
* feat(kubenetes): 添加kubenetes应用

* feat(Kubernetes): 添加K8S应用支持

* feat(Kubernetes): 添加K8S应用支持

Co-authored-by: OrangeM21 <orangemtony@gmail.com>
2020-08-11 11:01:14 +08:00
jym503558564
8fd624e0b7 fix(opsTask): 去掉任务详情中多余的ID字段 2020-08-11 10:32:25 +08:00
jym503558564
cef6521a2b fix(userCreateUpdate): 修复创建用户时生成密码的问题 2020-08-10 15:10:07 +08:00
OrangeM21
da1217972a fix: 修正创建用户时生成密码的问题 2020-08-07 12:11:43 +08:00
OrangeM21
9efacb68b6 fix: 修复工单提交时候方法冲突问题及删掉多余函数 2020-08-07 12:10:52 +08:00
fit2bot
b3c22f96d8 perf(tagSearch): 优化搜索组件增加编辑选项功能 (#286)
* perf(tagSearch): 优化搜索组件增加编辑选项功能

* 优化Tag细节

* 优化Tag细节

Co-authored-by: OrangeM21 <orangemtony@gmail.com>
2020-08-07 12:10:13 +08:00
OrangeM21
6bf15655b7 perf(ticket): 优化工单细节
Closes https://github.com/orgs/jumpserver/projects/1
2020-08-07 12:02:03 +08:00
jym503558564
ee3dc30985 feat(xpackOrg): 添加组织成员列表页面的翻译 2020-08-06 17:41:11 +08:00
OrangeM21
21da017f8e fix: 修复上传组件模板下载失败问题 2020-08-06 17:40:21 +08:00
OrangeM21
38b4810d9e perf(sass): 替换node-sass为sass
BREAKING CHANGE: 替换node-sass为sass
2020-08-04 13:42:03 +08:00
fit2bot
d49aae69ab fix(Tickets): 调整工单详情样式 (#281)
* fix(Tickets): 调整工单详情样式

Co-authored-by: OrangeM21 <orangemtony@gmail.com>
2020-08-04 13:40:08 +08:00
OrangeM21
012fefa3ea fix(zTree): 修复zTree销毁冲突问题
Closes https://github.com/jumpserver/lina/issues/280
2020-08-04 11:41:48 +08:00
fit2bot
1f91b9a72f perf(Router): 优化外链路由 (#268)
* perf(Router): 优化外链路由

* perf(Router): 优化外链路由

* 替换BASEURL为BASE_URL

Co-authored-by: OrangeM21 <orangemtony@gmail.com>
2020-07-31 11:57:04 +08:00
八千流
e061d9eb75 Merge pull request #277 from jumpserver/pr@dev@hotfix_build_error
fix(ticket): 修复Merge冲突造成的编译问题
2020-07-31 11:15:48 +08:00
OrangeM21
00cd04e103 fix(ticket): 修复Merge冲突造成的编译问题 2020-07-31 11:12:31 +08:00
Orange
4b3b1a723f feat: 添加申请资源工单功能 (#185)
* [Feature] 添加申请资产工单

* feat: 添加资产申请工单功能

* update

* feat: 添加申请资源工单功能

* feat: 添加申请资源工单功能

* feat: 添加申请资源工单功能

* feat: 添加申请资源工单功能

* fix(终端列表): 还原终端列表的代码

* fix: 修改申请资源工单功能

* fix: 修改申请资源工单功能

* fix: 修改申请资源工单功能

* feat: 添加请求资产权限工单

* Update cn.json

* Update en.json

Co-authored-by: xinwen <coderWen@126.com>
2020-07-31 10:51:17 +08:00
OrangeM21
2d3a43c202 fix(dialog): 修复当有搜索条件时禁用导出所有选项
Closes https://github.com/jumpserver/trello/issues/74
2020-07-31 10:49:22 +08:00
fit2bot
e8e751668d feat(terminalStorage): 添加批量更新终端存储功能 (#247)
* feat(terminalStorage): 添加批量更新终端存储功能

Closes https://github.com/jumpserver/jumpserver/issues/4392
Closes https://github.com/jumpserver/jumpserver/issues/4172

* update

* update

Co-authored-by: OrangeM21 <orangemtony@gmail.com>
2020-07-31 10:46:57 +08:00
OrangeM21
c6e0a17aaa fix: 修复创建远程应用密码框显示明文的问题
Closes https://github.com/jumpserver/trello/issues/133
2020-07-31 10:44:30 +08:00
OrangeM21
066d81446c fix(preload): 开启Preload
开启preload,提高首屏加载速度
2020-07-31 10:37:13 +08:00
fit2bot
a63b07cf2e feat: 个人界面添加更新公钥快捷按钮 (#271)
* feat: 个人界面添加更新公钥快捷按钮

Closes https://github.com/jumpserver/jumpserver/issues/4361

* feat: feat: 个人界面添加更新公钥快捷按钮

Co-authored-by: OrangeM21 <orangemtony@gmail.com>
2020-07-31 10:32:26 +08:00
jym503558564
0ab96fa413 fix(detailCard): 修复详情页数据如果没有ID字段,则不显示ID 2020-07-31 10:30:27 +08:00
ibuler
e064c1cfc4 ci(docker): 使用docker缓存,使用npm mirror 2020-07-29 16:07:15 +08:00
jym503558564
2913699597 fix(tableList): 优化列表组件一些字段的宽度 2020-07-28 16:04:33 +08:00
jym503558564
c413623a22 fix(sessions_cmd_storage): 优化创建命令存储的提示文案 2020-07-28 16:03:15 +08:00
jym503558564
92f08605df fix(sessions_cmd_storage): 优化创建命令存储的提示文案 2020-07-28 16:03:15 +08:00
jym503558564
3fb32ad81c fix(sessions_cmd_storage): 优化创建命令存储的提示文案 2020-07-28 16:03:15 +08:00
jym503558564
760dbad5ac fix(ticket): 优化工单详情 2020-07-28 16:00:58 +08:00
jym503558564
878933bc07 fix(ticket): 优化工单详情 2020-07-28 16:00:58 +08:00
Orange
603ebff771 Merge pull request #217 from jumpserver/pr_dev_i18n_xpack_cloud
feat(AddTranslate): 添加云管账户详情翻译
2020-07-28 15:57:28 +08:00
jym503558564
c23ed70df4 fix(xpack):修改xpack指向 2020-07-28 15:56:18 +08:00
jym503558564
d21598cf1c fix(xpack): 更新xpack指向 2020-07-27 17:41:14 +08:00
OrangeM21
34f1b5d662 fix(CommandExecution): 修复命令过滤执行的系统用户选择 2020-07-24 11:32:37 +08:00
jym503558564
c493aca11b fix(assets): 优化资产详情中网域字段名的显示 2020-07-24 11:01:55 +08:00
jym503558564
cc937d600b fix(permissions): 优化授权列表各列宽度 2020-07-24 10:58:48 +08:00
OrangeM21
fcbe61cb92 fix(el-data-table): 修复翻页搜索结果不正确的问题
Closes https://github.com/jumpserver/trello/issues/69
Closes https://github.com/jumpserver/trello/issues/68
2020-07-24 10:53:26 +08:00
jym503558564
b39f01a023 fix(detailCard): detailCard 增加ID字段 2020-07-24 10:48:43 +08:00
jym503558564
794ad35f84 fix(sessionDetailTimeFormat): 优化会话详情中时间字段的显示格式 2020-07-24 10:46:32 +08:00
fit2bot
729b07798e feat(Clipboard): 添加剪切板权限控制 (#238)
Co-authored-by: OrangeM21 <orangemtony@gmail.com>
2020-07-24 10:44:47 +08:00
fit2bot
f2514f68b8 fix(select2): 默认添加clearable属性 (#240)
* fix(select2): 默认添加clearable属性


Co-authored-by: OrangeM21 <orangemtony@gmail.com>
2020-07-24 10:35:41 +08:00
fit2bot
30d4044d20 fix(perms): 优化授权列表添加刷新按钮 (#245)
* fix(perms): 优化授权列表添加刷新按钮

* fix(perms): 优化授权列表添加导入导出按钮

Co-authored-by: jym503558564 <503558564@qq.com>
2020-07-24 10:20:37 +08:00
jym503558564
e7cd6e49e8 fix(Xpack): 更新xpack指向 2020-07-21 15:45:03 +08:00
OrangeM21
75276d37e4 fix: 添加form组件的refs 2020-07-21 15:04:24 +08:00
Orange
5a92c4f3ee Merge pull request #228 from jumpserver/pr@dev@ci_add_generic_handler
ci(github): 添加通用action
2020-07-21 13:08:36 +08:00
Orange
ae6fb22fae Merge pull request #229 from jumpserver/pr@master@ci_add_generic_handler
ci(github): 添加通用action
2020-07-21 13:07:55 +08:00
github-actions
ccd98606a1 ci(github): 添加通用action 2020-07-21 05:06:02 +00:00
github-actions
7f2ed5d038 ci(github): 添加通用action 2020-07-21 05:05:59 +00:00
jym503558564
d24741ab4b feat(AddTranslate): 添加云管账户详情翻译 2020-07-20 11:38:19 +08:00
Orange
9ac5eabff2 Merge pull request #212 from jumpserver/pr_dev_ci-change
ci(pull-request): 添加自动pr打标签功能
2020-07-17 16:14:52 +08:00
ibuler
18b5fafa41 ci(pull-request): 添加自动pr打标签功能 2020-07-17 16:10:02 +08:00
Orange
8a76bb05ac Merge pull request #208 from jumpserver/jym_bug_fix
fix(settings): 修复安全设置页面更新不了的问题
2020-07-16 15:49:18 +08:00
Orange
ad5b5c7c20 Merge pull request #207 from jumpserver/jym_bug_fix
fix(settings): 修复安全设置页面更新不了的问题
2020-07-16 15:48:40 +08:00
jym503558564
fe7d43f669 fix(settings): 修复安全设置页面更新不了的问题 2020-07-16 15:35:42 +08:00
八千流
aad23f3de5 Merge pull request #202 from jumpserver/dev
merge(master): Merge from dev to master
2020-07-16 11:12:33 +08:00
八千流
9c33093b01 Merge pull request #205 from jumpserver/update_xpack
update:Xpack
2020-07-16 11:11:00 +08:00
OrangeM21
c94a5dfa1f update:Xpack 2020-07-16 11:08:24 +08:00
八千流
143973531c Merge pull request #204 from jumpserver/update_xpack_master
fix: 更新Master Xpack代码
2020-07-16 11:05:29 +08:00
OrangeM21
7db590950c fix: 更新Master Xpack代码 2020-07-16 11:04:13 +08:00
八千流
7e65a52062 Merge pull request #203 from jumpserver/update_xpack
fix: 更新Xpack代码
2020-07-16 11:01:05 +08:00
OrangeM21
226d118d28 fix: 更新Xpack代码 2020-07-16 10:58:49 +08:00
老广
0e3bd186fe Merge pull request #201 from jumpserver/v2.0
Merge v2.0
2020-07-16 10:44:27 +08:00
八千流
50e8dfc86d fix(timeFormat): 统一时间格式 (#197)
* fix(timeFormat): 统一时间格式

* fix: 更新Xpack代码

Co-authored-by: OrangeM21 <orangemtony@gmail.com>
2020-07-15 20:46:01 +08:00
OrangeM21
a023e03074 fix: 更新Xpack代码&&用户界面资产添加备注选项
Closes https://github.com/jumpserver/trello/issues/75
2020-07-15 20:43:39 +08:00
OrangeM21
a0a592f064 fix(AssetUpdate): 创建资产网域选项可清空
Closes https://github.com/jumpserver/lina/issues/141
2020-07-15 20:19:15 +08:00
八千流
4d86edd65e fix(ldapUserListFix): 优化ldap用户列表勾选后关闭页面,再次打开时,上一次选中的状态还存在的问题 (#192)
* fix(ldapUserListFix): 优化ldap用户列表勾选后关闭页面,再次打开时,上一次选中的状态还存在的问题
2020-07-15 18:34:17 +08:00
Orange
c597bc1dca fix(SessionList): 修复命令详情参数及在线会话快捷操作组件 (#198)
* fix(SessionList): 修复命令详情参数及在线会话快捷操作组件

Closes https://github.com/jumpserver/trello/issues/72

* fix: 更新API参数
2020-07-15 18:31:13 +08:00
OrangeM21
a3d45fd4b9 fix(UserProfile): 修复登录密码后没有立即提示退出登录等问题
Closes https://github.com/jumpserver/trello/issues/23
2020-07-15 18:23:00 +08:00
jym503558564
e16d3c2e3f fix(changePasswd): 添加changePassword创建页面中的翻译 2020-07-15 18:21:17 +08:00
OrangeM21
e251127f6e fix(AssetUserTable): 去掉删除资产的前端提示,改为由后端提示
Closes https://github.com/jumpserver/trello/issues/30
2020-07-15 18:19:20 +08:00
jym503558564
58b8917739 fix(ldap): 修复LDAP一键导入不选择用户,导入了全部用户的问题 2020-07-15 18:18:26 +08:00
八千流
ad423e921a fix(highlight): 修复创建资源后跳转到列表页世,左侧菜单没有高亮问题 (#191) 2020-07-15 17:54:11 +08:00
八千流
326302ea5f fix(setting): 优化安全设置的一些字段提醒 (#190) 2020-07-15 17:52:54 +08:00
八千流
be213268fa fix(firefox_time_fix): 修改有些列表页面的时间在firefox上显示不出来的问题 (#189) 2020-07-15 17:51:10 +08:00
OrangeM21
b82231f3ea fix(Userlist): 添加禁止更新和禁止删除的权限判断
Closes https://github.com/jumpserver/trello/issues/29
2020-07-15 17:49:48 +08:00
Orange
f976800cde fix: 修改弹窗按钮高亮提示 (#187) 2020-07-15 17:47:22 +08:00
OrangeM21
4973c62618 fix: 用户来源不为Local的时候禁止更新密码
Closes https://github.com/jumpserver/trello/issues/44
2020-07-15 17:46:04 +08:00
Orange
5db7538216 Merge pull request #182 from jumpserver/remote_permission_fix
fix(remote_app_permission): 修复创建远程应用页面系统用户的获取
2020-07-09 18:29:38 +08:00
Orange
786528f9b2 Merge pull request #184 from jumpserver/remote_permission_fix
fix(remote_app_permission): 修复创建远程应用页面系统用户的获取
2020-07-09 18:29:19 +08:00
Orange
661409b99d Merge pull request #183 from jumpserver/remote_permission_fix
fix(remote_app_permission): 修复创建远程应用页面系统用户的获取
2020-07-09 18:29:04 +08:00
ibuler
702bb3acfa ci(build): 修改构建,使用严格模式 2020-07-09 17:50:05 +08:00
jym503558564
448b64757c fix(remote_app_permission): 修复创建远程应用页面系统用户的获取 2020-07-09 17:44:51 +08:00
ibuler
4289c36bd4 ci(build): 修改构建,使用严格模式 2020-07-09 17:44:01 +08:00
ibuler
bbccba3731 ci(docker): 修改docker构建 2020-07-09 17:44:01 +08:00
ibuler
57b6e02960 fix(Docker): 修改Dockerfile,统一使用build.sh构建 2020-07-09 17:44:01 +08:00
ibuler
8c6d2a1150 ci(build): 修改构建,使用严格模式 2020-07-09 17:29:56 +08:00
ibuler
1295fb7fd2 ci(docker): 修改docker构建 2020-07-09 16:35:55 +08:00
BaiJiangJie
d3fbb9a391 Merge pull request #177 from jumpserver/ci
ci(docker): 修改docker构建
2020-07-09 16:31:23 +08:00
ibuler
2b14cf0225 ci(docker): 修改docker构建 2020-07-09 16:25:42 +08:00
BaiJiangJie
a78f8a3633 Merge pull request #176 from jumpserver/dev
merge: Merge to master from branch dev
2020-07-09 16:01:19 +08:00
何去何从
c4868dabac fix: 优化资产树 mini-button 添加 cursor:pointer 2020-07-09 15:40:41 +08:00
BaiJiangJie
faf848dca5 Merge pull request #175 from jumpserver/ci
fix(Docker): 修改Dockerfile,统一使用build.sh构建
2020-07-09 15:40:19 +08:00
ibuler
05edffe173 fix(Docker): 修改Dockerfile,统一使用build.sh构建 2020-07-09 15:35:44 +08:00
Orange
4b3862443a Merge pull request #173 from jumpserver/master_merge
fix: lina dev中xpack的commit号指向xpack master
2020-07-09 14:20:31 +08:00
Orange
536ebd7513 Merge pull request #172 from jumpserver/master_merge
fix: lina master中xpack的commit号指向xpack master
2020-07-09 14:16:59 +08:00
jym503558564
7dce39d79c fix: lina master中xpack的commit号指向xpack master 2020-07-09 14:14:06 +08:00
八千流
a8389304d6 Merge pull request #171 from jumpserver/master
Merge Master 代码
2020-07-09 13:53:18 +08:00
Orange
649d4ac848 Merge pull request #170 from jumpserver/v2.0
feat: 更新Master代码
2020-07-09 13:44:11 +08:00
Orange
781bbe0ffa Merge pull request #169 from jumpserver/audit_fix
fix(audit): 修复时间显示兼容firefox
2020-07-09 12:01:28 +08:00
jym503558564
2771d80749 fix(audit): 修复时间显示兼容firefox 2020-07-09 11:48:27 +08:00
八千流
86b5cb81fc Merge pull request #168 from jumpserver/v2.0
feat: 更新Master代码
2020-07-08 17:30:03 +08:00
八千流
1ffcf9e7b4 Merge pull request #163 from jumpserver/v2.0
revert: 撤销网域显示ID的修复
2020-07-08 14:02:27 +08:00
Orange
a665d6ed20 Merge pull request #161 from jumpserver/v2.0
ci(fix): 修改ci使用的action为node10
2020-07-08 12:03:17 +08:00
Orange
47c05922ae Merge pull request #159 from jumpserver/v2.0
fix(many): v2.0中修改的bug Merge到master
2020-07-08 11:51:06 +08:00
八千流
7db080b418 Merge pull request #114 from jumpserver/fix_bugs_orange
fix:修复资产和前端bugs
2020-07-01 15:24:01 +08:00
老广
57e4b65059 Merge pull request #100 from jumpserver/dev
[hotfix]更改路由权限验证模块
2020-06-22 18:42:42 +08:00
Orange
5aa28903dd Merge pull request #99 from jumpserver/dev
[feature] v2.0 Release
2020-06-18 11:04:32 +08:00
429 changed files with 24081 additions and 6483 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

@@ -0,0 +1,12 @@
on: [push, pull_request, release]
name: JumpServer repos generic handler
jobs:
generic_handler:
name: Run generic handler
runs-on: ubuntu-latest
steps:
- uses: jumpserver/action-generic-handler@master
env:
GITHUB_TOKEN: ${{ secrets.PRIVATE_TOKEN }}

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "src/views/xpack"]
path = src/views/xpack
url = git@github.com:jumpserver/lina-xpack.git

View File

@@ -1,12 +1,23 @@
FROM node:10 as stage-build
WORKDIR /data
ADD ./package.json /data/package.json
ADD ./yarn.lock /data/yarn.lock
RUN yarn
ADD . /data
RUN yarn build:prod
ARG VERSION
ENV VERSION=$VERSION
ARG NPM_REGISTRY="https://registry.npm.taobao.org"
ENV NPM_REGISTY=$NPM_REGISTRY
ARG SASS_BINARY_SITE="https://npm.taobao.org/mirrors/node-sass"
ENV SASS_BINARY_SITE=$SASS_BINARY_SITE
WORKDIR /data
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/
RUN yarn install
RUN npm rebuild node-sass
ADD . /data
RUN cd utils && bash -xieu build.sh build
FROM nginx:alpine
COPY --from=stage-build /data/lina /opt/lina/
COPY --from=stage-build /data/release/lina /opt/lina
COPY nginx.conf /etc/nginx/conf.d/default.conf

View File

@@ -46,7 +46,7 @@ server {
## License & Copyright
Copyright (c) 2014-2020 飞致云 FIT2CLOUD, All rights reserved.
Copyright (c) 2014-2021 飞致云 FIT2CLOUD, All rights reserved.
Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

View File

@@ -1,5 +1,8 @@
module.exports = {
presets: [
'@vue/app'
],
plugins: [
'@babel/plugin-proposal-optional-chaining',
]
}

View File

@@ -1,9 +1,19 @@
server {
listen 80;
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
#gzip_http_version 1.0;
gzip_comp_level 8;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
gzip_vary off;
gzip_static on;
gzip_disable "MSIE [1-6].";
location /ui/ {
try_files $uri / /ui/index.html;
alias /opt/lina/;
try_files $uri / /ui/index.html;
alias /opt/lina/;
}
location / {

View File

@@ -15,17 +15,23 @@
"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",
"@ztree/ztree_v3": "3.5.44",
"axios": "0.18.1",
"axios": "0.21.1",
"axios-retry": "^3.1.9",
"deepmerge": "^4.2.2",
"echarts": "^4.7.0",
"element-ui": "2.13.2",
"eslint-plugin-html": "^6.0.0",
"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",
@@ -40,8 +46,10 @@
"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",
"nprogress": "0.2.0",
"path-to-regexp": "2.4.0",
"vue": "2.6.10",
@@ -72,6 +80,7 @@
"babel-eslint": "10.0.1",
"babel-jest": "23.6.0",
"chalk": "2.4.2",
"compression-webpack-plugin": "^6.1.1",
"connect": "3.6.6",
"element-theme-chalk": "^2.13.1",
"eslint": "^5.15.3",
@@ -82,8 +91,8 @@
"less-loader": "^5.0.0",
"lint-staged": "^10.1.2",
"mockjs": "1.0.1-beta3",
"node-sass": "^4.9.0",
"runjs": "^4.3.2",
"sass": "^1.26.10",
"sass-loader": "^7.1.0",
"script-ext-html-webpack-plugin": "2.1.3",
"script-loader": "0.7.2",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -3,8 +3,11 @@
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta http-equiv="Expires" content="0">
<meta http-equiv="Pragma" content="no-cache">
<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>

15
src/api/orgs.js Normal file
View File

@@ -0,0 +1,15 @@
import request from '@/utils/request'
export function getOrgDetail(oid) {
return request({
url: `/api/v1/orgs/orgs/current/?oid=${oid}`,
method: 'get'
})
}
export function getCurrentOrg() {
return request({
url: `/api/v1/orgs/orgs/current/`,
method: 'get'
})
}

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',

8
src/api/ticket.js Normal file
View File

@@ -0,0 +1,8 @@
import request from '@/utils/request'
export function getTicketOpenCount(assign) {
return request({
url: `/api/v1/tickets/tickets/?assignees__id=${assign}&status=open&offset=0&limit=15&display=1&draw=1/`,
method: 'get'
})
}

View File

@@ -65,3 +65,7 @@ export function logout() {
method: 'post'
})
}
export function refreshSessionIdAge() {
return getProfile()
}

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

@@ -1,40 +1,15 @@
<template>
<div :class="grouped ? 'el-button-group' : ''">
<el-button v-for="item in iActions" :key="item.name" :size="size" v-bind="item" @click="handleClick(item.name)">
<el-tooltip v-if="['actionExport', 'actionImport', 'actionRefresh'].indexOf(item.name) !== -1" effect="dark" :content="item.tip" placement="top">
<i v-if="item.fa" :class="'fa ' + item.fa" />{{ item.title }}
</el-tooltip>
<span v-else>
<i v-if="item.fa" :class="'fa ' + item.fa" />{{ item.title }}
</span>
</el-button>
<el-dropdown v-if="iMoreActions.length > 0" trigger="click" @command="handleClick">
<el-button :size="size" :type="moreActionsType" class="btn-more-actions">
{{ iMoreActionsTitle }}<i class="el-icon-arrow-down el-icon--right" />
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="item in iMoreActions" :key="item.name" :command="item.name" v-bind="item" @click="handleClick(item.name)">{{ item.title }} </el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<DataActions :actions="iActions" v-bind="$attrs" />
</template>
<script>
import DataActions from '@/components/DataActions'
export default {
name: 'ActionsGroup',
components: {
DataActions
},
props: {
grouped: {
type: Boolean,
default: false
},
size: {
type: String,
default: 'small'
},
type: {
type: String,
default: ''
},
actions: {
type: Array,
default: () => []
@@ -52,78 +27,31 @@ export default {
moreActionsType: {
type: String,
default: 'default'
},
moreActionsPlacement: {
type: String,
default: 'bottom'
// 居中对齐
}
},
computed: {
iActions() {
return this.cleanActions(this.actions)
},
iMoreActions() {
return this.cleanActions(this.moreActions)
},
totalActions() {
return [...this.actions, ...this.moreActions]
},
totalNamedActions() {
const actions = {}
for (const action of this.totalActions) {
if (!action || !action.hasOwnProperty('name')) {
continue
}
actions[action.name] = action
const actions = [...this.actions]
if (this.iMoreAction && this.iMoreAction.dropdown.length > 0) {
actions.push(this.iMoreAction)
}
return actions
},
iMoreAction() {
return {
name: 'moreActions',
title: this.iMoreActionsTitle,
dropdown: this.moreActions || []
}
},
iMoreActionsTitle() {
return this.moreActionsTitle || this.$t('common.MoreActions')
}
},
methods: {
handleClick(item) {
const action = this.totalNamedActions[item]
if (action && action.callback) {
action.callback(action)
} else {
this.$log.debug('No callback found')
}
this.$emit('actionClick', item)
},
checkItem(item, attr, defaults) {
if (!item) {
return true
}
let ok = item[attr]
if (ok && typeof ok === 'function') {
ok = ok(item)
} else if (ok == null) {
ok = defaults === undefined ? true : defaults
}
return ok
},
cleanActions(actions) {
const cleanedActions = []
const cloneActions = _.cloneDeep(actions)
for (const v of cloneActions) {
if (!v) {
continue
}
const action = Object.assign({}, v)
// 是否拥有这个action
const has = this.checkItem(action, 'has')
delete action['has']
if (!has) {
continue
}
// 是否是disabled
const can = this.checkItem(action, 'can')
delete action['can']
action.disabled = !can
cleanedActions.push(action)
// 删掉callback避免前台看到
delete action['callback']
}
return cleanedActions
}
}
}
</script>

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

@@ -3,12 +3,12 @@
<table style="width: 100%">
<tr>
<td colspan="2">
<AssetSelect ref="assetSelect" />
<AssetSelect ref="assetSelect" :disabled="disabled" :can-select="canSelect" />
</td>
</tr>
<tr>
<td colspan="2">
<el-button :type="type" size="small" @click="addObjects">{{ $t('common.Add') }}</el-button>
<el-button :type="type" size="small" :disabled="disabled" @click="addObjects">{{ $t('common.Add') }}</el-button>
</td>
</tr>
</table>
@@ -38,6 +38,10 @@ export default {
type: String,
default: 'primary'
},
disabled: {
type: [Boolean, Function],
default: false
},
value: {
type: [Array, Number, String],
default: () => []
@@ -49,6 +53,12 @@ export default {
onAddSuccess: {
type: Function,
default: (objects, that) => {}
},
canSelect: {
type: Function,
default(row, index) {
return true
}
}
},
data() {

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 {
@@ -34,6 +34,16 @@ export default {
value: {
type: Array,
default: () => []
},
canSelect: {
type: Function,
default(row, index) {
return true
}
},
disabled: {
type: [Boolean, Function],
default: false
}
},
data() {
@@ -66,8 +76,9 @@ 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: [
{
prop: 'hostname',
@@ -81,8 +92,20 @@ export default {
},
{
prop: 'ip',
label: this.$t('assets.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: {
@@ -156,20 +179,20 @@ export default {
.el-select{
width: 100%;
}
.page /deep/ .page-heading{
.page ::v-deep .page-heading{
display: none;
}
.el-dialog__wrapper /deep/.el-dialog__body{
.el-dialog__wrapper ::v-deep .el-dialog__body{
padding: 5px 10px;
}
.page /deep/ .treebox{
.page ::v-deep .treebox{
height: inherit !important;
}
.asset-select-dialog >>> .transition-box:first-child {
background-color: #f3f3f3 ;
}
.el-dialog__wrapper /deep/.el-dialog__body .wrapper-content {
.el-dialog__wrapper ::v-deep .el-dialog__body .wrapper-content {
padding: 10px;
}

View File

@@ -1,361 +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
}
},
data() {
return {
MFAConfirmed: false,
MFAInput: '',
MFAInfo: {
asset: '',
username: '',
hostname: '',
password: ''
},
showDialog: false,
showMFADialog: false,
dialogInfo: {
asset: '',
username: '',
hostname: '',
password: '',
key: ''
},
tableConfig: {
url: this.url,
columns: [
{
prop: 'hostname',
label: this.$t('assets.Hostname'),
showOverflowTooltip: true
},
{
prop: 'ip',
label: this.$t('assets.ip'),
width: 140
},
{
prop: 'username',
label: this.$t('assets.Username'),
showOverflowTooltip: true
},
{
prop: 'version',
label: this.$t('assets.Version'),
width: '50px'
},
{
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)
moreActionsTitle: this.$t('common.More'),
extraActions: [
{
name: 'View',
title: this.$t('common.View'),
type: 'primary',
callback: function(val) {
this.MFAInfo.asset = val.cellValue
if (this.MFAVerifyAt + this.MFA_TTl * 1000 > (new Date()).valueOf()) {
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.cellValue}/`).then(() => {
this.$message.success(this.$t('common.deleteSuccessMsg'))
this.$refs.ListTable.reloadTable()
}).catch(() => this.$message.error(this.$t('common.deleteFailedMsg')))
}
},
{
name: 'Test',
title: this.$t('common.Test'),
callback: (val) => {
this.$axios.post(
`/api/v1/assets/asset-users/tasks/?id=${val.cellValue}`,
{ action: 'test' }
).then(res => {
window.open(`/#/ops/celery/task/${res.task}/log/`, '', 'width=900,height=600')
})
}
},
{
name: 'Update',
title: this.$t('common.Update'),
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,
hasBulkDelete: 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'
]),
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: '',
key: ''
}
this.showDialog = false
},
Onchange(e) {
const vm = this
// TODO 校验文件类型
const reader = new FileReader()
reader.onload = function() {
vm.dialogInfo.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.key !== '') {
data.key = this.dialogInfo.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: '',
key: ''
}
this.showDialog = false
}
}
}
</script>
<style lang='less' scoped>
</style>

View File

@@ -0,0 +1,81 @@
<template>
<DataForm
:fields="iFields"
:form="value"
style="margin-left: -26%;margin-right: -6%"
v-bind="kwargs"
v-on="$listeners"
/>
</template>
<script>
import DataForm from '@/components/DataForm'
export default {
name: 'NestedField',
components: {
DataForm
},
props: {
fields: {
type: Array,
default: () => []
},
value: {
type: Object,
default: () => ({})
},
errors: {
type: [Object, String],
default: ''
}
},
data() {
return {
kwargs: {
hasReset: false,
hasSaveContinue: false,
hasButtons: false
}
}
},
computed: {
iFields() {
const fields = this.fields
if (this.errors && typeof this.errors === 'object') {
// eslint-disable-next-line prefer-const
for (let [name, error] of Object.entries(this.errors)) {
const field = fields.find((v) => v.prop === name)
if (!field) {
continue
}
this.$log.debug(`${name}: ${error}`)
if (typeof error === 'object' && !Array.isArray(error)) {
error = this.objectToString(error)
}
field.attrs.error = error.toString()
}
}
this.$log.debug('Fields change: ', fields, this.errors)
return fields
}
},
methods: {
objectToString(obj) {
let data = ''
// eslint-disable-next-line prefer-const
for (let [key, value] of Object.entries(obj)) {
if (typeof value === 'object') {
value = this.objectToString(value)
}
data += ` ${key}: ${value} `
}
return data
}
}
}
</script>
<style scoped>
</style>

View File

@@ -1,15 +1,20 @@
<template>
<DataForm ref="dataForm" v-loading="loading" :fields="totalFields" 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" />
<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"
:group="group"
:index="i"
:line="i !== 0"
/>
</DataForm>
</template>
<script>
import DataForm from '../DataForm'
import FormGroupHeader from '@/components/FormGroupHeader'
// import { optionUrlMeta } from '@/api/common'
import rules from '@/components/DataForm/rules'
import Select2 from '@/components/Select2'
import { FormFieldGenerator } from '@/components/AutoDataForm/utils'
export default {
name: 'AutoDataForm',
components: {
@@ -31,6 +36,10 @@ export default {
return []
}
},
form: {
type: Object,
default: () => ({})
},
fieldsMeta: {
type: Object,
default: () => ({})
@@ -38,133 +47,52 @@ export default {
},
data() {
return {
meta: {},
remoteMeta: {},
totalFields: [],
loading: true,
groups: []
groups: [],
iForm: this.form,
errors: {}
}
},
mounted() {
this.optionUrlMeta()
this.optionUrlMetaAndGenerateColumns()
},
methods: {
optionUrlMeta() {
optionUrlMetaAndGenerateColumns() {
this.$store.dispatch('common/getUrlMeta', { url: this.url }).then(data => {
this.meta = data.actions[this.method.toUpperCase()] || {}
this.remoteMeta = data.actions[this.method.toUpperCase()] || {}
this.generateColumns()
this.cleanFormValue()
}).catch(err => {
console.error(err)
this.$log.error(err)
}).finally(() => {
this.loading = false
})
},
generateFieldByType(type, field, fieldMeta) {
switch (type) {
case 'choice':
type = 'radio-group'
field.options = fieldMeta.choices.map(v => {
return { label: v.display_name, value: v.value }
})
break
case 'datetime':
type = 'date-picker'
field.el = {
type: 'datetime'
}
break
case 'field':
type = ''
field.component = Select2
break
case 'string':
type = 'input'
if (!fieldMeta.max_length) {
field.el.type = 'textarea'
field.el.rows = 3
}
break
default:
type = 'input'
break
}
if (type === 'radio-group') {
const options = fieldMeta.choices.map(v => {
return { label: v.display_name, value: v.value }
})
if (options.length > 4) {
type = 'select'
field.el.filterable = true
}
}
field.type = type
return field
},
generateFieldByName(name, field) {
switch (name) {
case 'email':
field.el.type = 'email'
break
case 'password':
field.el.type = 'password'
break
case 'comment':
field.el.type = 'textarea'
break
}
return field
},
generateFieldByOther(field, fieldMeta) {
const filedRules = field.rules || []
if (fieldMeta.required) {
if (field.type === 'input') {
filedRules.push(rules.Required)
} else {
filedRules.push(rules.RequiredChange)
}
}
field.rules = filedRules
return field
},
generateField(name) {
let field = { id: name, prop: name, el: {}, attrs: {}}
const fieldMeta = this.meta[name] || {}
field.label = fieldMeta.label
field = this.generateFieldByType(fieldMeta.type, field, fieldMeta)
field = this.generateFieldByName(name, field)
field = this.generateFieldByOther(field, fieldMeta)
field = Object.assign(field, this.fieldsMeta[name] || {})
_.set(field, 'attrs.error', '')
return field
},
generateFieldGroup(data) {
const [groupTitle, fields] = data
this.groups.push({
id: groupTitle,
title: groupTitle,
name: fields[0]
})
return this.generateFields(fields)
},
generateFields(data) {
let fields = []
for (let field of data) {
if (field instanceof Array) {
const items = this.generateFieldGroup(field)
fields = [...fields, ...items]
} else if (typeof field === 'string') {
field = this.generateField(field)
fields.push(field)
} else if (field instanceof Object) {
this.errors[field.prop] = ''
_.set(field, 'attrs.error', '')
fields.push(field)
}
}
return fields
},
generateColumns() {
this.totalFields = this.generateFields(this.fields)
const generator = new FormFieldGenerator()
this.totalFields = generator.generateFields(this.fields, this.fieldsMeta, this.remoteMeta)
this.groups = generator.groups
this.$log.debug('Total fields: ', this.totalFields)
},
_cleanFormValue(form, remoteMeta) {
for (const [k, v] of Object.entries(remoteMeta)) {
if (v.default === undefined) {
continue
}
const valueSet = form[k]
if (valueSet !== undefined) {
continue
}
if (v.type === 'nested object' && typeof valueSet === 'object') {
this._cleanFormValue(valueSet, v.children)
}
form[k] = v.default
}
},
cleanFormValue() {
this._cleanFormValue(this.iForm, this.remoteMeta)
},
setFieldError(name, error) {
const field = this.totalFields.find((v) => v.prop === name)
@@ -174,7 +102,11 @@ export default {
if (field.attrs.error === error) {
error += '.'
}
field.attrs.error = error
if (field.type === 'nestedField') {
field.el.errors = error
} else {
field.attrs.error = error
}
}
}
}

View File

@@ -0,0 +1,158 @@
import Vue from 'vue'
import Select2 from '@/components/FormFields/Select2'
import NestedField from '@/components/AutoDataForm/components/NestedField'
import rules from '@/components/DataForm/rules'
import { assignIfNot } from '@/utils/common'
export class FormFieldGenerator {
constructor() {
this.groups = []
}
generateFieldByType(type, field, fieldMeta, fieldRemoteMeta) {
switch (type) {
case 'choice':
type = 'radio-group'
if (!fieldRemoteMeta.read_only) {
field.options = fieldRemoteMeta.choices.map(v => {
return { label: v.display_name, value: v.value }
})
}
break
case 'datetime':
type = 'date-picker'
field.el = {
type: 'datetime'
}
break
case 'field':
type = ''
field.component = Select2
if (fieldRemoteMeta.required) {
field.el.clearable = false
}
break
case 'string':
type = 'input'
if (!fieldRemoteMeta['max_length']) {
field.el.type = 'textarea'
field.el.rows = 3
}
if (fieldRemoteMeta['write_only']) {
field.el.type = 'password'
}
break
case 'boolean':
type = 'checkbox'
break
case 'nested object':
type = 'nestedField'
field.component = NestedField
field.label = ''
field.labelWidth = 0
field.el.fields = this.generateNestFields(field, fieldMeta, fieldRemoteMeta)
field.el.errors = {}
Vue.$log.debug('All fields in generate: ', field.el.allFields)
break
default:
type = 'input'
break
}
if (type === 'radio-group') {
if (!fieldRemoteMeta.read_only) {
const options = fieldRemoteMeta.choices.map(v => {
return { label: v.display_name, value: v.value }
})
if (options.length > 4) {
type = 'select'
field.el.filterable = true
}
}
}
field.type = type
return field
}
generateNestFields(field, fieldMeta, fieldRemoteMeta) {
const fields = []
const nestedFields = fieldMeta.fields || []
const nestedFieldsMeta = fieldMeta.fieldsMeta || {}
const nestedFieldsRemoteMeta = fieldRemoteMeta.children || {}
for (const name of nestedFields) {
const f = this.generateField(name, nestedFieldsMeta, nestedFieldsRemoteMeta)
fields.push(f)
}
Vue.$log.debug('NestFields: ', fields)
return fields
}
generateFieldByName(name, field) {
switch (name) {
case 'email':
field.el.type = 'email'
break
case 'password':
field.el.type = 'password'
break
case 'comment':
field.el.type = 'textarea'
break
}
return field
}
generateFieldByOther(field, fieldMeta, fieldRemoteMeta) {
const filedRules = field.rules || []
if (fieldRemoteMeta.required) {
if (field.type === 'input') {
filedRules.push(rules.Required)
} else {
filedRules.push(rules.RequiredChange)
}
}
field.rules = filedRules
return field
}
generateField(name, fieldsMeta, remoteFieldsMeta) {
let field = { id: name, prop: name, el: {}, attrs: {}, rules: [] }
const remoteFieldMeta = remoteFieldsMeta[name] || {}
const fieldMeta = fieldsMeta[name] || {}
field.label = remoteFieldMeta.label
field.helpText = remoteFieldMeta.help_text
field = this.generateFieldByType(remoteFieldMeta.type, field, fieldMeta, remoteFieldMeta)
field = this.generateFieldByName(name, field)
field = this.generateFieldByOther(field, fieldMeta, remoteFieldMeta)
const el = assignIfNot(fieldMeta.el || {}, field.el)
const rules = fieldMeta.rules || field.rules
field = Object.assign(field, fieldMeta)
field.el = el
field.rules = rules
_.set(field, 'attrs.error', '')
// Vue.$log.debug('Generate field: ', name, field)
return field
}
generateFieldGroup(field, fieldsMeta, remoteFieldsMeta) {
const [groupTitle, fields] = field
this.groups.push({
id: groupTitle,
title: groupTitle,
name: fields[0],
fields: fields
})
return this.generateFields(fields, fieldsMeta, remoteFieldsMeta)
}
generateFields(_fields, fieldsMeta, remoteFieldsMeta) {
let fields = []
for (let field of _fields) {
if (field instanceof Array) {
const items = this.generateFieldGroup(field, fieldsMeta, remoteFieldsMeta)
fields = [...fields, ...items]
} else if (typeof field === 'string') {
field = this.generateField(field, fieldsMeta, remoteFieldsMeta)
fields.push(field)
} else if (field instanceof Object) {
if (this.errors) {
this.errors[field.prop] = ''
}
fields.push(field)
}
}
return fields
}
}

View File

@@ -1,5 +1,5 @@
<template>
<TagSearch :options="options" v-bind="$attrs" v-on="$listeners" />
<TagSearch :options="iOption" v-bind="$attrs" v-on="$listeners" />
</template>
<script>
@@ -23,9 +23,22 @@ export default {
default: () => []
}
},
data() {
return {
internalOptions: []
}
},
computed: {
iOption() {
return this.options.concat(this.internalOptions)
}
},
watch: {
options() {
// 空函数,方便子组件刷新
},
url() {
this.genericOptions()
}
},
mounted() {
@@ -36,6 +49,7 @@ export default {
methods: {
async genericOptions() {
const vm = this // 透传This
vm.internalOptions = [] // 重置
const data = await this.optionUrlMeta()
const meta = data.actions['GET'] || {}
for (const [name, field] of Object.entries(meta)) {
@@ -47,6 +61,7 @@ export default {
}
const option = {
label: field.label,
type: field.type,
value: name
}
if (field.type === 'choice' && field.choices) {
@@ -67,7 +82,7 @@ export default {
{ label: this.$t('common.No'), value: false }
]
}
vm.options.push(option)
vm.internalOptions.push(option)
}
},
optionUrlMeta() {
@@ -79,5 +94,4 @@ export default {
</script>
<style lang='less' scoped>
</style>

View File

@@ -0,0 +1,91 @@
<template>
<Dialog
v-if="showColumnSettingPopover"
:title="$t('common.CustomCol')"
:visible.sync="showColumnSettingPopover"
:destroy-on-close="true"
:show-cancel="false"
width="35%"
top="10%"
@confirm="handleColumnConfirm()"
>
<el-alert type="success">
{{ this.$t('common.TableColSettingInfo') }}
</el-alert>
<el-checkbox-group
v-model="iCurrentColumns"
>
<el-row>
<el-col
v-for="item in totalColumnsList"
:key="item.prop"
:span="8"
style="margin-top:5px;"
>
<el-checkbox
:label="item.prop"
:disabled="
item.prop==='id' ||
item.prop==='actions' ||
minColumns.indexOf(item.prop)!==-1
"
>
{{ item.label }}
</el-checkbox>
</el-col>
</el-row>
</el-checkbox-group>
</Dialog>
</template>
<script>
import Dialog from '@/components/Dialog/index'
export default {
name: 'ColumnSettingPopover',
components: {
Dialog
},
props: {
totalColumnsList: {
type: Array,
default: () => []
},
currentColumns: {
type: Array,
default: () => []
},
minColumns: {
type: Array,
default: () => []
},
url: {
type: String,
default: ''
}
},
data() {
return {
showColumnSettingPopover: false,
iCurrentColumns: ''
}
},
mounted() {
this.$eventBus.$on('showColumnSettingPopover', ({ url }) => {
if (url === this.url) {
this.showColumnSettingPopover = true
this.iCurrentColumns = this.currentColumns
}
})
},
methods: {
handleColumnConfirm() {
this.showColumnSettingPopover = false
this.$emit('columnsUpdate', { columns: this.iCurrentColumns, url: this.url })
}
}
}
</script>
<style lang='less' scoped>
</style>

View File

@@ -1,20 +1,36 @@
<template>
<DataTable v-if="!loading" ref="dataTable" v-loading="loading" :config="iConfig" v-bind="$attrs" v-on="$listeners" />
<div>
<DataTable v-if="!loading" ref="dataTable" v-loading="loading" :config="iConfig" v-bind="$attrs" v-on="$listeners" @filter-change="filterChange" />
<ColumnSettingPopover
:current-columns="popoverColumns.currentCols"
:total-columns-list="popoverColumns.totalColumnsList"
:min-columns="popoverColumns.minCols"
:url="config.url"
@columnsUpdate="handlePopoverColumnsChange"
/>
</div>
</template>
<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: {
DataTable
DataTable,
ColumnSettingPopover
},
props: {
config: {
type: Object,
default: () => ({})
},
filterTable: {
type: Function,
default: () => ({})
}
},
data() {
@@ -23,8 +39,18 @@ export default {
method: 'get',
autoConfig: {},
iConfig: {},
meta: {}
meta: {},
cleanedColumnsShow: {},
totalColumns: [],
popoverColumns: {
totalColumnsList: [],
minCols: [],
currentCols: []
}
}
},
computed: {
},
watch: {
config: {
@@ -40,10 +66,18 @@ export default {
},
methods: {
async optionUrlMetaAndGenCols() {
if (this.config.url === '') { return }
const url = (this.config.url.indexOf('?') === -1) ? `${this.config.url}?draw=1&display=1` : `${this.config.url}&draw=1&display=1`
this.$store.dispatch('common/getUrlMeta', { url: url }).then(data => {
this.meta = data.actions[this.method.toUpperCase()] || {}
this.generateColumns()
const method = this.method.toUpperCase()
this.meta = data.actions && data.actions[method] ? data.actions[method] : {}
this.generateTotalColumns()
}).then(() => {
// 根据当前列重新生成最终渲染表格
this.filterShowColumns()
}).then(() => {
// 生成给子组件使用的TotalColList
this.generatePopoverColumns()
}).catch((error) => {
this.$log.error('Error occur: ', error)
}).finally(() => {
@@ -59,7 +93,7 @@ export default {
break
case 'actions':
col = {
prop: 'id',
prop: 'actions',
label: i18n.t('common.Actions'),
align: 'center',
width: '150px',
@@ -118,6 +152,37 @@ export default {
}
return col
},
addFilterIfNeed(col) {
if (col.prop) {
const column = this.meta[col.prop] || {}
if (!column.filter) {
return col
}
if (column.type === 'boolean') {
col.filters = [
{ text: this.$t('common.Yes'), value: true },
{ text: this.$t('common.No'), value: false }
]
col.sortable = false
col['column-key'] = col.prop
}
if (column.type === 'choice' && column.choices) {
col.filters = column.choices.map(item => {
if (typeof (item.value) === 'boolean') {
if (item.value) {
return { text: item['display_name'], value: 'True' }
} else {
return { text: item['display_name'], value: 'False' }
}
}
return { text: item['display_name'], value: item.value }
})
col.sortable = false
col['column-key'] = col.prop
}
}
return col
},
generateColumn(name) {
const colMeta = this.meta[name] || {}
const customMeta = this.config.columnsMeta ? this.config.columnsMeta[name] : {}
@@ -127,9 +192,10 @@ export default {
col = this.generateColumnByType(colMeta.type, col)
col = Object.assign(col, customMeta)
col = this.addHelpTipsIfNeed(col)
col = this.addFilterIfNeed(col)
return col
},
generateColumns() {
generateTotalColumns() {
const config = _.cloneDeep(this.config)
const columns = []
for (let col of config.columns) {
@@ -140,8 +206,85 @@ export default {
columns.push(col)
}
}
// 第一次初始化时记录 totalColumns
this.totalColumns = columns
config.columns = columns
this.iConfig = config
},
// 生成给子组件使用的TotalColList
cleanColumnsShow() {
const totalColumnsNames = this.totalColumns.map(obj => obj.prop)
// 默认列
let defaultColumnsNames = _.get(this.iConfig, 'columnsShow.default', [])
if (defaultColumnsNames.length === 0) {
defaultColumnsNames = totalColumnsNames
}
// Clean it
defaultColumnsNames = totalColumnsNames.filter(n => defaultColumnsNames.indexOf(n) > -1)
// 最小列
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 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
}
// 校对显示的列,是不是包含最小列
minColumnsNames.forEach((v, i) => {
if (showColumnsNames.indexOf(v) === -1) {
showColumnsNames.push(v)
}
})
// Clean it
showColumnsNames = totalColumnsNames.filter(n => showColumnsNames.indexOf(n) > -1)
this.cleanedColumnsShow = {
default: defaultColumnsNames,
show: showColumnsNames,
min: minColumnsNames,
configShow: configShowColumnsNames
}
this.$log.debug('Cleaned columns show: ', this.cleanedColumnsShow)
},
filterShowColumns() {
this.cleanColumnsShow()
this.iConfig.columns = this.totalColumns.filter(obj => {
return this.cleanedColumnsShow.show.indexOf(obj.prop) > -1
})
},
generatePopoverColumns() {
this.popoverColumns.totalColumnsList = this.totalColumns.map(obj => {
return { prop: obj.prop, label: obj.label }
})
this.popoverColumns.currentCols = this.cleanedColumnsShow.show
this.popoverColumns.minCols = this.cleanedColumnsShow.min
this.$log.debug('Popover cols: ', this.popoverColumns)
},
handlePopoverColumnsChange({ columns, url }) {
this.$log.debug('Columns change: ', columns)
this.popoverColumns.currentCols = columns
const _tableConfig = localStorage.getItem('tableConfig')
? JSON.parse(localStorage.getItem('tableConfig'))
: {}
const tableName = this.config.name || this.$route.name + '_' + newURL(url).pathname
_tableConfig[tableName] = {
'showColumns': columns
}
localStorage.setItem('tableConfig', JSON.stringify(_tableConfig))
this.filterShowColumns()
},
filterChange(filters) {
const key = Object.keys(filters)[0]
const attr = {}
attr[key] = filters[key][0]
this.filterTable(attr)
}
}
}

View File

@@ -1,6 +1,6 @@
<template>
<DataZTree ref="dataztree" :setting="treeSetting">
<slot slot="rMenu">
<DataZTree ref="dataztree" :setting="treeSetting" class="data-z-tree" v-on="$listeners">
<slot v-if="treeSetting.hasRightMenu" slot="rMenu">
<li id="m_create" class="rmenu" tabindex="-1" @click="createTreeNode">
<i class="fa fa-plus-square-o" /> {{ this.$t('tree.CreateNode') }}
</li>
@@ -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: {
@@ -55,7 +55,8 @@ export default {
// beforeDrag
// onDrag
// beforeAsync: this.defaultCallback.bind(this, 'beforeAsync')
}
},
hasRightMenu: true
},
currentNode: '',
currentNodeId: ''
@@ -63,6 +64,7 @@ export default {
},
computed: {
treeSetting() {
this.$log.debug('Settings: ', this.setting)
return _.merge(this.defaultSetting, this.setting)
},
zTree() {
@@ -76,6 +78,10 @@ export default {
$('body').unbind('mousedown')
},
methods: {
refreshTree: function() {
const refreshIconRef = $('#tree-refresh')
refreshIconRef.click()
},
editTreeNode: function() {
this.hideRMenu()
const currentNode = this.zTree.getSelectedNodes()[0]
@@ -83,7 +89,7 @@ export default {
return
}
if (currentNode) {
currentNode.name = currentNode.meta.node.value
currentNode.name = currentNode.meta.data.value
}
this.zTree.editName(currentNode)
},
@@ -98,15 +104,19 @@ export default {
if (this.setting.url.indexOf('?') !== -1) {
combinator = '&'
}
let url = ''
const query = Object.assign({}, this.$route.query)
if (treeNode.meta.type === 'node') {
this.currentNode = treeNode
this.currentNodeId = treeNode.meta.node.id
this.$route.query['node'] = this.currentNodeId
this.$emit('urlChange', `${this.setting.url}${combinator}node_id=${treeNode.meta.node.id}&show_current_asset=${show_current_asset}`)
this.currentNodeId = treeNode.meta.data.id
query['node'] = this.currentNodeId
url = `${this.setting.url}${combinator}node_id=${treeNode.meta.data.id}&show_current_asset=${show_current_asset}`
} else if (treeNode.meta.type === 'asset') {
this.$route.query['asset'] = treeNode.meta.asset.id
this.$emit('urlChange', `${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)
},
removeTreeNode: function() {
this.hideRMenu()
@@ -115,12 +125,13 @@ 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)
this.refreshTree()
}).catch(() => {
// this.$message.error(this.$t('common.deleteErrorMsg' + ' ' + error))
// this.$message.error(this.$t('common.deleteErrorMsg') + ' ' + error)
})
},
onRename: function(event, treeId, treeNode, isCancel) {
@@ -132,16 +143,14 @@ export default {
url,
{ 'value': treeNode.name }
).then(res => {
let assetsAmount = treeNode.meta.node.assetsAmount
let assetsAmount = treeNode.meta.data.assetsAmount
if (!assetsAmount) {
assetsAmount = 0
}
treeNode.name = treeNode.name + ' (' + assetsAmount + ')'
this.zTree.updateNode(treeNode)
this.$message.success(this.$t('common.updateSuccessMsg'))
}).catch(error => {
this.$message.error(this.$t('common.updateErrorMsg' + ' ' + error))
})
}).finally(() => { this.refreshTree() })
},
onBodyMouseDown: function(event) {
const rMenuID = this.$refs.dataztree.$refs.ztree.iRMenuID
@@ -159,6 +168,11 @@ export default {
y -= (offset.top + scrollTop) / 3 - 10
x += document.body.scrollLeft
y += document.body.scrollTop + document.documentElement.scrollTop
if (y + $(`#${rMenuID} ul`).height() >= window.innerHeight) {
y -= $(`#${rMenuID} ul`).height()
}
this.rMenu.css({ 'top': y + 'px', 'left': x + 'px', 'visibility': 'visible' })
$(`#${rMenuID} ul`).show()
$('body').bind('mousedown', this.onBodyMouseDown)
@@ -194,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
@@ -205,7 +219,7 @@ export default {
this.$message.success(this.$t('common.updateSuccessMsg'))
}).catch(error => {
this.$message.error(this.$t('common.updateErrorMsg' + ' ' + error))
})
}).finally(() => this.refreshTree())
},
createTreeNode: function() {
this.hideRMenu()
@@ -215,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 => {
@@ -237,19 +251,21 @@ export default {
})
},
refresh: function() {
this.$axios.post(
'/api/v1/assets/nodes/00000000-0000-0000-0000-000000000000/tasks/',
{ action: 'refresh_cache' }
)
},
getSelectedNodes: function() {
return this.zTree.getSelectedNodes()
},
getNodes: function() {
return this.zTree.getNodes()
},
selectNode: function(node) {
return this.zTree.selectNode(node)
}
}
}
</script>
<style lang='less' scoped>
<style scoped>
.rmenu {
font-size: 12px;
padding: 0 16px;
@@ -272,4 +288,8 @@ export default {
.rmenu:hover{
background-color: #f5f7fa;
}
.data-z-tree >>> .fa {
width: 10px;
}
</style>

View File

@@ -0,0 +1,174 @@
<template>
<div :class="grouped ? 'el-button-group' : 'el-button-ungroup'">
<template v-for="action in iActions">
<el-dropdown
v-if="action.dropdown"
v-show="action.dropdown.length > 0"
:key="action.name"
class="action-item"
trigger="click"
placement="bottom-start"
@command="handleDropdownCallback"
>
<el-button :size="size" v-bind="cleanButtonAction(action)">
{{ action.title }}<i class="el-icon-arrow-down el-icon--right" />
</el-button>
<el-dropdown-menu slot="dropdown">
<template v-for="option in action.dropdown">
<div v-if="option.group" :key="'group:'+option.name" class="dropdown-menu-title">
{{ option.group }}
</div>
<el-dropdown-item
:key="option.name"
:command="[option, action]"
v-bind="option"
>
{{ option.title }}
</el-dropdown-item>
</template>
</el-dropdown-menu>
</el-dropdown>
<el-button
v-else
:key="action.name"
:size="size"
v-bind="cleanButtonAction(action)"
class="action-item"
@click="handleClick(action)"
>
<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>
</el-button>
</template>
</div>
</template>
<script>
export default {
name: 'DataActions',
props: {
grouped: {
type: Boolean,
default: false
},
size: {
type: String,
default: 'small'
},
type: {
type: String,
default: ''
},
actions: {
type: Array,
default: () => []
}
},
computed: {
iActions() {
return this.cleanActions(this.actions)
}
},
methods: {
handleDropdownCallback(command) {
const [option, dropdown] = command
const defaultCallback = () => this.$log.debug('No callback found: ', option, dropdown)
let callback = option.callback
if (!callback) {
callback = dropdown.callback
}
if (!callback) {
callback = defaultCallback
}
return callback(option)
},
handleClick(action) {
if (action && action.callback) {
action.callback(action)
} else {
this.$log.debug('No callback found')
}
this.$emit('actionClick', action)
},
checkItem(item, attr, defaults) {
if (!item) {
return true
}
let ok = item[attr]
if (ok && typeof ok === 'function') {
ok = ok(item)
} else if (ok == null) {
ok = defaults === undefined ? true : defaults
}
return ok
},
cleanButtonAction(action) {
action = _.cloneDeep(action)
delete action['dropdown']
delete action['callback']
delete action['name']
delete action['can']
return action
},
cleanActions(actions) {
const cleanedActions = []
const cloneActions = _.cloneDeep(actions)
for (const v of cloneActions) {
if (!v) {
continue
}
const action = Object.assign({}, v)
// 是否拥有这个action
const has = this.checkItem(action, 'has')
delete action['has']
if (!has) {
continue
}
// 是否有分割线
action.divided = this.checkItem(action, 'divided', false)
// 是否是disabled
const can = this.checkItem(action, 'can')
action.disabled = !can
if (action.dropdown) {
// const dropdown = this.cleanActions(action.dropdown)
action.dropdown = this.cleanActions(action.dropdown)
}
cleanedActions.push(action)
}
return cleanedActions
}
}
}
</script>
<style scoped>
.dropdown-menu-title {
text-align: left;
font-size: 12px;
color: #909399;
line-height: 30px;
padding-left: 10px;
padding-top: 10px;
border-top: solid 1px #e4e7ed;
}
.dropdown-menu-title:first-child {
padding-top: 0;
border-top: none;
}
.el-button-ungroup .action-item {
margin-left: 4px
}
.el-button-ungroup .action-item:first-child {
margin-left: 0;
}
</style>

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

@@ -4,7 +4,7 @@
:content="fields"
:form="basicForm"
label-position="right"
label-width="17%"
label-width="20%"
v-bind="$attrs"
v-on="$listeners"
>
@@ -12,9 +12,10 @@
<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>
<el-button v-if="defaultButton" size="small" :loading="isSubmitting" type="primary" @click="submitForm('form')">{{ $t('common.Submit') }}</el-button>
</el-form-item>
</ElFormRender>
@@ -31,10 +32,18 @@ export default {
type: Boolean,
default: true
},
hasButtons: {
type: Boolean,
default: true
},
hasReset: {
type: Boolean,
default: true
},
hasSaveContinue: {
type: Boolean,
default: true
},
fields: {
type: Array,
default: () => []
@@ -42,7 +51,7 @@ export default {
// 初始值
form: {
type: Object,
default: () => { return {} }
default: () => ({})
},
moreButtons: {
type: Array,
@@ -60,11 +69,11 @@ export default {
},
methods: {
// 获取表单数据
submitForm(formName) {
submitForm(formName, addContinue) {
const form = this.$refs[formName]
form.validate((valid) => {
if (valid) {
this.$emit('submit', form.getFormValue(), form)
this.$emit('submit', form.getFormValue(), form, addContinue)
} else {
this.$emit('invalid', valid)
return false
@@ -77,7 +86,7 @@ export default {
},
handleClick(button) {
const callback = button.callback || function(values, form) {
console.log('Click ', button.title, ': ', values)
// console.log('Click ', button.title, ': ', values)
}
const form = this.$refs['form']
const values = form.getFormValue()
@@ -88,27 +97,27 @@ export default {
</script>
<style lang="less" scoped>
.el-form /deep/ .el-form-item {
.el-form ::v-deep .el-form-item {
margin-bottom: 12px;
}
.el-form /deep/ .el-form-item__content {
.el-form ::v-deep .el-form-item__content {
width: 75%;
}
.el-form /deep/ .el-form-item__label {
.el-form ::v-deep .el-form-item__label {
padding: 0 30px 0 0;
}
.el-form /deep/ .el-form-item__error {
.el-form ::v-deep .el-form-item__error {
position: inherit;
}
.el-form /deep/ .form-group-header {
.el-form ::v-deep .form-group-header {
margin-left: 50px;
}
.el-form /deep/ .help-block {
.el-form ::v-deep .help-block {
display: block;
margin-top: 5px;
margin-bottom: 10px;
@@ -116,7 +125,7 @@ export default {
font-size: 12px;
line-height: 18px;
}
.el-form /deep/ .help-block a {
.el-form ::v-deep .help-block a {
color: #1c84c6;
}
</style>

View File

@@ -8,7 +8,27 @@ export const RequiredChange = {
required: true, message: i18n.t('common.fieldRequiredError'), trigger: 'change'
}
export const EmailCheck = {
type: 'email',
message: i18n.t('common.InputEmailAddress'),
trigger: ['blur', 'change']
}
export default {
Required,
RequiredChange
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

@@ -11,9 +11,10 @@
v-bind="tableAttrs"
:data="data"
:row-class-name="rowClassName"
v-on="$listeners"
@selection-change="selectStrategy.onSelectionChange"
@select="selectStrategy.onSelect"
@select-all="selectStrategy.onSelectAll($event, selectable)"
@select-all="selectStrategy.onSelectAll($event, canSelect)"
@sort-change="onSortChange"
>
<!--TODO 不用jsx写, 感觉template逻辑有点不清晰了-->
@@ -90,11 +91,14 @@
<!--非树-->
<template v-else>
<el-data-table-column v-if="hasSelection" type="selection" :align="selectionAlign" />
<el-data-table-column v-if="hasSelection" type="selection" :align="selectionAlign" :selectable="canSelect" />
<el-data-table-column
v-for="col in columns"
:key="col.prop"
:formatter="typeof col.formatter === 'function' ? col.formatter : null"
:filters="col.filters || null"
:filter-multiple="false"
:filter-method="typeof col.filterMethod === 'function' ? col.filterMethod : null"
v-bind="{align: columnsAlign, ...col}"
>
<template v-if="col.formatter && typeof col.formatter !== 'function'" v-slot:default="{row, column, index}">
@@ -163,7 +167,7 @@ import getLocatedSlotKeys from './utils/extract-keys'
import transformSearchImmediatelyItem from './utils/search-immediately-item'
import isFalsey from './utils/is-falsey'
import merge from 'deepmerge'
const defaultFirstPage = 0
const defaultFirstPage = 1
const noPaginationDataPath = 'payload'
export default {
@@ -422,7 +426,6 @@ export default {
onEdit: {
type: Function,
default(row) {
console.log('On delete row')
}
},
/**
@@ -713,6 +716,16 @@ export default {
hasDetail: {
type: Boolean,
default: true
},
canSelect: {
type: Function,
default(row, index) {
return true
}
},
totalData: {
type: Array,
default: null
}
},
data() {
@@ -800,6 +813,13 @@ export default {
},
_searchForm() {
return transformSearchImmediatelyItem(this.collapseForm, this)
},
lastPageNum() {
// page
const pageOffset = this.firstPage - defaultFirstPage
const pageCount = Math.ceil(this.total / this.size)
const lastPageNum = pageCount + pageOffset
return lastPageNum
}
},
watch: {
@@ -818,6 +838,13 @@ export default {
* @property {array} rows - 已选中的行数据的数组
*/
this.$emit('selection-change', val)
},
totalData(val) {
if (val && val.length !== this.total) {
this.page = defaultFirstPage
this.total = val.length
this.getList()
}
}
},
mounted() {
@@ -834,6 +861,9 @@ export default {
}
}
}
if (this.totalData) {
this.getList()
}
},
methods: {
getQuery() {
@@ -867,12 +897,58 @@ export default {
}
return query
},
getPageData() {
return this.data
},
async gotoNextPage() {
if (!this.hasNextPage()) {
return false
}
this.page += 1
await this.getList({ loading: true })
},
hasNextPage() {
return this.page < this.lastPageNum
},
getList({ loading = true } = {}) {
const { url } = this
if (url) {
return this.getListFromRemote({ loading: loading })
}
if (this.totalData) {
return this.getListFromStaticData({ loading: true })
}
// this.$log.debug("last page is: ", this.lastPageNum)
},
getListFromStaticData({ loading = true } = {}) {
if (loading) {
this.loading = true
}
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
const pageOffset = this.firstPage - defaultFirstPage
const page = this.page === 0 ? 1 : this.page
const start = (page + pageOffset - 1) * this.size
const end = (page + pageOffset) * this.size
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
},
/**
* 手动刷新列表数据,选项的默认值为: { loading: true }
* @public
* @param {object} options 方法选项
*/
getList({ loading = true } = {}) {
getListFromRemote({ loading = true } = {}) {
const { url } = this
if (!url) {
return
@@ -953,6 +1029,8 @@ export default {
})
},
search(attrs, reset) {
// 重置搜索结果到第一页
this.page = defaultFirstPage
// Orange 重置查询对象
if (reset) {
this.innerQuery = merge({}, attrs)
@@ -962,6 +1040,8 @@ export default {
return this.getList()
},
searchDate(attrs) {
// 重置搜索结果到第一页
this.page = defaultFirstPage
this.innerQuery = merge(this.innerQuery, attrs)
return this.getList()
},
@@ -996,7 +1076,7 @@ export default {
},
handleSizeChange(val) {
if (this.size === val) return
this.$emit('sizeChange', val)
this.page = defaultFirstPage
this.size = val
this.getList()

View File

@@ -1,16 +1,16 @@
.el-data-table /deep/ .el-pagination{
.el-data-table ::v-deep .el-pagination{
text-align: center !important;
}
.el-data-table /deep/ .el-table td{
.el-data-table ::v-deep .el-table td{
padding: 4px 0;
}
.el-data-table /deep/ .el-table th{
.el-data-table ::v-deep .el-table th{
padding: 4px 0;
}
.el-data-table/deep/ .el-form-item{
.el-data-table ::v-deep .el-form-item{
margin-bottom:10px !important ;
margin-top:10px;
}
.el-data-table/deep/ .el-pagination{
.el-data-table ::v-deep .el-pagination{
padding:15px 0 !important ;
}
}

View File

@@ -1,9 +1,17 @@
<template>
<ElDatableTable ref="table" class="el-table" v-bind="tableConfig" @update="onUpdate" v-on="iListeners" />
<ElDatableTable
ref="table"
class="el-table"
v-bind="tableConfig"
@update="onUpdate"
v-on="iListeners"
@sizeChange="handleSizeChange"
/>
</template>
<script>
import { default as ElDatableTable } from './compenents/el-data-table'
import { mapGetters } from 'vuex'
export default {
name: 'DataTable',
@@ -58,7 +66,6 @@ export default {
pageCount: 5,
paginationLayout: 'total, sizes, prev, pager, next',
paginationSizes: [15, 30, 50, 100],
paginationSize: 15,
paginationBackground: true,
transformQuery: query => {
if (query.page && query.size) {
@@ -85,12 +92,25 @@ export default {
},
computed: {
tableConfig() {
const config = Object.assign(this.defaultConfig, this.config)
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() {
return Object.assign({}, this.$listeners, this.tableConfig.listeners)
}
},
dataTable() {
return this.$refs.table
},
...mapGetters({
'globalTableConfig': 'tableConfig'
})
},
watch: {
config: {
@@ -131,6 +151,14 @@ export default {
this.toggleRowSelection(row, true)
}
}
},
handleSizeChange(val) {
this.$store.commit('table/SET_TABLE_CONFIG',
{
key: 'paginationSize',
value: val
}
)
}
}
}
@@ -138,16 +166,16 @@ export default {
<style lang="less" scoped>
.el-table /deep/ .el-table__row > td {
.el-table ::v-deep .el-table__row > td {
line-height: 1.5;
padding: 8px 0;
}
.el-table /deep/ .el-table__row > td> div > span {
.el-table ::v-deep .el-table__row > td> div > span {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.el-table /deep/ .el-table__header > thead > tr >th {
.el-table ::v-deep .el-table__header > thead > tr >th {
padding: 8px 0;
background-color: #F5F5F6;
font-size: 13px;
@@ -158,11 +186,11 @@ export default {
}
//分页
.el-pagination /deep/ .el-pagination__total{
.el-pagination ::v-deep .el-pagination__total{
float: left;
}
.el-pagination /deep/ .el-pagination__sizes{
.el-pagination ::v-deep .el-pagination__sizes{
float: left;
}
//修改颜色

View File

@@ -1,6 +1,9 @@
<template>
<div>
<div class="treebox">
<ul v-show="loading" class="ztree">
{{ this.$t('common.tree.Loading') }}...
</ul>
<div v-show="!loading" class="treebox">
<ul :id="iZTreeID" class="ztree">
{{ this.$t('common.tree.Loading') }}...
</ul>
@@ -22,6 +25,7 @@
import $ from '@/utils/jquery-vendor.js'
import '@ztree/ztree_v3/js/jquery.ztree.all.min.js'
import '@/styles/ztree.css'
import axiosRetry from 'axios-retry'
const defaultObject = {
type: Object,
@@ -39,7 +43,9 @@ export default {
iZTreeID: `zTree_${this._uid}`,
iRMenuID: `rMenu_${this._uid}`,
zTree: '',
rMenu: ''
rMenu: '',
init: false,
loading: false
}
},
computed: {
@@ -52,11 +58,30 @@ export default {
// $('.treebox').css('height', window.innerHeight - 60)
},
beforeDestroy() {
$.fn.zTree.destroy()
$.fn.zTree.destroy(this.iZTreeID)
},
methods: {
initTree: function() {
this.$axios.get(this.treeSetting.treeUrl).then(res => {
const vm = this
let treeUrl
if (this.init) {
this.loading = true
}
if (this.init && this.treeSetting.treeUrl.indexOf('/perms/') !== -1 && this.treeSetting.treeUrl.indexOf('rebuild_tree') === -1) {
treeUrl = (this.treeSetting.treeUrl.indexOf('?') === -1) ? `${this.treeSetting.treeUrl}?rebuild_tree=1` : `${this.treeSetting.treeUrl}&rebuild_tree=1`
} else {
treeUrl = this.treeSetting.treeUrl
}
this.$axios.get(treeUrl, {
'axios-retry': {
retries: 20,
retryCondition: e => {
return axiosRetry.isNetworkOrIdempotentRequestError(e) || e.response.status === 409
},
shouldResetTimeout: true,
retryDelay: () => { return 5000 }
}
}).then(res => {
if (!res) {
res = []
}
@@ -65,7 +90,13 @@ export default {
name: this.$t('common.tree.Empty')
})
}
this.treeSetting.treeUrl = treeUrl
if (this.init) {
vm.zTree.destroy()
}
this.zTree = $.fn.zTree.init($(`#${this.iZTreeID}`), this.treeSetting, res)
// 手动上报事件, Tree加载完成
this.$emit('TreeInitFinish', this.zTree)
if (this.treeSetting.showRefresh) {
this.rootNodeAddDom(
this.zTree,
@@ -79,6 +110,9 @@ export default {
if (this.treeSetting.otherMenu) {
$('.menu-actions').append(this.otherMenu)
}
}).finally(_ => {
vm.loading = false
vm.init = true
})
},
rootNodeAddDom: function(ztree, callback) {
@@ -95,7 +129,6 @@ export default {
}
const refreshIconRef = $('#tree-refresh')
refreshIconRef.bind('click', function() {
ztree.destroy()
const result = callback()
if (result && result.then) {
result.finally(() => {
@@ -158,7 +191,7 @@ export default {
top: 100%;
z-index: 1000;
}
.ztree /deep/ .fa-refresh {
.ztree ::v-deep .fa-refresh {
font: normal normal normal 14px/1 FontAwesome !important;
}
.dropdown a:hover {

View File

@@ -1,5 +1,5 @@
<template>
<ZTree ref="ztree" :setting="treeSetting">
<ZTree ref="ztree" :setting="treeSetting" v-on="$listeners">
<!--Slot透传-->
<div slot="rMenu" slot-scope="{data}">
<slot name="rMenu" :data="data" />
@@ -37,8 +37,8 @@ export default {
showRemoveBtn: false,
showRenameBtn: false,
drag: {
isCopy: true,
isMove: true
isCopy: false,
isMove: false
}
},
callback: {
@@ -72,7 +72,7 @@ export default {
},
methods: {
defaultCallback: function(action) {
console.log(action)
// console.log(action)
}
}
}

View File

@@ -3,7 +3,7 @@ export default {
name: 'ItemValue',
props: {
value: {
type: [String, Number, Function, Array, Object],
type: [String, Number, Function, Array, Object, Boolean],
default: ''
},
item: {
@@ -15,15 +15,33 @@ export default {
default: null
}
},
methods: {
toChoicesDisplay(value) {
if (!value) {
return this.$t('common.No')
}
return this.$t('common.Yes')
}
},
render(h) {
if (typeof this.formatter === 'function') {
return this.formatter(this.item, this.value)
}
return <span>{this.value}</span>
if (typeof this.value === 'boolean') {
return (
<span class='item-value'>{this.toChoicesDisplay(this.value)}</span>
)
}
return (
<span class='item-value'>{this.value}</span>
)
}
}
</script>
<style scoped>
.item-value {
word-break: break-word;
}
</style>

View File

@@ -1,11 +1,19 @@
<template>
<IBox :title="title" fa="fa-info-circle">
<div class="content">
<el-row v-if="this.$route.params.id" :gutter="10" class="item">
<el-col :span="6"><div :style="{ 'text-align': align }" class="item-label"><label>ID: </label></div></el-col>
<el-col :span="18"><div class="item-text">{{ this.$route.params.id }}</div></el-col>
</el-row>
<el-row v-for="item in items" :key="'card-' + item.key" :gutter="10" class="item">
<el-col :span="6"><div :style="{ 'text-align': align }" class="item-label"><label>{{ item.key }}: </label></div></el-col>
<el-col :span="18"><div class="item-text">
<ItemValue :value="item.value" v-bind="item" />
</div></el-col>
<el-col :span="6">
<div :style="{ 'text-align': align }" class="item-label"><label>{{ item.key }}: </label></div>
</el-col>
<el-col :span="18">
<div class="item-text">
<ItemValue :value="item.value" v-bind="item" />
</div>
</el-col>
</el-row>
<slot />
</div>

View File

@@ -10,7 +10,7 @@
<div slot="footer" class="dialog-footer">
<slot name="footer">
<el-button v-if="showCancel" size="small" @click="onCancel">{{ cancelTitle }}</el-button>
<el-button v-if="showConfirm" type="primary" size="small" @click="onConfirm">{{ confirmTitle }}</el-button>
<el-button v-if="showConfirm" type="primary" size="small" :loading="loadingStatus" @click="onConfirm">{{ confirmTitle }}</el-button>
</slot>
</div>
</el-dialog>
@@ -42,6 +42,10 @@ export default {
type: Boolean,
default: true
},
loadingStatus: {
type: Boolean,
default: false
},
confirmTitle: {
type: String,
default() {
@@ -51,7 +55,6 @@ export default {
},
data() {
return {
}
},
methods: {
@@ -70,4 +73,8 @@ export default {
/*padding-top: 10px;*/
}
.dialog-footer {
padding-right: 20px;
}
</style>

View File

@@ -93,17 +93,17 @@ export default {
<style lang='less' scoped>
.datepicker{
width: 240px;
width: 233px;
}
.el-input__inner{
border: 1px solid #dcdee2;
border-radius: 3px;
height: 36px;
}
/*.el-date-editor /deep/ .el-input__icon{*/
/*.el-date-editor ::v-deep .el-input__icon{*/
/* line-height: 28px;*/
/*}*/
.el-date-editor /deep/ .el-range-separator{
.el-date-editor ::v-deep .el-range-separator{
line-height: 28px;
}
</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,11 +2,13 @@
<el-select
ref="select"
v-model="iValue"
v-loading="!initialized"
v-loadmore="loadMore"
:options="iOptions"
:remote="remote"
:remote-method="filterOptions"
:multiple="multiple"
:clearable="clearable"
filterable
popper-append-to-body
class="select2"
@@ -69,6 +71,10 @@ export default {
type: Boolean,
default: true
},
clearable: {
type: Boolean,
default: true
},
//
value: {
type: [Array, String, Number, Boolean],
@@ -92,7 +98,6 @@ export default {
return {
loading: false,
initialized: false,
iValue: this.value ? this.value : [],
defaultParams: _.cloneDeep(defaultParams),
params: _.cloneDeep(defaultParams),
iOptions: this.options || [],
@@ -107,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) => {
@@ -156,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
@@ -172,6 +191,9 @@ export default {
},
methods: {
async loadMore(load) {
if (!this.iAjax.url) {
return
}
if (!this.params.hasMore) {
return
}
@@ -235,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
}
@@ -65,22 +65,24 @@ export default {
sortable: true,
formatterArgs: {
route: 'AssetDetail'
}
},
showOverflowTooltip: true
},
{
prop: 'ip',
label: this.$t('assets.IP'),
width: '140px',
sortable: 'custom'
},
{
prop: 'systemUsers',
label: this.$t('assets.SystemUsers'),
align: 'center',
width: '200px',
formatter: SystemUserFormatter,
formatterArgs: {
getUrl: this.getShowUrl.bind(this)
}
},
showOverflowTooltip: true
}
]
},

View File

@@ -1,24 +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 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: {
@@ -27,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: {
@@ -50,9 +79,12 @@ export default {
},
data() {
return {
showExportDialog: false,
exportOption: '',
meta: {}
exportDialogShow: false,
exportOption: 'all',
exportTypeOption: 'csv',
meta: {},
mfaVerified: false,
mfaDialogShow: false
}
},
computed: {
@@ -67,6 +99,8 @@ export default {
const query = listTableRef.dataTable.getQuery()
delete query['limit']
delete query['offset']
delete query['date_from']
delete query['date_to']
return query
},
tableHasQuery() {
@@ -77,7 +111,7 @@ export default {
{
label: this.$t('common.imExport.ExportAll'),
value: 'all',
can: this.canExportAll
can: this.canExportAll && !this.tableHasQuery
},
{
label: this.$t('common.imExport.ExportOnlySelectedItems'),
@@ -90,21 +124,50 @@ export default {
can: this.tableHasQuery && this.canExportFiltered
}
]
},
exportTypeOptions() {
return [
{
label: 'CSV',
value: 'csv',
can: true
},
{
label: 'Excel',
value: 'xlsx',
can: true
}
]
}
},
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') {
@@ -115,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'] = 'csv'
query['format'] = exportTypeOption
const queryStr =
(url.indexOf('?') > -1 ? '&' : '?') +
queryUtil.stringify(query, '=', '&')
@@ -129,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

@@ -1,52 +1,73 @@
<template>
<Dialog :title="$t('common.Import')" :visible.sync="showImportDialog" :destroy-on-close="true" @confirm="handleImportConfirm" @cancel="handleImportCancel()">
<el-form label-position="left" style="padding-left: 50px">
<Dialog
:title="importTitle"
:visible.sync="showImportDialog"
:destroy-on-close="true"
:close-on-click-modal="false"
:loading-status="loadStatus"
width="80%"
class="importDialog"
:show-cancel="false"
:show-confirm="false"
@close="handleImportCancel"
>
<el-form v-if="!showTable" label-position="left" style="padding-left: 50px">
<el-form-item :label="$t('common.Import' )" :label-width="'100px'">
<el-radio v-model="importOption" class="export-item" label="1">{{ this.$t('common.Create') }}</el-radio>
<el-radio v-model="importOption" class="export-item" label="2">{{ this.$t('common.Update') }}</el-radio>
<el-radio v-model="importOption" class="export-item" label="create">{{ this.$t('common.Create') }}</el-radio>
<el-radio v-model="importOption" class="export-item" label="update">{{ this.$t('common.Update') }}</el-radio>
<div style="line-height: 1.5">
<span v-if="importOption==='1'" class="el-upload__tip">
{{ this.$t('common.imExport.downloadImportTemplateMsg') }}
<el-link type="success" :underline="false" :href="downloadImportTempUrl">{{ this.$t('common.Download') }}</el-link>
</span>
<span v-else class="el-upload__tip">
{{ this.$t('common.imExport.downloadUpdateTemplateMsg') }}
<el-link type="success" :underline="false" @click="downloadUpdateTempUrl">{{ this.$t('common.Download') }}</el-link>
<span class="el-upload__tip">
{{ downloadTemplateTitle }}
<el-link type="success" :underline="false" style="padding-left: 10px" @click="downloadTemplateFile('csv')"> CSV </el-link>
<el-link type="success" :underline="false" style="padding-left: 10px" @click="downloadTemplateFile('xlsx')"> XLSX </el-link>
</span>
</div>
</el-form-item>
<el-form-item :label="$t('common.Upload' )" :label-width="'100px'">
<el-form-item :label="$t('common.Upload' )" :label-width="'100px'" class="file-uploader">
<el-upload
ref="upload"
drag
action="string"
list-type="text/csv"
:http-request="handleImport"
:limit="1"
:auto-upload="false"
:on-change="onFileChange"
:before-upload="beforeUpload"
accept=".csv,.xlsx"
>
<el-button size="mini" type="default">{{ this.$t('common.SelectFile') }}</el-button>
<div slot="tip" :class="uploadHelpTextClass" style="line-height: 1.5">{{ this.$t('common.imExport.onlyCSVFilesTips') }}</div>
<i class="el-icon-upload" />
<div class="el-upload__text">{{ $t('common.imExport.dragUploadFileInfo') }}</div>
<div slot="tip" class="el-upload__tip">
<span :class="{'hasError': hasFileFormatOrSizeError }">{{ $t('common.imExport.uploadCsvLth10MHelpText') }}</span>
<div v-if="renderError" class="hasError">{{ renderError }}</div>
</div>
</el-upload>
</el-form-item>
</el-form>
<div v-if="errorMsg" class="error-msg error-results">
<ul v-if="typeof errorMsg === 'object'">
<li v-for="(item, index) in errorMsg" :key="item + '-' + index"> {{ item }}</li>
</ul>
<span v-else>{{ errorMsg }}</span>
<div v-else class="importTableZone">
<ImportTable
ref="importTable"
:json-data="jsonData"
:import-option="importOption"
:url="url"
@cancel="cancelUpload"
@finish="closeDialog"
/>
</div>
</Dialog>
</template>
<script>
import Dialog from '@/components/Dialog'
import ImportTable from '@/components/ListTable/TableAction/ImportTable'
import { getErrorResponseMsg } from '@/utils/common'
import { createSourceIdCache } from '@/api/common'
export default {
name: 'ImportDialog',
components: {
Dialog
Dialog,
ImportTable
},
props: {
selectedRows: {
@@ -61,82 +82,119 @@ export default {
data() {
return {
showImportDialog: false,
importOption: '1',
isCsv: true,
errorMsg: ''
importOption: 'create',
errorMsg: '',
loadStatus: false,
importTypeOption: 'csv',
importTypeIsCsv: true,
showTable: false,
renderError: '',
hasFileFormatOrSizeError: false,
jsonData: {}
}
},
computed: {
hasSelected() {
return this.selectedRows.length > 0
},
upLoadUrl() {
return this.url
},
downloadImportTempUrl() {
const baseUrl = (process.env.VUE_APP_ENV === 'production') ? (`${this.url}`) : (`${process.env.VUE_APP_BASE_API}${this.url}`)
return baseUrl + '?format=csv&template=import&limit=1'
},
uploadHelpTextClass() {
const cls = ['el-upload__tip']
if (!this.isCsv) {
cls.push('error-msg')
}
return cls
},
downloadTemplateTitle() {
if (this.importOption === 'create') {
return this.$t('common.imExport.downloadImportTemplateMsg')
} else {
return this.$t('common.imExport.downloadUpdateTemplateMsg')
}
},
importTitle() {
if (this.importOption === 'create') {
return this.$t('common.Import') + this.$t('common.Create')
} else {
return this.$t('common.Import') + this.$t('common.Update')
}
}
},
watch: {
importOption(val) {
this.showTable = false
}
},
mounted() {
this.$eventBus.$on('showImportDialog', (row) => {
this.showImportDialog = true
this.$eventBus.$on('showImportDialog', ({ url }) => {
if (url === this.url) {
this.showImportDialog = true
}
})
},
methods: {
performUpdate(item) {
this.$axios.put(
this.upLoadUrl,
item.file,
{ headers: { 'Content-Type': 'text/csv' }, disableFlashErrorMsg: true }
).then((data) => {
const msg = this.$t('common.imExport.updateSuccessMsg', { count: data.length })
this.onSuccess(msg)
closeDialog() {
this.showImportDialog = false
},
cancelUpload() {
this.showTable = false
this.renderError = ''
this.jsonData = {}
},
onFileChange(file, fileList) {
fileList.splice(0, fileList.length)
if (file.status !== 'ready') {
return
}
// const isCsv = file.raw.type = 'text/csv'
if (!this.beforeUpload(file)) {
return
}
const isCsv = file.name.indexOf('csv') > -1
const url = new URL(this.url, 'http://localhost')
url.pathname += 'render-to-json/'
const renderToJsonUrl = url.toString().replace('http://localhost', '')
this.$axios.post(
renderToJsonUrl,
file.raw,
{ headers: { 'Content-Type': isCsv ? 'text/csv' : 'text/xlsx' }, disableFlashErrorMsg: true }
).then(data => {
this.jsonData = data
this.showTable = true
}).catch(error => {
this.catchError(error)
fileList.splice(0, fileList.length)
this.renderError = getErrorResponseMsg(error)
}).finally(() => {
this.loadStatus = false
})
},
performCreate(item) {
this.$axios.post(
this.upLoadUrl,
item.file,
{ headers: { 'Content-Type': 'text/csv' }, disableFlashErrorMsg: true }
).then((data) => {
const msg = this.$t('common.imExport.createSuccessMsg', { count: data.length })
this.onSuccess(msg)
}).catch(error => {
this.catchError(error)
})
beforeUpload(file) {
const isLt30M = file.size / 1024 / 1024 < 30
if (!isLt30M) {
this.hasFileFormatOrSizeError = true
}
return isLt30M
},
async downloadTemplateFile(tp) {
const downloadUrl = await this.getDownloadTemplateUrl(tp)
window.open(downloadUrl)
},
async getDownloadTemplateUrl(tp) {
const template = this.importOption === 'create' ? 'import' : 'update'
let query = `format=${tp}&template=${template}`
if (this.importOption === 'update' && this.selectedRows.length > 0) {
const resources = []
for (const item of this.selectedRows) {
resources.push(item.id)
}
const resp = await createSourceIdCache(resources)
query += `&spm=${resp.spm}`
} else {
query += '&limit=1'
}
return this.url.indexOf('?') === -1 ? `${this.url}?${query}` : `${this.url}&${query}`
},
catchError(error) {
this.$refs.upload.clearFiles()
if (error.response && error.response.status === 400) {
const errorData = error.response.data
const totalErrorMsg = []
errorData.forEach((value, index) => {
if (typeof value === 'string') {
totalErrorMsg.push(`line ${index}. ${value}`)
} else {
const errorMsg = [`line ${index}. `]
for (const [k, v] of Object.entries(value)) {
if (v) {
errorMsg.push(`${k}: ${v}`)
}
}
if (errorMsg.length > 1) {
totalErrorMsg.push(errorMsg.join(' '))
}
}
})
this.errorMsg = totalErrorMsg
}
console.log(error)
},
onSuccess(msg) {
this.errorMsg = ''
@@ -148,33 +206,14 @@ export default {
a.click()
window.URL.revokeObjectURL(url)
},
handleImport(item) {
if (this.importOption === '1') {
this.performCreate(item)
} else {
this.performUpdate(item)
}
},
async downloadUpdateTempUrl() {
var resources = []
const data = this.selectedRows
for (let index = 0; index < data.length; index++) {
resources.push(data[index].id)
}
const spm = await createSourceIdCache(resources)
const baseUrl = (process.env.VUE_APP_ENV === 'production') ? (`${this.url}`) : (`${process.env.VUE_APP_BASE_API}${this.url}`)
const url = `${baseUrl}?format=csv&template=update&spm=` + spm.spm
return this.downloadCsv(url)
},
async handleImportConfirm() {
this.$refs.upload.submit()
this.$refs['importTable'].performUpload()
},
handleImportCancel() {
this.showImportDialog = false
},
beforeUpload(file) {
this.isCsv = _.endsWith(file.name, 'csv')
return this.isCsv
this.showTable = false
this.renderError = ''
this.jsonData = {}
}
}
}
@@ -191,4 +230,49 @@ export default {
overflow: auto
}
.importDialog >>> .el-form-item.file-uploader {
padding-right: 150px;
}
.file-uploader >>> .el-upload {
width: 100%;
//padding-right: 150px;
}
.file-uploader >>> .el-upload-dragger {
width: 100%;
}
.importTableZone {
padding: 0 20px;
.importTable {
overflow: auto;
}
.tableFilter {
padding-bottom: 10px;
}
}
.importTable >>> .el-dialog__body {
padding-bottom: 20px;
}
.export-item {
margin-left: 80px;
}
.export-item:first-child {
margin-left: 0;
}
.hasError {
color: $--color-danger;
}
.el-upload__tip {
line-height: 1.5;
padding-top: 0;
}
</style>

View File

@@ -0,0 +1,427 @@
<template>
<div>
<el-row>
<el-col :span="8">
<div class="tableFilter">
<el-radio-group v-model="importStatusFilter" size="small">
<el-radio-button label="all">{{ $t('common.Total') }}</el-radio-button>
<el-radio-button label="ok">{{ $t('common.Success') }}</el-radio-button>
<el-radio-button label="error">{{ $t('common.Failed') }}</el-radio-button>
<el-radio-button label="pending">{{ $t('common.Pending') }}</el-radio-button>
</el-radio-group>
</div>
</el-col>
<el-col :span="8" style="text-align: center">
<span class="summary-item summary-total"> {{ $t('common.Total') }}: {{ totalCount }}</span>
<span class="summary-item summary-success"> {{ $t('common.Success') }}: {{ successCount }}</span>
<span class="summary-item summary-failed"> {{ $t('common.Failed') }}: {{ failedCount }}</span>
<span class="summary-item summary-pending"> {{ $t('common.Pending') }}: {{ pendingCount }}</span>
</el-col>
</el-row>
<div class="row">
<el-progress :percentage="processedPercent" />
</div>
<DataTable v-if="tableGenDone" id="importTable" ref="dataTable" :config="tableConfig" class="importTable" />
<div class="row" style="padding-top: 20px">
<div style="float: right">
<el-button size="small" @click="performCancel">{{ $t('common.Cancel') }}</el-button>
<el-button size="small" type="primary" @click="performImportAction">{{ importActionTitle }}</el-button>
</div>
</div>
</div>
</template>
<script>
import DataTable from '@/components/DataTable'
import { sleep, getUpdateObjURL } from '@/utils/common'
import { EditableInputFormatter, StatusFormatter } from '@/components/TableFormatters'
export default {
name: 'ImportTable',
components: {
DataTable
},
props: {
jsonData: {
type: Object,
default: () => ({})
},
url: {
type: String,
required: true
},
importOption: {
type: String,
required: true
}
},
data() {
return {
columns: [],
importStatusFilter: 'all',
iTotalData: [],
tableConfig: {
hasSelection: false,
// hasPagination: false,
columns: [],
totalData: [],
paginationSize: 10,
paginationSizes: [10],
tableAttrs: {
stripe: true, // 斑马纹表格
border: true, // 表格边框
fit: true, // 宽度自适应,
tooltipEffect: 'dark'
}
},
tableGenDone: false,
importTaskStatus: 'pending', // pending, started, stopped, done
importTaskResult: '', // success, hasError
hasImport: false,
hasContinueButton: false,
importActions: {
import: this.$t('common.Import'),
continue: this.$t('common.Continue'),
stop: this.$t('common.Stop'),
finished: this.$t('common.Finished')
}
}
},
computed: {
tableColumnNameMapper() {
const mapper = {}
for (const column of this.tableConfig.columns) {
mapper[column['prop']] = column['label']
}
return mapper
},
importAction() {
switch (this.importTaskStatus) {
case 'pending':
return 'import'
case 'started':
return 'stop'
}
if (this.totalCount === this.successCount) {
return 'finished'
} else {
return 'continue'
}
},
importActionTitle() {
return this.importActions[this.importAction]
},
successData() {
return this.iTotalData.filter((item) => {
return item['@status'] === 'ok'
})
},
failedData() {
return this.iTotalData.filter((item) => {
return typeof item['@status'] === 'object' && item['@status'].name === 'error'
})
},
pendingData() {
return this.iTotalData.filter((item) => {
return item['@status'] === 'pending'
})
},
totalCount() {
return this.iTotalData.length
},
successCount() {
return this.successData.length
},
failedCount() {
return this.failedData.length
},
pendingCount() {
return this.pendingData.length
},
processedCount() {
return this.totalCount - this.pendingCount
},
processedPercent() {
if (this.totalCount === 0) {
return 0
}
return Math.round(this.processedCount / this.totalCount * 100)
},
elDataTable() {
return this.$refs['dataTable'].dataTable
}
},
watch: {
importStatusFilter(val) {
if (val === 'all') {
this.tableConfig.totalData = this.iTotalData
} else if (val === 'error') {
this.tableConfig.totalData = this.failedData
} else {
this.tableConfig.totalData = this.iTotalData.filter((item) => {
return item['@status'] === val
})
}
}
},
mounted() {
this.generateTable()
},
methods: {
generateTableColumns(tableTitles, tableData) {
const vm = this
const columns = [{
prop: '@status',
label: vm.$t('common.Status'),
width: '80px',
align: 'center',
formatter: StatusFormatter,
formatterArgs: {
iconChoices: {
ok: 'fa-check text-primary',
error: 'fa-times text-danger',
pending: 'fa-clock-o'
},
getChoicesKey(val) {
if (val === 'ok' || val === 'pending') {
return val
}
return 'error'
},
getTip(val) {
if (val === 'ok') {
return vm.$t('common.Success')
} else if (val === 'pending') {
return vm.$t('common.Pending')
} else if (val && val.name === 'error') {
return val.error
}
return ''
},
hasTips: true
}
}]
for (const item of tableTitles) {
const dataItemLens = tableData.map(d => {
const prop = item[1]
const itemColData = d[prop]
if (!d) {
return 0
}
if (typeof itemColData !== 'number' && (!itemColData || !itemColData.length)) {
return 0
}
return itemColData.length
})
let colMaxWidth = Math.max(...dataItemLens) * 10
if (colMaxWidth === 0) {
continue
}
colMaxWidth = Math.min(180, colMaxWidth)
colMaxWidth = Math.max(colMaxWidth, 100)
columns.push({
prop: item[1],
label: item[0],
minWidth: colMaxWidth + 'px',
showOverflowTooltip: true,
formatter: EditableInputFormatter,
formatterArgs: {
onEnter: ({ row, col, oldValue, newValue }) => {
const prop = col.prop
row['@status'] = 'pending'
this.$log.debug(`Set value ${oldValue} => ${newValue}`)
this.$set(row, prop, newValue)
}
}
})
}
return columns
},
generateTableData(tableTitles, tableData) {
const totalData = []
tableData.forEach(item => {
this.$set(item, '@status', 'pending')
totalData.push(item)
})
return totalData
},
generateTable() {
const tableTitles = this.jsonData['title']
const tableData = this.jsonData['data']
const columns = this.generateTableColumns(tableTitles, tableData)
const totalData = this.generateTableData(tableTitles, tableData)
this.tableConfig.columns = columns
this.tableGenDone = true
setTimeout(() => {
this.iTotalData = totalData
this.tableConfig.totalData = totalData
}, 200)
},
beautifyErrorData(errorData) {
if (typeof errorData === 'string') {
return errorData
} else if (Array.isArray(errorData)) {
return errorData
} else if (typeof errorData !== 'object') {
return errorData
}
const data = []
// eslint-disable-next-line prefer-const
for (let [key, value] of Object.entries(errorData)) {
if (typeof value === 'object') {
value = this.beautifyErrorData(value)
}
let label = this.tableColumnNameMapper[key]
if (!label) {
label = key
}
data.push(`${label}: ${value}`)
}
return data
},
performCancel() {
this.performStop()
this.$emit('cancel')
},
performFinish() {
this.performStop()
this.$emit('finish')
},
taskIsStopped() {
return this.importTaskStatus === 'stopped'
},
performImportAction() {
switch (this.importAction) {
case 'continue':
return this.performContinue()
case 'import':
return this.performUpload()
case 'stop':
return this.performStop()
case 'finished':
return this.performFinish()
}
},
performContinue() {
if (this.importTaskStatus === 'done') {
for (const item of this.failedData) {
item['@status'] = 'pending'
}
this.tableConfig.totalData = this.pendingData
}
this.importTaskStatus = 'started'
setTimeout(() => {
this.performUpload()
}, 100)
},
performStop() {
this.importTaskStatus = 'stopped'
},
async performUploadCurrentPageData() {
const currentData = this.elDataTable.getPageData()
for (const item of currentData) {
if (item['@status'] !== 'pending') {
continue
}
if (this.taskIsStopped()) {
return
}
await this.performUploadObject(item)
await sleep(100)
}
},
async performUpload() {
this.importTaskStatus = 'started'
this.importStatusFilter = 'pending'
while (!this.taskIsStopped()) {
await this.performUploadCurrentPageData()
const hasNextPage = this.elDataTable.hasNextPage()
if (hasNextPage && !this.taskIsStopped()) {
await this.elDataTable.gotoNextPage()
await sleep(100)
} else {
break
}
}
if (this.pendingCount === 0) {
this.importTaskStatus = 'done'
}
if (this.failedCount > 0) {
this.$message.error(this.$t('common.imExport.hasImportErrorItemMsg') + '')
}
},
async performUpdateObject(item) {
const updateUrl = getUpdateObjURL(this.url, item.id)
return this.$axios.put(
updateUrl,
item,
{ disableFlashErrorMsg: true }
)
},
async performUploadObject(item) {
let handler = this.performCreateObject
if (this.importOption === 'update') {
handler = this.performUpdateObject
}
try {
await handler.bind(this)(item)
item['@status'] = 'ok'
} catch (error) {
const errorData = error?.response?.data
const _error = this.beautifyErrorData(errorData)
item['@status'] = {
name: 'error',
error: _error
}
}
},
async performCreateObject(item) {
return this.$axios.post(
this.url,
item,
{ disableFlashErrorMsg: true }
)
},
keepElementInViewport() {
const tableRef = document.getElementById('importTable')
const pendingRef = tableRef?.getElementsByClassName('pendingStatus')[0]
if (!pendingRef) {
return
}
const parentTdRef = pendingRef.parentElement.parentElement.parentElement.parentElement
const rect = parentTdRef.getBoundingClientRect()
let windowInnerHeight = window.innerHeight || document.documentElement.clientHeight
windowInnerHeight = windowInnerHeight * 0.97 - 150
const inViewport = (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= windowInnerHeight
)
if (!inViewport) {
parentTdRef.scrollIntoView({ behavior: 'auto', block: 'start', inline: 'start' })
}
}
}
}
</script>
<style lang="scss" scoped>
@import "~@/styles/element-variables.scss";
.summary-item {
padding: 0 10px
}
.summary-success {
color: $--color-primary;
}
.summary-failed {
color: $--color-danger;
}
.importTable >>> .cell {
min-height: 20px;
height: 100%;
overflow: auto;
}
</style>

View File

@@ -1,34 +1,46 @@
<template>
<ActionsGroup v-if="hasLeftActions" :actions="actions" :more-actions="moreActions" :more-actions-title="moreActionsTitle" v-bind="$attrs" class="header-action" />
<DataActions
v-if="hasLeftActions"
:actions="iActions"
v-bind="$attrs"
class="header-action"
/>
</template>
<script>
import ActionsGroup from '@/components/ActionsGroup'
import i18n from '@/i18n/i18n'
import DataActions from '@/components/DataActions'
import { createSourceIdCache } from '@/api/common'
import { cleanActions } from './utils'
const defaultTrue = { type: Boolean, default: true }
const defaultFalse = { type: Boolean, default: false }
const defaultTrue = { type: [Boolean, Function], default: true }
const defaultFalse = { type: [Boolean, Function], default: false }
export default {
name: 'LeftSide',
components: {
ActionsGroup
DataActions
},
props: {
hasCreate: defaultTrue,
hasBulkDelete: defaultTrue,
hasBulkUpdate: defaultFalse,
hasLeftActions: defaultTrue,
tableUrl: {
type: String,
default: ''
},
hasCreate: defaultTrue,
canCreate: defaultTrue,
createRoute: {
type: [String, Object],
type: [String, Object, Function],
default: function() {
return this.$route.name.replace('List', 'Create')
}
},
createInNewPage: {
type: Boolean,
default: false
},
hasBulkDelete: defaultTrue,
hasBulkUpdate: defaultFalse,
hasMoreActions: defaultTrue,
tableUrl: {
type: String,
default: ''
},
reloadTable: {
type: Function,
default: () => {}
@@ -53,23 +65,41 @@ export default {
type: String,
default: null
},
moreActionsButton: {
moreCreates: {
type: Object,
default: () => ({})
default: null
},
createTitle: {
type: String,
default: () => i18n.t('common.Create')
}
},
data() {
const defaultActions = [
{
name: 'actionCreate',
title: this.createTitle,
type: 'primary',
has: this.hasCreate && !this.moreCreates,
can: this.canCreate,
callback: this.handleCreate
}
]
if (this.moreCreates) {
const defaultMoreCreate = {
name: 'actionMoreCreate',
title: this.createTitle,
type: 'primary',
has: true,
can: this.canCreate,
dropdown: [],
callback: this.handleCreate
}
const createCreateAction = Object.assign(defaultMoreCreate, this.moreCreates)
defaultActions.push(createCreateAction)
}
return {
defaultActions: [
{
name: 'actionCreate',
title: this.$t('common.Create'),
type: 'primary',
has: this.hasCreate,
can: true,
callback: this.handleCreate
}
],
defaultActions: defaultActions,
defaultMoreActions: [
{
title: this.$t('common.deleteSelected'),
@@ -92,6 +122,9 @@ export default {
}
},
computed: {
iActions() {
return [...this.actions, this.moreAction]
},
actions() {
const actions = [...this.defaultActions, ...this.extraActions]
return cleanActions(actions, true, {
@@ -99,12 +132,20 @@ export default {
reloadTable: this.reloadTable
})
},
moreActions() {
const actions = [...this.defaultMoreActions, ...this.extraMoreActions]
return cleanActions(actions, true, {
moreAction() {
if (!this.hasMoreActions) {
return
}
let dropdown = [...this.defaultMoreActions, ...this.extraMoreActions]
dropdown = cleanActions(dropdown, true, {
selectedRows: this.selectedRows,
reloadTable: this.reloadTable
})
return {
name: 'moreActions',
title: this.moreActionsTitle || this.$t('common.MoreActions'),
dropdown: dropdown
}
},
hasSelectedRows() {
return this.selectedRows.length > 0
@@ -112,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" />
<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,20 +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,
handleTableSettingClick: {
type: Function,
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: () => []
@@ -54,12 +81,12 @@ export default {
data() {
return {
defaultRightSideActions: [
{ 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: {
@@ -73,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()
}
}
}
@@ -119,7 +144,7 @@ export default {
}
.right-side-actions >>> .el-button:hover {
background-color: rgb(0, 0, 0, 0.05);
background-color: rgba(0, 0, 0, 0.05);
}
.action-search >>> .el-input__suffix i {

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

@@ -5,7 +5,9 @@ export function cleanActions(actions, canDefaults, { selectedRows, reloadTable }
cloneActions.forEach((action) => {
action.has = cleanBoolean(action, 'has', true, { selectedRows, reloadTable })
action.can = cleanBoolean(action, 'can', true, { selectedRows, reloadTable })
action.callback = cleanCallback(action, { selectedRows, reloadTable })
if (!action.dropdown) {
action.callback = cleanCallback(action, { selectedRows, reloadTable })
}
cleanedActions.push(action)
})
return cleanedActions

View File

@@ -1,78 +0,0 @@
<template>
<ActionsGroup :size="'mini'" :actions="cleanedActions" :more-actions="cleanMoreActions" v-bind="$attrs" />
</template>
<script>
import ActionsGroup from '@/components/ActionsGroup/index'
import BaseFormatter from './base'
export default {
name: 'CustomActionsFormatterVue',
components: {
ActionsGroup
},
extends: BaseFormatter,
computed: {
cleanedActions() {
if (this.col.actions.actions instanceof Array) {
const copy = _.cloneDeep(this.col.actions.actions)
let actions = [...copy]
actions = actions.map((v) => {
v.has = this.checkBool(v, 'has')
v.can = this.checkBool(v, 'can')
v.callback = this.cleanCallback(v)
return v
})
return actions
}
return []
},
cleanMoreActions() {
if (this.col.actions.extraActions instanceof Array) {
const copy = _.cloneDeep(this.col.actions.extraActions)
let actions = [...copy]
actions = actions.map((v) => {
v.has = this.checkBool(v, 'has')
v.can = this.checkBool(v, 'can')
v.callback = this.cleanCallback(v)
return v
})
return actions
}
return []
}
},
mounted() {
console.log(this.col)
},
methods: {
checkBool(item, attr, defaults) {
if (!item) {
return false
}
let ok = item[attr]
if (ok && typeof ok === 'function') {
ok = ok(this.row, this.cellValue)
} else if (ok == null) {
ok = defaults === undefined ? true : defaults
}
return ok
},
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) }
}
}
}
</script>
<style scoped>
</style>

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" :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>
@@ -13,6 +26,7 @@ import IBox from '../IBox'
import TableAction from './TableAction'
import Emitter from '@/mixins/emitter'
import deepmerge from 'deepmerge'
export default {
name: 'ListTable',
components: {
@@ -46,8 +60,12 @@ export default {
},
iTableConfig() {
const config = deepmerge(this.tableConfig, { extraQuery: this.extraQuery })
this.$log.debug('Header actions', this.headerActions)
this.$log.debug('ListTable: iTableConfig change', config)
return config
},
tableUrl() {
return this.iTableConfig.url
}
},
watch: {
@@ -56,10 +74,14 @@ export default {
this.$log.debug('ListTable: found extraQuery change')
},
deep: true
},
tableColConfig: {
handler() {
this.$log.debug('ListTable: found colConfig change')
},
deep: true
}
},
mounted() {
},
methods: {
handleSelectionChange(val) {
this.selectedRows = val
@@ -68,8 +90,13 @@ export default {
this.dataTable.getList()
},
search(attrs) {
this.$emit('TagSearch', attrs)
return this.dataTable.search(attrs, true)
},
filter(attrs) {
this.$emit('TagFilter', attrs)
this.$refs.dataTable.$refs.dataTable.search(attrs, true)
},
handleDateChange(attrs) {
this.$set(this.extraQuery, 'date_from', attrs[0].toISOString())
this.$set(this.extraQuery, 'date_to', attrs[1].toISOString())
@@ -81,6 +108,7 @@ export default {
date_from: attrs[0].toISOString(),
date_to: attrs[1].toISOString()
}
this.$emit('TagDateChange', attrs)
return this.dataTable.searchDate(query)
},
toggleRowSelection(row, isSelected) {
@@ -92,28 +120,19 @@ export default {
<style lang="scss" scoped>
.table-content {
margin-top: 10px;
.table-content {
margin-top: 10px;
& >>> .el-card__body {
padding: 0;
}
& >>> .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;*!*/
/*}*/
& >>> .el-card__body {
padding: 0;
}
& >>> .el-table__header thead > tr > th {
background-color: white;
}
}
//修改颜色
// .el-button--text{
// color: #409EFF;
// }
//修改颜色
// .el-button--text{
// color: #409EFF;
// }
</style>

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

View File

@@ -1,29 +1,36 @@
<template>
<IBox :fa="icon" :type="type" :title="title" v-bind="$attrs">
<table style="width: 100%">
<table style="width: 100%;table-layout:fixed;" class="CardTable">
<tr>
<td colspan="2">
<Select2 ref="select2" v-model="select2.value" v-bind="select2" />
<Select2 ref="select2" v-model="select2.value" :disabled="iDisabled" v-bind="select2" />
</td>
</tr>
<slot />
<tr>
<td colspan="2">
<el-button :type="type" size="small" :loading="submitLoading" @click="addObjects">{{ $t('common.Add') }}</el-button>
<el-button :type="type" size="small" :loading="submitLoading" :disabled="iDisabled" @click="addObjects">
{{ $t('common.Add') }}
</el-button>
</td>
</tr>
<template v-if="showHasObjects">
<tr v-for="obj of iHasObjects" :key="obj.value" style="width: 100%" class="item">
<td><b>{{ obj.label }}</b></td>
<tr v-for="obj of iHasObjects" :key="obj.value" class="item">
<td style="width: 100%;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;">
<el-tooltip style="margin: 4px;" effect="dark" :content="obj.label" placement="left">
<b>{{ obj.label }}</b>
</el-tooltip>
</td>
<td>
<el-button size="mini" type="danger" style="float: right" @click="removeObject(obj)">
<el-button size="mini" :disabled="iDisabled" type="danger" style="float: right" @click="removeObject(obj)">
<i class="fa fa-minus" />
</el-button>
</td>
</tr>
</template>
<tr v-if="params.hasMore" class="item">
<tr v-if="params.hasMore && showHasMore" class="item">
<td colspan="2">
<el-button :type="type" size="small" style="width: 100%" @click="loadMore">
<el-button :type="type" :disabled="iDisabled" size="small" style="width: 100%" @click="loadMore">
<i class="fa fa-arrow-down" />
{{ $t('common.More') }}
</el-button>
@@ -34,10 +41,10 @@
</template>
<script>
import Select2 from '../Select2'
import Select2 from '../FormFields/Select2'
import IBox from '../IBox'
import { createSourceIdCache } from '@/api/common'
import { mapGetters } from 'vuex'
export default {
name: 'RelationCard',
components: {
@@ -83,6 +90,14 @@ export default {
type: [Array, Number, String],
default: () => []
},
disabled: {
type: [Boolean, Function],
default: null
},
showHasMore: {
type: Boolean,
default: true
},
performDelete: {
type: Function,
default: (obj, that) => {}
@@ -130,11 +145,13 @@ export default {
ajax: this.objectsAjax,
options: this.objects,
value: this.value,
disabled: this.disabled,
disabledValues: []
}
}
},
computed: {
...mapGetters(['currentOrgIsRoot']),
iAjax() {
return this.$refs.select2.iAjax
},
@@ -143,6 +160,12 @@ export default {
},
hasObjectLeftLength() {
return this.totalHasObjectsLength - this.iHasObjects.length
},
iDisabled() {
if (this.disabled !== null) {
return this.disabled
}
return this.currentOrgIsRoot
}
},
watch: {

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 }}
@@ -24,6 +27,19 @@ const defaultUpdateCallback = function({ row, col }) {
this.$router.push(route)
}
const defaultCloneCallback = function({ row, col }) {
const id = row.id
let route = { query: { clone_from: id }}
const cloneRoute = this.colActions.cloneRoute
if (typeof cloneRoute === 'object') {
route = Object.assign(route, cloneRoute)
} else {
route.name = cloneRoute
}
this.$router.push(route)
}
const defaultDeleteCallback = function({ row, col, cellValue, reload }) {
let msg = this.$t('common.deleteWarningMsg')
const name = row.name || row.hostname
@@ -68,13 +84,21 @@ export default {
default: function() {
return {
hasUpdate: true, // can set function(row, value)
canUpdate: true, // can set function(row, value)
canUpdate: () => {
return !this.$store.getters.currentOrgIsRoot
}, // can set function(row, value)
hasDelete: true, // can set function(row, value)
canDelete: true,
hasClone: true,
canClone: () => {
return !this.$store.getters.currentOrgIsRoot
},
updateRoute: this.$route.name.replace('List', 'Update'),
cloneRoute: this.$route.name.replace('List', 'Create'),
performDelete: defaultPerformDelete,
onUpdate: defaultUpdateCallback,
onDelete: defaultDeleteCallback,
onClone: defaultCloneCallback,
extraActions: [] // format see defaultActions
}
}
@@ -89,7 +113,8 @@ export default {
type: 'primary',
has: colActions.hasUpdate,
can: colActions.canUpdate,
callback: colActions.onUpdate
callback: colActions.onUpdate,
order: 10
},
{
name: 'delete',
@@ -97,7 +122,17 @@ export default {
type: 'danger',
has: colActions.hasDelete,
can: colActions.canDelete,
callback: colActions.onDelete
callback: colActions.onDelete,
order: 20
},
{
name: 'clone',
title: this.$t('common.Clone'),
type: 'info',
has: colActions.hasClone,
can: colActions.canClone,
callback: colActions.onClone,
order: 30
}
]
return {
@@ -112,12 +147,16 @@ 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)
actions.sort((a, b) => a.order - b.order)
return actions
},
actions() {
@@ -131,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,
@@ -151,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

@@ -0,0 +1,15 @@
<template>
<span>{{ cellValue.toString() }}</span>
</template>
<script>
import BaseFormatter from './base'
export default {
name: 'ArrayFormatter',
extends: BaseFormatter
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,42 @@
<template>
<i :class="'fa ' + iconClass" />
</template>
<script>
import BaseFormatter from './base'
export default {
name: 'ChoicesFormatter',
extends: BaseFormatter,
props: {
formatterArgsDefault: {
type: Object,
default() {
return {
iconChoices: {
true: 'fa-check text-primary',
false: 'fa-times text-danger'
},
typeChange(val) {
return !!val
}
}
}
}
},
data() {
return {
formatterArgs: Object.assign(this.formatterArgsDefault, this.col.formatterArgs)
}
},
computed: {
iconClass() {
const key = this.formatterArgs.typeChange(this.cellValue)
return this.formatterArgs.iconChoices[key]
}
}
}
</script>
<style scoped>
</style>

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" />
@@ -22,18 +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.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
}
}
}
@@ -46,15 +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() {
return this.cellValue.datetime
tips() {
return this.formatterArgs.getTips({ cellValue: this.cellValue, row: this.row })
}
}
}

View File

@@ -4,14 +4,14 @@
<script>
import BaseFormatter from './base'
import { toSafeLocalDateStr } from '@/utils/common'
export default {
name: 'DateFormatter',
extends: BaseFormatter,
data() {
let value
if (this.cellValue) {
const dt = new Date(this.cellValue)
value = this.$d(dt, 'medium')
value = toSafeLocalDateStr(this.cellValue)
} else {
value = ''
}

View File

@@ -1,5 +1,5 @@
<template>
<el-button ref="deleteButton" size="mini" type="danger" :disabled="canDelete" @click="onDelete(col, row, cellValue, reload)">
<el-button ref="deleteButton" size="mini" type="danger" :disabled="iDisabled" @click="onDelete(col, row, cellValue, reload)">
<i class="fa fa-minus" />
</el-button>
</template>
@@ -11,18 +11,19 @@ export default {
name: 'DeleteActionFormatter',
extends: BaseFormatter,
computed: {
canDelete() {
return this.iCanDelete()
iDisabled() {
//
return (this.disabled() || this.$store.getters.currentOrgIsRoot)
}
},
methods: {
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))
this.$message.error(this.$t('common.deleteErrorMsg') + ' ' + error)
})
},
onDelete(col, row, cellValue, reload) {
@@ -32,7 +33,10 @@ export default {
this.defaultOnDelete(col, row, cellValue, reload)
}
},
iCanDelete() {
disabled() {
if (this.col.objects === 'all') {
return false
}
return this.col.objects.indexOf(this.cellValue) === -1
}
}

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

@@ -0,0 +1,84 @@
<template>
<div style="width: 100%;min-height: 20px" @click.stop="editCell">
<el-input
v-if="inEditMode"
v-model="value"
size="mini"
class="editInput"
@keyup.enter.native="onInputEnter"
@blur="onInputEnter"
/>
<template v-else>
<span>{{ cellValue }}</span>
</template>
</div>
</template>
<script>
import BaseFormatter from './base'
export default {
name: 'EditableInputFormatter',
components: {
},
extends: BaseFormatter,
props: {
formatterArgsDefault: {
type: Object,
default() {
return {
trigger: 'click',
onEnter: ({ row, col, oldValue, newValue }) => {
const prop = col.prop
this.$log.debug(`Set value ${oldValue} => ${newValue}`)
this.$set(row, prop, newValue)
}
}
}
}
},
data() {
const valueIsString = typeof this.cellValue === 'string'
const jsonValue = this.cellValue ? JSON.stringify(this.cellValue) : ''
return {
inEditMode: false,
value: valueIsString ? this.cellValue || '' : jsonValue,
valueIsString: valueIsString,
formatterArgs: Object.assign(this.formatterArgsDefault, this.col.formatterArgs)
}
},
methods: {
editCell() {
this.inEditMode = true
},
onInputEnter() {
let validValue = this.value
try {
validValue = JSON.parse(validValue)
} catch (e) {
// pass
}
this.formatterArgs.onEnter({
row: this.row, col: this.col,
oldValue: this.cellValue,
newValue: validValue
})
this.inEditMode = false
},
cancelEdit() {
this.inEditMode = false
}
}
}
</script>
<style scoped>
.editInput >>> .el-input__inner {
padding: 2px;
line-height: 12px;
}
.editInput {
padding: -6px;
}
</style>

View File

@@ -0,0 +1,68 @@
<template>
<div>
<el-tooltip v-if="formatterArgs.hasTips" placement="bottom" effect="dark">
<div slot="content">
<template v-if="tipsIsArray">
<div v-for="tip of tips" :key="tip">
<span>{{ tip }}</span>
<br>
</div>
</template>
<span v-else>
{{ tips }}
</span>
</div>
<i :class="'fa ' + iconClass" />
</el-tooltip>
<i v-else :class="'fa ' + iconClass" />
</div>
</template>
<script>
import BaseFormatter from './base'
export default {
name: 'StatusFormatter',
extends: BaseFormatter,
props: {
formatterArgsDefault: {
type: Object,
default() {
return {
iconChoices: {
true: 'fa-check text-primary',
false: 'fa-times text-danger'
},
getChoicesKey(val) {
return !!val
},
getTip(val, col) {
},
hasTips: false
}
}
}
},
data() {
return {
formatterArgs: Object.assign(this.formatterArgsDefault, this.col.formatterArgs)
}
},
computed: {
iconClass() {
const key = this.formatterArgs.getChoicesKey(this.cellValue)
return this.formatterArgs.iconChoices[key] + ' ' + key + 'Status'
},
tips() {
const vm = this
return this.formatterArgs.getTip(this.cellValue, vm)
},
tipsIsArray() {
return Array.isArray(this.tips)
}
}
}
</script>
<style scoped>
</style>

View File

@@ -1,39 +1,45 @@
import DetailFormatter from './DetailFormatter'
import ArrayFormatter from './ArrayFormatter'
import DisplayFormatter from './DisplayFormatter'
import BooleanFormatter from './ChoicesFormatter'
import BooleanFormatter from './BooleanFormatter'
import ChoicesFormatter from './ChoicesFormatter'
import ActionsFormatter from './ActionsFormatter'
import CustomActionsFormatter from './CustomActionsFormatter'
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'
export default {
DetailFormatter,
DisplayFormatter,
BooleanFormatter,
ChoicesFormatter,
ActionsFormatter,
CustomActionsFormatter,
DeleteActionFormatter,
DateFormatter,
SystemUserFormatter,
ShowKeyFormatter,
DialogDetailFormatter,
LoadingActionsFormatter
ArrayFormatter,
EditableInputFormatter,
StatusFormatter
}
export {
DetailFormatter,
DisplayFormatter,
BooleanFormatter,
ChoicesFormatter,
ActionsFormatter,
CustomActionsFormatter,
DeleteActionFormatter,
DateFormatter,
SystemUserFormatter,
ShowKeyFormatter,
DialogDetailFormatter,
LoadingActionsFormatter
ArrayFormatter,
EditableInputFormatter,
StatusFormatter
}

View File

@@ -1,14 +1,35 @@
<template>
<div class="filter-field">
<el-cascader ref="Cascade" :options="options" :props="config" @change="handleMenuItemChange" />
<el-tag v-for="(v, k) in filterTags" :key="k" :name="k" closable size="small" class="filter-tag" type="info" @close="handleTagClose(k)">
<el-tag
v-for="(v, k) in filterTags"
:key="k"
:name="k"
closable
size="small"
class="filter-tag"
type="info"
:disable-transitions="true"
@close="handleTagClose(k)"
@click="handleTagClick(v,k)"
>
<strong v-if="v.label">{{ v.label + ':' }}</strong>
<span v-if="v.valueLabel">{{ v.valueLabel }}</span>
<span v-else>{{ v.value }}</span>
</el-tag>
<span v-if="keyLabel" slot="prefix" class="filterTitle">{{ keyLabel + ':' }}</span>
<el-input ref="SearchInput" v-model="filterValue" :placeholder="placeholder" class="search-input" @blur="focus = false" @focus="focus = true" @change="handleConfirm" />
<el-input
ref="SearchInput"
v-model="filterValue"
:placeholder="placeholder"
class="search-input"
@blur="focus = false"
@focus="focus = true"
@change="handleConfirm"
/>
</div>
</template>
<script>
@@ -65,13 +86,13 @@ export default {
}
},
watch: {
filterTags: {
handler(val) {
this.$nextTick(() => this.$emit('tagSearch', this.filterMaps))
// this.$emit('tagSearch', this.filterMaps)
},
deep: true
}
// filterTags: {
// handler(val) {
// this.$nextTick(() => this.$emit('tagSearch', this.filterMaps))
// // this.$emit('tagSearch', this.filterMaps)
// },
// deep: true
// }
},
mounted() {
setTimeout(() => {
@@ -116,6 +137,7 @@ export default {
},
handleTagClose(evt) {
this.$delete(this.filterTags, evt)
this.$emit('tagSearch', this.filterMaps)
return true
},
handleConfirm() {
@@ -124,9 +146,33 @@ export default {
}
const tag = { key: this.filterKey, label: this.keyLabel, value: this.filterValue, valueLabel: this.valueLabel }
this.$set(this.filterTags, this.filterKey, tag)
this.$emit('tagSearch', this.filterMaps)
this.filterKey = ''
this.filterValue = ''
this.valueLabel = ''
},
handleTagClick(v, k) {
let unableChange = false
for (const field of this.options) {
if (field.value === v.key) {
if (field.type === 'choice') {
unableChange = true
}
if (field.type === 'boolean') {
unableChange = true
}
}
}
if (unableChange) {
return
}
if (this.filterValue.length !== 0) {
this.handleConfirm()
}
this.$delete(this.filterTags, k)
this.filterKey = v.key
this.filterValue = v.value
this.$refs.SearchInput.focus()
}
}
}

View File

@@ -5,8 +5,10 @@
<component
:is="component"
ref="AutoDataZTree"
:key="componentTreeKey"
:setting="treeSetting"
class="auto-data-ztree"
v-on="$listeners"
@urlChange="handleUrlChange"
>
<div slot="rMenu" slot-scope="{data}">
@@ -22,7 +24,7 @@
</div>
<div class="transition-box" style="width: calc(100% - 17px);">
<slot name="table">
<ListTable ref="ListTable" :key="componentKey" :table-config="iTableConfig" :header-actions="headerActions" />
<ListTable ref="ListTable" :key="componentKey" :table-config="iTableConfig" :header-actions="headerActions" v-on="$listeners" />
</slot>
</div>
</div>
@@ -63,25 +65,40 @@ export default {
return {
iTableConfig: this.tableConfig,
iShowTree: this.showTree,
componentKey: 0
componentKey: 0,
componentTreeKey: 0
}
},
watch: {
treeConfig: {
handler(val) {
},
deep: true
}
},
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() {
this.componentKey += 1
},
forceRerenderTree() {
this.componentTreeKey += 1
},
hideRMenu() {
this.$refs.AutoDataZTree.hideRMenu()
},
getSelectedNodes: function() {
return this.$refs.AutoDataZTree.getSelectedNodes()
},
getNodes: function() {
return this.$refs.AutoDataZTree.getNodes()
},
selectNode: function(node) {
return this.$refs.AutoDataZTree.selectNode(node)
}
}
}
@@ -98,6 +115,7 @@ export default {
color: #FFFFFF;
border-radius: 3px;
line-height: 1.428;
cursor:pointer;
}
.el-tree{
background-color: inherit !important;

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'

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,58 @@
{
"": "",
"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",
"ip_group": "IP group",
"action": "Action",
"priority": "Priority",
"date_created": "Date created",
"created_by": "Created by",
"asset": "Asset",
"system_user": "System user",
"username_group":"Username group",
"hostname_group":"Hostname group",
"asset_ip_group": "Asset ip group",
"system_users_name_group": "Systemusers name group",
"system_users_protocol_group": "Systemusers protocol group",
"system_users_username_group": "systemusers username group",
"apply_login_asset": "Apply login asset",
"apply_login_system_user": "Apply login system user",
"apply_login_user": "Apply login user",
"RuleDetail": "Rule detail"
},
"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",
"vmware_client":"Vmware Client",
"custom":"Custom",
"mysql": "MySQL",
"oracle": "Oracle",
"postgresql": "PostgreSQL",
"mariadb": "MariaDB",
"k8s": "kubernetes"
},
"applicationsCategory": {
"remote_app": "Remote app",
"db": "Database app",
"cloud": "Cloud app"
},
"appPath": "App path",
"appType": "App type",
"appName": "App name",
"asset": "Asset",
"database": "Database",
"host": "Host",
@@ -17,6 +66,7 @@
"mysql_workbench": "MySQL Workbench",
"mysql_workbench_ip": "DB IP",
"mysql_workbench_name": "DB Name",
"mysql_workbench_port": "DB Port",
"mysql_workbench_username": "DB Account",
"mysql_workbench_password": "DB Password",
"vmware_client": "vSphere Client",
@@ -28,20 +78,37 @@
"custom_target": "target URL",
"custom_username": "Account",
"custom_password": "Password",
"Custom": "Custom"
"Custom": "Custom",
"cluster": "Cluster",
"kubernetes":"Kubernetes",
"clusterHelpTextMessage": "Tips: https://172.16.8.8:8443",
"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",
"AssetNumber": "Asset number",
"AssetUserList": "Asset user list",
"TestGatewayTestConnection":"Test gateway test connection",
"TestGatewayHelpMessage": "If use nat, set the ssh real port",
"SshPort": "SSH Port",
"Assets": "Assets",
"Auth": "Auth",
"AutoGenerateKey": "Auto generate ssh key",
@@ -57,12 +124,15 @@
"CommandFilterRules": "Command filter rules",
"Comment": "Comment",
"Cpu": "Cpu",
"CommonUser": "Common user",
"CreatedBy": "Created by",
"Database": "Database",
"DateJoined": "Date joined",
"DateUpdated": "Date updated",
"DeactiveSelected": "Deactive selected",
"Disk": "Disk",
"AdDomain": "AD Domain",
"AdDomainHelpText": "AD domain provided to domain users for login",
"Domain": "Domain",
"DomainDetail": "Domain detail",
"DomainHelpMessage": "The domain function is added to address the fact that some environments (such as the hybrid cloud) cannot be connected directly by jumping on the gateway server.\nJMS => Domain gateway => Target assets",
@@ -98,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",
@@ -106,23 +177,25 @@
"RefreshHardware": "Refresh hardware",
"RemoteAppListHelpMessage": "Before using this feature, make sure that the application loader has been uploaded to the application server and successfully published as a RemoteApp application <b><a href='https://github.com/jumpserver/Jmservisor/releases'> Download application loader</a></b>",
"RemoteApps": "Remote apps",
"Applications": "Applications",
"RemoteType": "Remote type",
"RemoveFromCurrentNode": "Remove from node",
"ReplaceNodeAssetsAdminUserWithThis": "Replace node assets admin user with this",
"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": "Unselected assets",
"UnselectedAssets": "No asset selected or the selected asset does not support SSH protocol connection",
"UnselectedNodes": "Unselected nodes",
"UpdateAssetUserToken": "Update asset user auth",
"Username": "Username",
@@ -132,13 +205,25 @@
"Version": "Version",
"command_filter_list": "Command filter list",
"date_joined": "Date joined",
"sshKeyFingerprint": "SSH fingerprint",
"ip": "IP",
"sshkey": "sshkey"
"sshkey": "sshkey",
"GroupsHelpMessage": "Please fill in user groups, separated by commas if there are multiple user groups(Please fill in the existing user groups)",
"HomeHelpMessage": "Default home directory: /home/system username",
"Home": "Home",
"LinuxUserAffiliateGroup": "Linux user affiliate group",
"ipDomain": "IP(Domain)",
"HostProtocol": "Host Protocol",
"DatabaseProtocol": "Database Protocol",
"OtherProtocol": "Other Protocol",
"PasswordOrToken": "Password / Token"
},
"audits": {
"Hosts": "Host",
"RunUser": "Run user",
"User": "User",
"Username": "Username",
"SystemUserName": "System username",
"View": "View"
},
"auth": {
@@ -146,17 +231,38 @@
"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",
"RequestTickets": "Request tickets",
"Actions": "Actions",
"NeedSpecifiedFile": "Required to upload the specified format file",
"TestPortErrorMsg":"Port Error, please check",
"Activate": "Activate",
"actionsTips":"Clipboard's copy and paste control only support RDP/VNC protocol.",
"Active": "Active",
"TableColSettingInfo": "Please select the list details you want to display",
"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",
"BadRequestErrorMsg": "Bad request, please check again",
"BadRoleErrorMsg": "Bad request, no permission for this operation",
"BadConflictErrorMsg": "Refreshing, please try again later",
"Basic": "Basic",
"BasicInfo": "Basic info",
"ApplyInfo": "Apply info",
"Cancel": "Cancel",
"Close": "Close",
"Command filter": "Command filter",
@@ -164,8 +270,9 @@
"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",
"DateLast3Months": "Last 3 months",
"DateLastMonth": "Last month",
@@ -179,12 +286,19 @@
"EnterForSearch": "Press enter to search",
"Export": "Export",
"Import": "Import",
"ContinueImport": "ContinueImport",
"Continue": "Continue",
"Stop": "Stop",
"Finished": "Finished",
"Refresh": "Refresh",
"Info": "Info",
"MFAConfirm": "MFA Confirm",
"MFARequireForSecurity": "MFA required for security",
"PasswordConfirm": "Password Confirm",
"PasswordRequireForSecurity": "Password required for security",
"Members": "Members",
"More": "More",
"Message": "Message",
"MoreActions": "Actions",
"Name": "Name",
"No": "No",
@@ -193,20 +307,26 @@
"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",
"Show": "Show",
"Submit": "Submit",
"Test": "Test",
"SaveAndAddAnother":"Save and add another",
"TestSuccessMsg": "Test Success",
"To": "To",
"Update": "Update",
"bind": "Bind",
"unbind": "Unbind",
"Upload": "Upload",
"Clone": "Clone",
"Username": "Username",
"Validity": "Validity",
"Invalidity": "Invalidity",
@@ -218,11 +338,15 @@
"bulkDeleteSuccessMsg": "Bulk delete success",
"bulkRemoveErrorMsg": "Bulk remove failed: ",
"bulkRemoveSuccessMsg": "Bulk remove success",
"NeedAssetsAndSystemUserErrMsg": "Need assets and systemuser",
"createBy": "Create by",
"cloneFrom": "Clone from",
"createErrorMsg": "Create error",
"createSuccessMsg": "Create success",
"saveSuccessContinueMsg": "Create success, you may add another",
"createdBy": "Created by",
"dateCreated": "Date created",
"dateFinished": "Date finished",
"dateExpired": "Date expired",
"dateStart": "Date start",
"deleteErrorMsg": "Delete failed",
@@ -233,6 +357,12 @@
"disableSelected": "Disable selected",
"fieldRequiredError": "This field is required",
"getErrorMsg": "Get failed",
"fileType": "File type",
"Status": "Status",
"Total": "Total",
"Success": "Success",
"Failed": "Failed",
"Pending": "Pending",
"imExport": {
"ExportAll": "Export all",
"ExportOnlyFiltered": "Export only filtered",
@@ -242,7 +372,10 @@
"downloadImportTemplateMsg": "Download import template",
"downloadUpdateTemplateMsg": "Download update template",
"onlyCSVFilesTips": "Only csv supported",
"updateSuccessMsg": "Update success, total: {count}"
"updateSuccessMsg": "Update success, total: {count}",
"dragUploadFileInfo": "Drag file here or click to upload",
"uploadCsvLth10MHelpText": "csv/xlsx files with a size less than 10M",
"hasImportErrorItemMsg": "There is an error item, click the x icon to view the details, and continue to import after editing"
},
"isValid": "Is valid",
"nav": {
@@ -278,7 +411,9 @@
"NUMBER_REQUIRED": "Number required",
"SPECIAL_CHAR_REQUIRED": "Special char required",
"MIN_LENGTH_ERROR": "Password minimum length {}"
}
},
"lastCannotBeDeleteMsg": "The last one can't be delete",
"InvalidJson": "Not a valid json format"
},
"dashboard": {
"ActiveAsset": "Asset active",
@@ -358,7 +493,7 @@
},
"perms": {
"": "",
"Actions": "Actions",
"Actions": "Permission",
"Asset": "Asset",
"Basic": "Basic",
"Exclude": "Exclude",
@@ -369,59 +504,81 @@
"SystemUser": "System user",
"User": "User",
"UserGroups": "UserGroups",
"DatabaseAppPermission": "Databases permissions",
"RemoteAppPermission": "Remote apps permissions",
"KubernetesAppPermission": "Kubernetes permissions",
"addAssetToThisPermission": "Add asset to this permission",
"addDatabaseAppToThisPermission": "Add DatabaseApp to this permission",
"addK8sAppToThisPermission": "Add KubernetesApp to this permission",
"addApplicationToThisPermission": "Add Application to this permission",
"addNodeToThisPermission": "Add node to this permission",
"addRemoteAppToThisPermission": "Add RemoteApp to this permission",
"addSystemUserToThisPermission": "System user",
"addUserGroupToThisPermission": "Add user group to this permission",
"addUserToThisPermission": "Add user to this permission",
"all": "All",
"PermName":"Perm name",
"assetAndNode": "Assets and node",
"assetCount": "Asset count",
"connect": "Connect",
"databaseApp": "DatabaseApp",
"KubernetesApp": "KubernetesApp",
"dateStart": "Date start",
"downloadFile": "Download file",
"hostName": "Hostname",
"isValid": "Validity",
"fromTicket": "From ticket",
"isEffective": "Effective",
"nodeCount": "Node count",
"refreshFail": "Refresh fail",
"refreshPermissionCache": "Refresh permission cache",
"refreshSuccess": "Refresh success",
"remoteApp": "RemoteApp",
"remoteAppCount": "RemoteApp count",
"appsCount": "App count",
"appsList":"App list",
"DatabaseAppCount": "DatabaseApp count",
"KubernetesAppCount": "KubernetesApp count",
"systemUserCount": "System user count",
"upDownload": "Upload download",
"uploadFile": "Upload file",
"clipboardCopyPaste":"Copy Paste",
"clipboardCopy":"Clipboard copy",
"clipboardPaste":"Clipboard paste",
"userCount": "User count",
"userGroupCount": "User group count",
"usersAndUserGroups": "Users and user groups"
},
"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",
@@ -430,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",
@@ -438,58 +595,83 @@
"DatabaseAppPermissionDetail": "Databases permissions detail",
"DatabaseAppPermissionUpdate": "Databases permissions update",
"DatabaseAppUpdate": "Database app update",
"KubernetesApp": "Kubernetes Apps",
"KubernetesAppCreate": "Kubernetes app create",
"KubernetesAppDetail": "Kubernetes app detail",
"KubernetesAppPermission": "Kubernetes permissions",
"KubernetesAppPermissionCreate": "Kubernetes permissions create",
"KubernetesAppPermissionDetail": "Kubernetes permissions detail",
"KubernetesAppPermissionUpdate": "Kubernetes permissions update",
"KubernetesAppUpdate": "Kubernetes app update",
"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",
"AssetAclCreate": "Asset acl create",
"AssetAclUpdate": "Asset acl update",
"AssetAclDetail": "Asset acl detail",
"DomainCreate": "Domain create",
"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",
"RemoteAppPermissionCreate": "Remote apps permission create",
"RemoteAppPermissionDetail": "Remote apps permissions detail",
"RemoteAppPermissionUpdate": "Remote app permission update",
"ApplicationDetail": "Application detail",
"ApplicationPermissionCreate": "Application permission create",
"ApplicationPermissionDetail": "Application permission detail",
"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",
"Tickets": "Tickets",
"UserCreate": "User create",
"UserDetail": "User detail",
"UserFirstLogin": "UserFirstLogin",
"UserGroupCreate": "User group create",
"UserGroupDetail": "User group detail",
"UserGroupList": "User groups",
"UserGroupList": "User Groups",
"UserGroupUpdate": "User group update",
"UserGuide": "UserGuide",
"UserList": "Users",
@@ -497,17 +679,25 @@
"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",
"active": "active",
"alive": "alive",
"asset": "Asset",
"target": "Target",
"bucket": "Bucket",
"command": "Command",
"Activity": "Activity",
"commandStorage": "Command storage",
"comment": "Comment",
"containerName": "Container name",
@@ -521,6 +711,11 @@
"duration": "Duration",
"endPoint": "Endpoint",
"endpointSuffix": "Endpoint suffix",
"sessionActiveCount": "session active count",
"systemCpuLoad": "cpu load",
"systemDiskUsedPercent": "disk used percent",
"systemMemoryUsedPercent": "memory used percent",
"EsDisabled": "Node is unavailable, please contact administrator",
"go": "Go",
"goto": "Goto",
"hosts": "Hosts",
@@ -544,7 +739,9 @@
"systemUser": "System user",
"terminalDetail": "Terminal detail",
"terminalUpdate": "Update terminal",
"terminalUpdateStorage": "Update terminal storage",
"terminate": "Terminate",
"sessionTerminate": "Session Terminate",
"test": "Test",
"type": "Type",
"user": "Use",
@@ -552,9 +749,41 @@
"common": "common"
},
"Monitor": "Monitor",
"TerminateTaskSendSuccessMsg": "Terminate task has been send, Please check later"
"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)",
"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",
"ossEndpoint": "OSS: http://{REGION_NAME}.aliyuncs.com<br>Example: http://oss-cn-hangzhou.aliyuncs.com"
}
},
"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",
@@ -574,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",
@@ -583,11 +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",
@@ -602,11 +835,15 @@
"emailTest": "Test connection",
"emailUserSSL": "Use SSL",
"emailUserTLS": "Use TLS",
"MailSend": "Mail send",
"LDAPServerInfo": "LDAP Server",
"LDAPUser": "LDAP User",
"InsecureCommandAlert": "Insecure command alert",
"helpText": {
"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)",
@@ -635,6 +872,11 @@
"securityPasswordUpperCase": "After opening, the user password changes and resets must contain uppercase letters",
"securityServiceAccountRegistration": "Allow using bootstrap token register service account, when terminal setup, can disable it"
},
"validatorMessage": {
"EnsureThisValueIsGreaterThanOrEqualTo3": "Ensure this value is greater than or equal to 3",
"EnsureThisValueIsGreaterThanOrEqualTo5": "Ensure this value is greater than or equal to 5",
"EnsureThisValueIsGreaterThanOrEqualTo6": "Ensure this value is greater than or equal to 6"
},
"import": "Import",
"importLdapUserTip": "Please submit the LDAP configuration before import",
"importLdapUserTitle": "LDAP user list",
@@ -670,19 +912,37 @@
"userGuideUrl": "User Guide URL",
"username": "Username",
"usernamePlaceholder": "Please input username",
"refreshLdapCache":"Refreshing Ldap cache "
},
"settings": {
"refreshLdapCache":"Refreshing Ldap cache ",
"LicenseExpired": "License expired",
"LicenseWillBe": "License will expire at ",
"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",
"RequestPerm":"Request Perm",
"AssignedInfo":"Assigned Info",
"OpenTicket": "Open Ticket",
"HandleTicket": "Handle Ticket",
"FinishedTicket": "Finished Ticket",
"Assignees": "Assignees",
"Close": "Close",
"OpenStatus":"Open",
"CloseStatus":"Close",
"Comment": "Comment",
"MyTickets": "My tickets",
"action": "Action",
"IPGroup": "IP 组",
"Reject": "Reject",
"date": "Date",
"reply": "Reply",
@@ -691,7 +951,30 @@
"type": "Type",
"user": "User",
"Status": "Status",
"Open": "Open"
"Open": "Open",
"OrgName":"Org name",
"IP": "IP",
"Hostname": "Hostname",
"Asset": "Asset",
"SystemUser": "System user",
"Applicant": "Applicant",
"RequestAssetPerm": "Request asset perm",
"RequestApplicationPerm": "Request application perm",
"Pending": "Open",
"Approved": "Approved",
"Rejected": "Rejected",
"Closed": "Closed",
"helpText": {
"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",
@@ -701,12 +984,17 @@
"RenameNode": "Rename node",
"ShowAssetAllChildrenNode": "Show asset all children node",
"ShowAssetOnlyCurrentNode": "Show asset only current node",
"CheckAssetsAmount": "Check assets amount",
"ShowNodeInfo": "Show node information",
"TestNodeAssetConnectivity": "Test node asset connectivity",
"UpdateNodeAssetHardwareInfo": "Update node asset hardware information"
},
"users": {
"MessageSubscription": "Message Subscription",
"AuthSettings": "Auth settings",
"UserName": "Name",
"Account": "Account",
"Existing":"Existing",
"Authentication": "Account",
"Comment": "Comment",
"ConfirmPassword": "Confirm password",
@@ -714,16 +1002,31 @@
"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",
"InviteUserInOrg": "Invite user in this org",
"Invite": "Invite",
"Guide": "Guide",
"OrgUser": "Org User",
"OrgAdmin": "Org Admin",
"OrgAuditor": "Org Auditor",
"HelpText": {
"MFAOfUserFirstLoginPersonalInformationImprovementPage": "Enable multi-factor authentication to make the account more secure <br/> After is enabled, you will enter the multi-factor authentication binding process on your next login <br/> You can also bind directly in (personal information -> fast modifier -> modifier multiple factor Settings)",
"MFAOfUserFirstLoginUserGuidePage": "To protect the security of you and the company <br/> please properly keep your account, password, key and other important and sensitive information <br/> (e.g., set a complex password and enable multi-factor authentication)",
"SSHKeyOfProfileSSHUpdatePage": "Copy your public key here"
"SSHKeyOfProfileSSHUpdatePage": "Copy your public key here",
"OrgRoleHelpText": "Organizational roles are the user's role in the current organization"
},
"IAgree": "I agree",
"ImprovePersonalInformation": "Improve personal information",
@@ -742,6 +1045,8 @@
"ResetAndDownloadSSHKey": "Reset and download SSH Key",
"ResetPublicKeyAndDownload": "Reset public key and download",
"Role": "Role",
"SuperRole": "Super role",
"OrgRole": "Org role",
"SSHKey": "SSH Key",
"SSHKeySetting": "SSH Key setting",
"Secure": "Secure",
@@ -758,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"
@@ -772,28 +1081,64 @@
"tabs": {
"assetPermissionRules": "Asset permission rules",
"databasePermissionRules": "Database Permission rules",
"k8sPermissionRules": "Kubernetes Permission rules",
"grantedAssets": "Granted assets",
"grantedK8Ss":"Granted K8Ss",
"grantedDatabases": "Granted databases",
"grantedRemoteApps": "Granted remote apps",
"grantedApplications": "Granted applications",
"ApplicationPermissionRules": "Application permission rules",
"remoteAppPermissionRules": "Remote app permission rules"
},
"UpdatePassword": "",
"UpdatePublicKey": ""
"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",
@@ -803,6 +1148,9 @@
"ExecutionDetail": "Execution detail",
"ExecutionList": "Execution list",
"ExecutionTimes": "Execution times",
"validatorMessage": {
"EnsureThisValueIsGreaterThanOrEqualTo1": "Ensure this value is greater than or equal to 1"
},
"HelpText": {
"CrontabOfCreateUpdatePage": "For example: every Sunday at 03:05 execute <5 3 * * 0> <br/> Using the 5-bit Linux crontab expression <minute hour day month week> (<a href=\"https://tool.lu/crontab/\" target=\"_blank\"> Online tool </a>) <br/> If both regularly perform and cycle perform execution are set, use regularly perform first",
"IntervalOfCreateUpdatePage": "Unit: hour",
@@ -824,12 +1172,28 @@
"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",
"Azure":"Azure(China)",
"Azure_Int": "Azure(International)",
"HostnameStrategy": "Used to produce the asset hostname. For example, 1. Instance name (instanceDemo)2. Instance name and Partial IP (instanceDemo-250.1)",
"IsAlwaysUpdate": "Asset info is kept up-to-date",
"AccountCreate": "Create account",
"AccountList": "Account list",
"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",
"SyncInstanceTaskCreate": "Create sync instance task",
"SyncInstanceTaskList": "Sync instance task list",
@@ -868,28 +1232,49 @@
"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",
"ServiceRatio": "Service ratio",
"LoadStatus":"Status",
"NormalLoad":"Normal",
"HighLoad":"High",
"Offline": "Offline",
"CriticalLoad":"Critical",
"LicenseFile": "License file",
"NoLicense": "No License",
"Node": "Node",
"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",
"DeleteOrgMsg":"User list、User group、Asset list、Domain list、Admin user、System user、Labels、Asset permission"
"DeleteOrgMsg":"User list、User group、Asset list、Domain list、Admin user、System user、Labels、Asset permission",
"OrgRole": "Org role",
"CreateOrgMsg": "Please go to Organization Details to add users",
"AddOrgMembers": "Add organization members",
"users_amount": "Users amount",
"groups_amount": "Groups amount",
"assets_amount": "Assets amount",
"admin_users_amount": "Admin users amount",
"system_users_amount": "System users amount",
"applications_amount": "Applications amount",
"asset_perms_amount": "Asset perms amount",
"app_perms_amount": "App perms amount"
},
"RestoreButton": "Restore Default",
"SubscriptionID": "Subscription ID",
@@ -906,9 +1291,9 @@
"loginImageTip": "Tips: This will be displayed on the enterprise user login page. (suggest image size: 492px*472px)",
"loginTitle": "Title of login page",
"loginTitleTip": "Tips: This will be displayed on the enterprise user login page. (eg: Welcome to the JumpServer open source fortress)",
"logoIndex": "Logo of management page",
"logoIndex": "Logo (It contains text)",
"logoIndexTip": "Tips: This will appear at the top left of the administration page. (suggest image size: 185px*55px)",
"logoLogout": "Logo of logout page",
"logoLogout": "Logo (It contains no text)",
"logoLogoutTip": "Tips: This will be displayed on the enterprise user logout page. (suggest image size: 82px*82px)",
"restoreDialogMessage": "This will restore default Settings of the interface !!!",
"restoreDialogTitle": "Are you sure?",

View File

@@ -1,10 +1,10 @@
<template>
<div class="footer" :style="style">
<div class="pull-right">
Version <strong>2.0.2</strong> <span v-if="!publicSettings.XPACK_LICENSE_IS_VALID"> GPLv2. </span>
Version <strong> dev </strong> <span v-if="!publicSettings.XPACK_LICENSE_IS_VALID"> GPLv2. </span>
</div>
<div v-if="!publicSettings.XPACK_LICENSE_IS_VALID" style="padding-left:20px;">
<strong>Copyright</strong> FIT2CLOUD 飞致云 © 2014-2020
<strong>Copyright</strong> FIT2CLOUD 飞致云 © 2014-2021
</div>
</div>
</template>

View File

@@ -5,6 +5,8 @@
:method="method"
:form="form"
:url="iUrl"
:has-save-continue="iHasSaveContinue"
:has-reset="iHasReset"
:is-submitting="isSubmitting"
v-bind="$attrs"
v-on="$listeners"
@@ -13,50 +15,71 @@
</template>
<script>
import AutoDataForm from '@/components/AutoDataForm'
import { getUpdateObjURL } from '@/utils/common'
export default {
name: 'GenericCreateUpdateForm',
components: {
AutoDataForm
},
props: {
// 创建对象的地址
url: {
type: String,
default: ''
},
// 更新的对象
object: {
type: Object,
default: null
},
// form的默认值
initial: {
type: Object,
default: () => ({})
},
afterGetFormValue: {
type: Function,
default: (value) => value
},
// 提交前清理form的值
cleanFormValue: {
type: Function,
default: (value) => value
},
// 当提交的时候,怎么处理
onSubmit: {
type: Function,
default: null
},
// 如何提交数据
performSubmit: {
type: Function,
default(validValues) {
return this.$axios[this.method](this.iUrl, validValues)
}
},
// 创建成功的msg
createSuccessMsg: {
type: String,
default: function() {
return this.$t('common.createSuccessMsg')
}
},
// 保存成功继续添加的msg
saveSuccessContinueMsg: {
type: String,
default: function() {
return this.$t('common.saveSuccessContinueMsg')
}
},
// 更新成功的msg
updateSuccessMsg: {
type: String,
default: function() {
return this.$t('common.updateSuccessMsg')
}
},
// 创建成功的跳转路由
createSuccessNextRoute: {
type: Object,
default: function() {
@@ -64,6 +87,7 @@ export default {
return { name: routeName }
}
},
// 更新成功的跳转路由
updateSuccessNextRoute: {
type: Object,
default: function() {
@@ -71,13 +95,24 @@ export default {
return { name: routeName }
}
},
objectDetailRoute: {
type: Object,
default: function() {
const routeName = this.$route.name
.replace('Update', 'Detail')
.replace('Create', 'Detail')
return { name: routeName }
}
},
// 获取下一个路由
getNextRoute: {
type: Function,
default(res, method) {
return method === 'post' ? this.createSuccessNextRoute : this.updateSuccessNextRoute
}
},
getMethod: {
// 获取提交的方法
submitMethod: {
type: Function,
default: function() {
const params = this.$route.params
@@ -88,25 +123,68 @@ export default {
}
}
},
// 获取创建和更新的url function
getUrl: {
type: Function,
default: function() {
const params = this.$route.params
let url = this.url
if (params.id) {
url = `${url}${params.id}/`
url = getUpdateObjURL(url, params.id)
}
return url
}
},
emitPerformSuccessMsg: {
type: Function,
default(method, res, addContinue) {
let msg = method === 'post' ? this.createSuccessMsg : this.updateSuccessMsg
if (addContinue) {
msg = this.saveSuccessContinueMsg
}
let msgLinkName = ''
if (res.name) {
msgLinkName = res.name
} else if (res.hostname) {
msgLinkName = res.hostname
}
const h = this.$createElement
if (this.hasDetailInMsg) {
this.$message({
message: h('p', null, [
h('el-link', {
on: {
click: () => this.$router.push(this.objectDetailRoute)
},
style: { 'vertical-align': 'top' }
}, msgLinkName),
h('span', { style: {
'padding-left': '5px',
'height': '18px',
'line-height': '18px',
'font-size': '13.5px',
'font-weight': ' 400' }}, msg)
]),
type: 'success'
})
} else {
this.$message.success(msg)
}
}
},
onPerformSuccess: {
type: Function,
default(res, method, vm) {
const msg = method === 'post' ? this.createSuccessMsg : this.updateSuccessMsg
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.$message.success(msg)
setTimeout(() => this.$router.push(route), 100)
this.emitPerformSuccessMsg(method, res, addContinue)
if (!addContinue) {
setTimeout(() => this.$router.push(route), 100)
}
}
},
onPerformError: {
@@ -125,59 +203,104 @@ export default {
}
}
}
},
hasSaveContinue: {
type: Boolean,
default: null
},
hasDetailInMsg: {
type: Boolean,
default: true
}
},
data() {
return {
form: {},
loading: true,
isSubmitting: false
isSubmitting: false,
clone: false
}
},
computed: {
method() {
return this.getMethod(this)
return this.submitMethod(this)
},
iUrl() {
// 更新或创建的url
return this.getUrl()
},
iHasSaveContinue() {
if (this.hasSaveContinue != null) {
return this.hasSaveContinue
}
return this.method === 'post'
},
iHasReset() {
if (this.hasReset != null) {
return this.hasReset
}
return this.isUpdateMethod()
}
},
async created() {
this.$log.debug('Object init is: ', this.object)
this.loading = true
try {
const values = await this.getFormValue()
this.form = Object.assign(this.form, values)
this.$log.debug('Final object is: ', values)
const formValue = Object.assign(this.form, values)
this.form = this.afterGetFormValue(formValue)
} finally {
this.loading = false
}
},
methods: {
handleSubmit(values) {
isUpdateMethod() {
return ['put', 'patch'].indexOf(this.method.toLowerCase()) > -1
},
handleSubmit(values, formName, addContinue) {
let handler = this.onSubmit || this.defaultOnSubmit
handler = handler.bind(this)
values = this.cleanFormValue(values)
return handler(values)
return handler(values, formName, addContinue)
},
defaultOnSubmit(validValues) {
defaultOnSubmit(validValues, formName, addContinue) {
this.isSubmitting = true
this.performSubmit(validValues)
.then((res) => this.onPerformSuccess.bind(this)(res, this.method, this))
.then((res) => this.onPerformSuccess.bind(this)(res, this.method, this, addContinue))
.catch((error) => this.onPerformError(error, this.method, this))
.finally(() => { this.isSubmitting = false })
},
async getFormValue() {
if (this.method !== 'put') {
const cloneFrom = this.$route.query['clone_from']
if (!this.isUpdateMethod() && !cloneFrom) {
return Object.assign(this.form, this.initial)
}
let object = this.object
if (object === null) {
object = await this.getObjectDetail()
if (!object || Object.keys(object).length === 0) {
if (cloneFrom) {
this.$log.debug('Clone from: ', cloneFrom)
const url = `${this.url}${cloneFrom}/`
object = await this.getObjectDetail(url)
if (object['name']) {
object.name = this.$t('common.cloneFrom') + ' ' + object.name
} else if (object['hostname']) {
object.hostname = this.$t('common.cloneFrom') + ' ' + object.hostname
}
} else {
object = await this.getObjectDetail(this.iUrl)
}
}
if (object) {
object = _.cloneDeep(object)
this.$emit('update:object', object)
this.$emit('getObjectDone', object)
}
return object
},
async getObjectDetail() {
return this.$axios.get(this.iUrl)
async getObjectDetail(url) {
this.$log.debug('Get object detail: ', url)
return this.$axios.get(url)
}
}
}

View File

@@ -1,7 +1,7 @@
<template>
<Page>
<Page v-bind="$attrs">
<IBox>
<GenericCreateUpdateForm v-bind="$attrs" v-on="$listeners" />
<GenericCreateUpdateForm ref="createUpdateForm" v-bind="$attrs" v-on="$listeners" />
</IBox>
</Page>
</template>

View File

@@ -19,6 +19,7 @@ import TabPage from '../TabPage'
import { flashErrorMsg } from '@/utils/request'
import { getApiPath } from '@/utils/common'
import ActionsGroup from '@/components/ActionsGroup'
import { mapGetters } from 'vuex'
export default {
name: 'GenericDetailPage',
@@ -80,22 +81,27 @@ export default {
}
},
data() {
const vm = this
const defaultActions = {
canDelete: true,
deleteCallback: function(item) { this.defaultDelete(item) },
deleteApiUrl: getApiPath(this),
deleteSuccessRoute: this.$route.name.replace('Detail', 'List'),
canUpdate: true,
canUpdate: () => {
return !vm.currentOrgIsRoot
},
updateCallback: function(item) { this.defaultUpdate(item) },
updateRoute: this.$route.name.replace('Detail', 'Update'),
detailApiUrl: getApiPath(this)
}
return {
defaultActions: defaultActions,
loading: true,
validActions: Object.assign(defaultActions, this.actions)
}
},
computed: {
...mapGetters(['currentOrgIsRoot']),
pageActions() {
return [
{
@@ -158,7 +164,7 @@ export default {
this.$message.success(this.$t('common.deleteSuccessMsg'))
this.$router.push({ name: this.validActions.deleteSuccessRoute })
} catch (error) {
this.$message.error(this.$t('common.deleteErrorMsg' + ' ' + error))
this.$message.error(this.$t('common.deleteErrorMsg') + ' ' + error)
} finally {
instance.confirmButtonLoading = false
}
@@ -169,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

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