Compare commits

...

278 Commits

Author SHA1 Message Date
zhaojisen
cf094cbb88 perf:Optimize the execution efficiency of all and reverse selection 2024-12-05 12:47:05 +08:00
feng
66532f4d4b fix: ticket duplicate submission 2024-11-26 16:27:27 +08:00
wangruidong
42f27eb30f perf: users use Select2 2024-10-17 10:34:54 +08:00
wangruidong
57920bf771 fix: user lack permission to view the type tree 2024-10-10 17:11:40 +08:00
ZhaoJiSen
290772f44e Merge pull request #4384 from jumpserver/pr@v3@fix_special_chara
fixed: Fixed + and - not being special characters
2024-10-09 18:37:03 +08:00
zhaojisen
f140f2f59e fixed: Fixed + and - not being special characters 2024-10-09 18:35:40 +08:00
zhaojisen
7b1883e012 fixed: Fixed + and - not being special characters 2024-10-09 18:28:00 +08:00
wisonic
352ac7e828 fix: assets proportion tooltip cause page bounced at first time 2024-09-27 15:16:26 +08:00
ZhaoJiSen
1cbd58664c Merge pull request #4375 from jumpserver/pr@v3@perf_import_style
perf: Import UI style optimizations
2024-09-26 17:35:26 +08:00
zhaojisen
e48da6be9b perf: Import UI style optimizations 2024-09-26 17:30:14 +08:00
wangruidong
fa31b36550 fix: xterm output truncate 2024-09-26 17:16:22 +08:00
ibuler
6b93a6563d perf: revert pre org if logout 2024-08-30 17:02:49 +08:00
feng
d561701049 perf: Ticket flow rule user display name(username) 2024-08-19 16:06:55 +08:00
Ewall555
edbf477c1e perf: Translate ticket cancel button 2024-08-15 15:49:49 +08:00
ZhaoJiSen
6a2578b339 Merge pull request #4292 from jumpserver/pr@v3@fix_loading
perf: add button loading status
2024-08-08 15:01:35 +08:00
zhaojisen
2cb7569cb0 perf: add button loading status 2024-08-08 15:00:11 +08:00
fit2bot
9e0c623b9a perf: add button loading status (#4287)
* perf: add button loading status

* perf: add button loading status

---------

Co-authored-by: zhaojisen <1301338853@qq.com>
2024-08-08 11:38:04 +08:00
feng
40bf040501 perf: Asset authorization: The number of accounts displayed is incorrect ignore @SPEC 2024-08-07 17:51:21 +08:00
halo
efee7c7bbf perf: Email service authentication username is optional 2024-07-26 14:20:20 +08:00
ZhaoJiSen
5daecb84ae Merge pull request #4251 from jumpserver/pr@v3@fix_data_refresh
fixed: Fixed an issue where the validation date does not refresh
2024-07-23 10:09:16 +08:00
feng
3be325214d perf: You can modify sudo permissions multiple times 2024-07-22 18:18:11 +08:00
zhaojisen
581509f42f fixed: Fixed an issue where the validation date does not refresh 2024-07-22 14:52:45 +08:00
zhaojisen
654b36b064 fixed: Fixed the issue that parameter push parameters could not be saved 2024-07-17 10:49:02 +08:00
feng626
dcec73ae67 Merge pull request #4224 from jumpserver/pr@v3@download
perf: Downloading files does not trigger the beforeunload event
2024-07-16 12:42:48 +08:00
feng
00bafa8164 perf: Downloading files does not trigger the beforeunload event 2024-07-16 12:40:31 +08:00
zhaojisen
da09af79a7 fixed:Fixed an issue where the user was unable to enter non-MD content 2024-07-15 19:08:46 +08:00
zhaojisen
b596815ea5 fixed:Fixed an issue where the user was unable to enter non-MD content 2024-07-15 19:08:46 +08:00
ibuler
cb37273e80 perf: table search two times, one init one search 2024-07-12 10:20:55 +08:00
zhaojisen
c5bf7d0ad2 fixed: Fixed an issue where push parameters could not be saved when adding an asset account 2024-07-11 15:18:11 +08:00
wangruidong
c31195a67a perf: profile improvement mfa disabled 2024-07-10 14:21:27 +08:00
wangruidong
1eb59b379a perf: profile improvement mfa cancel hide 2024-07-09 16:14:43 +08:00
ZhaoJiSen
b7cee17156 Merge pull request #4188 from jumpserver/pr@v3@fix_unuse_item
fixed: Remove unused fields
2024-07-08 19:01:07 +08:00
zhaojisen
f1c8874010 fixed: Remove unused fields 2024-07-08 18:57:37 +08:00
Gerry.tan
5ccaa3b77d perf: 优化用户校验页面对密码进行加密传输 2024-06-24 10:38:00 +08:00
w940853815
27d3637330 Revert "fix: 待我审批列表页面默认过滤出还未审批的工单"
This reverts commit 91c44d0500.
2024-06-19 19:33:40 +08:00
wangruidong
9c8ceb04f0 perf: Disable editing labels for the root organization 2024-06-19 15:46:46 +08:00
wangruidong
ccd7b319c8 fix: 审计台仪表盘今日数据有问题 2024-06-18 17:59:18 +08:00
feng626
55637c7fa1 Merge pull request #4063 from jumpserver/pr@dev@account_template
fix: Account tempalte update secret failed
2024-06-18 14:54:14 +08:00
feng
4e95c88318 fix: Account tempalte update secret failed 2024-06-18 14:52:50 +08:00
wangruidong
1ff49ca16d perf: 系统设置-组织管理:最新创建成功后返回列表未按创建时间排序 2024-06-17 17:30:34 +08:00
wangruidong
91c44d0500 fix: 待我审批列表页面默认过滤出还未审批的工单 2024-06-13 16:15:35 +08:00
wangruidong
0b3a9844f7 fix: Crontab - Failed to set minute range 2024-06-13 10:40:22 +08:00
feng
95b58f3c96 perf: Complete asset hardware information 2024-06-11 18:49:03 +08:00
wangruidong
128b9c79ba fix: 工单-待我审批默认筛选打开的工单 2024-06-11 17:46:09 +08:00
halo
4eda83f83d perf: 优化图标不对齐的问题 2024-06-07 15:55:20 +08:00
halo
4cd0071054 feat: 支持批量测试资产可连接性 2024-06-07 11:18:35 +08:00
wangruidong
67a2a9be6a If non-existent values are entered into the select component, won't trigger a search request. 2024-06-05 16:01:46 +08:00
ibuler
f927a2a3cc perf: action 添加 token 2024-06-04 10:32:48 +08:00
Gerry.tan
ca40cb34da feat: 新增 dameng 图标 2024-05-31 11:11:06 +08:00
feng
d725e5497d perf: console dashboard api 2024-05-21 16:26:34 +08:00
ibuler
56f6c17275 perf: 优先选择上个 org 切换 2024-05-16 13:53:47 +08:00
ibuler
e1bde89b29 fix: 修复切换到全局组织回不来的问题
perf: 修改组织切换
2024-05-15 19:14:58 +08:00
feng626
e9da168c9f Merge pull request #3930 from jumpserver/pr@dev@mfa
perf: User personal settings mfa new window opens
2024-05-15 16:41:15 +08:00
feng
c19ef24ec9 perf: User personal settings mfa new window opens 2024-05-15 16:40:17 +08:00
wangruidong
fb7c4a8b2a fix: 竖屏审批工单时,动作显示不出来 2024-05-15 15:33:03 +08:00
wangruidong
428ba49f9c perf: 根据type生成导出文件名 2024-05-11 10:02:53 +08:00
ibuler
7602d6e270 fix: 修复自动切换到 root org 回不来的问题 2024-05-10 19:07:46 +08:00
wangruidong
7b62ce2d33 fix: 批量传输下载结果文件名undefined 2024-05-07 14:42:06 +08:00
jiangweidong
6ed40c45b0 perf: 云同步支持同步完成后自动删除云端已经释放的资产 2024-05-07 14:40:29 +08:00
wangruidong
31238e0398 fix: 命令存储创建es后跳转路由不对 2024-04-28 10:24:44 +08:00
wangruidong
676ac2bbf6 perf: 创建、更新用户时MFA选项根据系统设置选项进行动态渲染 2024-04-26 11:35:12 +08:00
Bai
d5415b84c9 feat: Support asset tree node drag to another one 2024-04-24 18:06:12 +08:00
Bai
5e91917ba4 perf: 优化 Web 资产详情时根据 autofill 类型返回对应的 spec_info 信息 2024-04-23 13:08:59 +08:00
zhaojisen
c4361b4c17 perf: 修复默认值相关内容,优化按钮禁用条件 2024-04-23 10:17:43 +08:00
Bai
70b5ec3683 fix: Fixed User first login long wait.(SYSTEM ORG) 2024-04-22 19:17:11 +08:00
Bai
c93a061852 fix: Linux Platform id is not 1, create gateway error 2024-04-22 15:54:56 +08:00
feng626
4351d20a1e Merge pull request #3867 from jumpserver/pr@dev@asset
perf: 新建/更新资产时,新tab页面打开
2024-04-22 14:17:29 +08:00
feng
ce23d53e3c perf: 新建/更新资产时,新tab页面打开 2024-04-22 14:16:59 +08:00
wangruidong
32fc16126f perf: dashboard typo 2024-04-22 13:24:52 +08:00
wangruidong
b9a99148e3 perf: 修改用户详情页-资产授权规则默认字段展示 2024-04-18 15:47:42 +08:00
ibuler
2a4b99484b perf: 修改查看 account 密码,切换 route 查询多次 2024-04-18 13:39:45 +08:00
ibuler
bd26894135 perf: 优化查看 account secret api 次数 2024-04-18 13:29:20 +08:00
wangruidong
01e55d7f6e perf: 全局组织禁用资产的测试可连接性 2024-04-18 11:40:10 +08:00
wangruidong
d0a7201683 perf: 优化查看资产账号列表过长显示不全 2024-04-18 11:25:52 +08:00
feng626
e2dcc98ab3 Merge pull request #3856 from jumpserver/pr@dev@dashboard
perf: dashboard 数字保留小数后两位
2024-04-17 20:05:45 +08:00
feng
8dd4f89395 perf: dashboard 数字保留小数后两位 2024-04-17 20:04:53 +08:00
feng
33a997f3bb fix: 仪表盘计算不准确 2024-04-17 19:16:59 +08:00
wangruidong
1adee78456 perf: QuickJob asset tree css 2024-04-17 14:45:12 +08:00
ibuler
baf2b6cd9b perf: view secret request many times 2024-04-17 14:25:36 +08:00
jiangweidong
28e756e163 perf: 优化云同步测试账号再次打开后不显示上次选的地域信息 2024-04-16 17:44:17 +08:00
老广
251873a7e9 Merge pull request #3850 from jumpserver/pr@dev@perf_krry_page
perf: 优化 krry paging
2024-04-16 17:33:20 +08:00
wangruidong
ff9bd42322 perf: 适配手机端 2024-04-16 15:40:30 +08:00
ibuler
2c245020cd perf: 优化 krry paging
chrome: remove debug
2024-04-16 14:34:20 +08:00
wangruidong
429d2fec40 perf: asset tree css 2024-04-16 11:38:40 +08:00
feng626
c2f8fe45a1 Merge pull request #3848 from jumpserver/pr@dev@records
fix: 改密推送任务记录查看失败
2024-04-16 10:36:08 +08:00
feng
caa5e7df75 fix: 改密推送任务记录查看失败 2024-04-16 10:34:05 +08:00
ibuler
8a439dec8d fix: 修复先删除标签resource,再创建报错 2024-04-15 19:43:51 +08:00
ibuler
eabf5a117d perf: account template support labels 2024-04-15 19:43:31 +08:00
wangruidong
c30672a014 fix: 会话详情中文件传输显示有误 2024-04-15 14:42:40 +08:00
wangruidong
5c5df32181 perf: 优化停止任务log输出 2024-04-12 11:29:41 +08:00
Bai
ce831df717 fix: I18n 2024-04-12 11:20:26 +08:00
feng626
04688930ad Merge pull request #3841 from jumpserver/pr@dev@change_secret
fix: 改密任务记录搜索失败
2024-04-12 11:03:01 +08:00
feng
ae5787ae52 fix: 改密任务记录搜索失败 2024-04-12 11:01:39 +08:00
wangruidong
1dfdfe3932 fix: xss处理后无class属性 2024-04-11 19:23:52 +08:00
wangruidong
f409abbf79 perf: 修改按钮样式,添加网关label 2024-04-11 17:33:55 +08:00
wangruidong
a6232da3d0 perf: 修改任务停止日志输出提示 2024-04-11 14:40:35 +08:00
feng626
2690538db6 Merge pull request #3837 from jumpserver/pr@dev@logo
fix: 点击logo前端页面卡住
2024-04-11 14:38:35 +08:00
feng
6d0af7a149 fix: 点击logo前端页面卡住 2024-04-11 14:32:05 +08:00
Bai
babc048eb0 fix: Session duration recurring 2024-04-11 11:01:36 +08:00
wangruidong
d49be903e8 perf: Domain detail add gateways 2024-04-10 16:46:47 +08:00
Bai
332058b0ea perf: Domain detail add asset 2024-04-10 16:46:47 +08:00
feng
e355abc1af perf: 切换zh hant 2024-04-10 15:31:39 +08:00
Bai
99200d58bb feat: LDAP User Auth support cache user_dn 2024-04-09 20:11:06 +08:00
ibuler
61bb97efa9 perf: xss attack 2024-04-09 17:09:01 +08:00
fit2bot
c5e030e2fe perf: Update user orgs roles (#3829)
* perf: Update user orgs roles

* perf: Update user orgs roles

---------

Co-authored-by: Bai <baijiangjie@gmail.com>
2024-04-09 16:51:05 +08:00
fit2bot
8a60ad774f perf: Update user orgs roles (#3828)
Co-authored-by: Bai <baijiangjie@gmail.com>
2024-04-09 15:44:07 +08:00
hzhfit2cloud
b08e6de527 支持中文繁体 2024-04-09 15:20:39 +08:00
feng
a96851686f fix: 修复xss漏洞 工单查不到数据问题 2024-04-08 18:56:22 +08:00
wangruidong
1b0adbfe71 fix: Web资产克隆,选择器信息丢失 2024-04-08 18:54:43 +08:00
wangruidong
c28419438b fix: 虚拟账号列表排序无效 2024-04-08 16:34:10 +08:00
jiangweidong
8807f24bcd perf: 云同步支持火山引擎 2024-04-08 14:04:56 +08:00
Bai
b8a914eb02 perf: ROOT Org show orgs-and-roles in user-detail page 2024-04-08 14:02:10 +08:00
wangruidong
8bbc66a281 fix: 授权资产查看账号失败 2024-04-08 11:35:13 +08:00
feng626
19bab778ca Merge pull request #3818 from jumpserver/pr@dev@user_session
fix: 修复用户下线失败问题
2024-04-03 16:41:43 +08:00
feng
134bcda895 fix: 修复用户下线失败问题 2024-04-03 16:14:54 +08:00
wangruidong
8b725fa4f6 perf: TransferSelect Dialog手机端适配 2024-04-02 19:21:33 +08:00
wangruidong
205f8bc280 fix: 勾选指定账号时,数量显示有问题 2024-04-02 19:20:44 +08:00
wangruidong
4963446b74 fix: 统一成模板 2024-04-02 19:19:40 +08:00
wangruidong
98be3903db fix: 华为交换机执行快捷命令报错 2024-04-02 18:51:19 +08:00
wangruidong
2cb7a859a8 perf: 授权的资产列表中支持查看某个资产授权的账号 2024-04-02 17:06:55 +08:00
wangruidong
d51571c530 perf: 搜索框和右侧在同一水平线上 2024-03-29 18:29:50 +08:00
wangruidong
bf0d19ac0b perf: 工单页面去掉一些筛选字段 2024-03-29 14:17:44 +08:00
wangruidong
28062f5d60 perf: Web,ChatGPT资产测试可连接性按钮禁用 2024-03-28 18:20:35 +08:00
ibuler
720dee578a fix: 修复创建账号时,资产没有默认值的问题 2024-03-28 18:15:50 +08:00
feng626
cc0d78a4e7 Merge pull request #3809 from jumpserver/pr@dev@role
fix: 修改content type 权限
2024-03-28 15:22:13 +08:00
feng
9f0b904043 fix: 修改content type 权限 2024-03-28 15:21:31 +08:00
dependabot[bot]
90cbb25d47 build(deps): bump express from 4.18.2 to 4.19.2
Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-27 16:18:44 +08:00
dependabot[bot]
450d9562c3 build(deps): bump ip from 1.1.8 to 1.1.9
Bumps [ip](https://github.com/indutny/node-ip) from 1.1.8 to 1.1.9.
- [Commits](https://github.com/indutny/node-ip/compare/v1.1.8...v1.1.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-27 16:18:19 +08:00
dependabot[bot]
a69eec5ef6 build(deps): bump axios from 0.21.1 to 0.28.0
Dependabot couldn't find the original pull request head commit, a01b1986d1e53870ecc18abd7ee72104fba7a08b.
2024-03-27 16:17:56 +08:00
wangruidong
5f6fc7e3b4 fix: 待审批列表显示没有过滤的问题 2024-03-27 16:10:22 +08:00
wangruidong
62b6ca026e perf: 优化资产、网域、网关的操作体验 2024-03-27 16:02:39 +08:00
Bai
006f258938 perf: Optimize the context menu for right-clicking on the asset tree. 2024-03-27 10:56:47 +08:00
feng626
756b7db6b6 Merge pull request #3798 from jumpserver/pr@dev@perm_asset
perf: 资产授权列表优化
2024-03-26 18:07:51 +08:00
feng
a8d7c01f94 perf: 资产授权列表优化 2024-03-26 18:06:37 +08:00
feng
0c7e7ecc99 feat: 拆分 feishu lark 2024-03-26 17:11:22 +08:00
feng626
342a70c441 Merge pull request #3795 from jumpserver/pr@dev@asset_perm
fix: 资产授权列表没有有效期字段
2024-03-22 16:01:12 +08:00
wangruidong
815247f5b5 perf: 封装SwitchFormatter组件 2024-03-22 15:28:13 +08:00
wangruidong
e6721a9905 feat: 支持开启、关闭定时任务执行 2024-03-22 15:28:13 +08:00
feng
d6305fddfd fix: 资产授权列表没有有效期字段 2024-03-22 14:47:15 +08:00
Eric
6db2d5ae31 perf: 支持发布机卸载远程应用 2024-03-21 16:12:13 +08:00
feng626
9b43bba6f8 Merge pull request #3790 from jumpserver/pr@dev@change_secret_record
feat: 改密记录可批量重试 新增更多过滤选项
2024-03-21 11:09:26 +08:00
feng
f9d89b30e2 feat: 改密记录可批量重试 新增更多过滤选项 2024-03-21 11:08:30 +08:00
feng626
be29794b4c Merge pull request #3788 from jumpserver/pr@dev@change_secret
perf: 改密记录可查看密文
2024-03-21 11:05:11 +08:00
jiangweidong
97bf4f1b97 feat: 云账号测试支持选择地域 2024-03-20 16:39:12 +08:00
Bai
76e7684e26 perf: 优化 Session 支持 duration 字段 2024-03-20 15:52:23 +08:00
wangruidong
2bfd764da7 perf: 支持导出收集账号列表的数据 2024-03-18 18:02:58 +08:00
wangruidong
daf3defe14 perf: 修改oracle默认数据库提示 2024-03-18 18:02:34 +08:00
Eric
d8c165ca78 perf: 支持发布机仅初始化配置 2024-03-18 15:52:22 +08:00
feng
42115b2c30 perf: 改密记录可查看密文 2024-03-15 17:29:23 +08:00
老广
447013abf5 Merge pull request #3784 from jumpserver/pr@dev@perf_asset_tree
perf: 优化资产树 root 节点宽度
2024-03-15 14:43:24 +08:00
ibuler
44216326f9 perf: 优化资产树 root 节点宽度 2024-03-15 06:38:32 +00:00
feng
fd64e71a3b perf: 优化user secret key 处理逻辑 2024-03-15 11:46:44 +08:00
feng626
7b990a264f Merge pull request #3782 from jumpserver/pr@dev@translate
perf: translate
2024-03-15 10:34:20 +08:00
feng
7687917aae perf: translate 2024-03-15 10:33:13 +08:00
wangruidong
02b71619de perf: 支持终断批量快捷命令执行的任务 2024-03-14 11:18:04 +08:00
Bai
da90b23f99 perf: 优化我的资产页面不能编辑 Labels 的问题 2024-03-14 11:08:58 +08:00
wangruidong
b8090abcdc perf: 添加oracle默认数据库提示 2024-03-14 11:08:31 +08:00
feng626
45b07dd628 Merge pull request #3777 from jumpserver/pr@dev@customize_footer
feat: 自定义footer
2024-03-12 14:34:44 +08:00
feng
ad7a6a5579 feat: 自定义footer 2024-03-12 14:22:28 +08:00
wangruidong
67c8521d5c fix: 自动任务跳转执行列表显示结果不准确的问题 2024-03-12 11:18:33 +08:00
wangruidong
18fc63b8c0 feat: 支持一键导出批量命令执行日志 2024-03-12 11:01:27 +08:00
w940853815
64d2854e49 Revert "fix: 刷新页面搜索条件丢失的问题"
This reverts commit 99f83a6bc4.
2024-03-11 18:20:24 +08:00
wangruidong
99f83a6bc4 fix: 刷新页面搜索条件丢失的问题 2024-03-07 14:22:20 +08:00
feng626
ca9f90624e Merge pull request #3772 from jumpserver/pr@dev@detail_formatter
perf: 支持点击“转到”后进行颜色标识
2024-03-06 19:11:00 +08:00
feng
05edf98514 perf: 支持点击“转到”后进行颜色标识 2024-03-06 19:10:04 +08:00
Bai
45b8c622bc perf: 优化界面设置页面主题Logo预览的背景颜色 2024-03-06 17:53:01 +08:00
feng626
faf1fb60b7 Merge pull request #3769 from jumpserver/pr@dev@account_gather
feat: 账号收集任务可单独添加资产
2024-03-05 15:00:27 +08:00
feng
f28825ba74 feat: 账号收集任务可单独添加资产 2024-03-05 14:59:02 +08:00
wangruidong
3626fd024d fix: 刷新页面根据搜索条件过滤出对应的资源 2024-03-04 19:13:18 +08:00
feng626
79ec0610a8 Merge pull request #3766 from jumpserver/revert-3764-pr@dev@fix_url_search
Revert "fix: 刷新页面根据搜索条件过滤出对应的资源"
2024-03-04 17:54:35 +08:00
Bryan
eee5889d51 Revert "fix: 刷新页面根据搜索条件过滤出对应的资源" 2024-03-04 17:53:46 +08:00
feng626
038ec49d5e Merge pull request #3764 from jumpserver/pr@dev@fix_url_search
fix: 刷新页面根据搜索条件过滤出对应的资源
2024-03-04 17:41:49 +08:00
wangruidong
669ffc73fd fix: 刷新页面根据搜索条件过滤出对应的资源 2024-03-04 14:24:39 +08:00
feng626
278562e855 Merge pull request #3763 from jumpserver/pr@dev@account_push
perf: 社区版账号推送,隐藏掉定时推送按钮
2024-03-01 16:57:37 +08:00
feng
fafa088a5e perf: 社区版账号推送,隐藏掉定时推送按钮 2024-03-01 16:51:06 +08:00
wangruidong
1ff65a2293 fix: sftp会话详情禁用监控按钮 2024-02-29 14:33:39 +08:00
Bai
1c69c61432 fix: 修复文件传输权限位 2024-02-29 14:13:42 +08:00
wangruidong
046870e366 fix: 隐藏停止任务按钮 2024-02-28 20:51:59 +08:00
feng626
b44af12f3b Merge pull request #3756 from jumpserver/pr@dev@push_params
fix: 平台中的改密参数和推送参数没了
2024-02-28 18:49:47 +08:00
feng
d9a9c7e229 fix: 平台中的改密参数和推送参数没了 2024-02-28 18:48:06 +08:00
wangruidong
55dfa5889b perf: 终断任务按钮状态变化优化 2024-02-28 10:00:13 +08:00
wangruidong
b35b5bd774 fix: 远程应用账号列表排版问题 2024-02-28 09:59:52 +08:00
feng626
1ee9e5df78 Merge pull request #3751 from jumpserver/pr@dev@push_account
fix: 【资产列表】资产详情中推送账号,推送参数未获取平台的参数
2024-02-27 16:56:50 +08:00
feng
2fe6cb37e6 fix: 【资产列表】资产详情中推送账号,推送参数未获取平台的参数 2024-02-27 16:55:48 +08:00
feng626
da570e21ee Merge pull request #3747 from jumpserver/pr@dev@luna
fix: 跳转到 luna 组织不对
2024-02-27 10:23:55 +08:00
wangruidong
16adcd299a perf: 平台参数联动逻辑优化 2024-02-26 19:49:26 +08:00
wangruidong
df8a464c36 fix: Web资产的选择器未根据平台参数联动 2024-02-26 16:40:15 +05:00
wangruidong
52e121cfdb perf: 禁用redis/clickhouse测试可连接性按钮 2024-02-26 15:53:20 +05:00
feng626
f014fc6426 Merge pull request #3748 from jumpserver/pr@dev@user_group
perf: Default组织下,标签关联用户资源,去掉组件用户
2024-02-26 17:21:00 +08:00
feng
88a6c2bb2b perf: Default组织下,标签关联用户资源,去掉组件用户 2024-02-26 17:08:05 +08:00
feng
8fa31fe0c2 fix: 跳转到 luna 组织不对 2024-02-26 16:24:50 +08:00
wangruidong
8f246c18e1 perf: 作业日志添加任务类型 2024-02-26 13:43:25 +08:00
wangruidong
9d999a7119 fix: LDAP用户导入会超时 2024-02-22 11:37:58 +08:00
wangruidong
dc8f237fec fix: 类别、类型排序无效 2024-02-21 14:21:38 +08:00
jiangweidong
41d0615ab5 feat: 支持工单链接直接免密审批 2024-02-21 11:39:57 +08:00
feng626
fadc3e7dd0 Merge pull request #3736 from jumpserver/pr@dev@account
perf: 账号收集添加资产名称模糊搜索
2024-02-20 18:41:58 +08:00
feng
152f56b496 perf: 账号收集添加资产名称模糊搜索 2024-02-20 18:39:48 +08:00
wangruidong
ed4f8dea90 perf: 终断批量快捷命令执行的任务 2024-02-20 15:13:11 +08:00
feng626
dced020a20 Merge pull request #3734 from jumpserver/pr@dev@perm_user
perf: 授权用户列表显示角色
2024-02-19 14:48:46 +08:00
feng
bf3c87575c perf: 授权用户列表显示角色 2024-02-19 14:43:01 +08:00
Eric
dadb54090c perf: 会话活动日志 2024-02-06 18:32:53 +08:00
ibuler
3f683b012c perf: 修改 ai chat 的位置 2024-02-06 17:59:13 +08:00
wangruidong
ecb1e91136 fix: Web资产选择器需跟平台的参数适配 2024-02-05 18:17:33 +08:00
wangruidong
454947f08b perf: 支持改密日志记录保留天数 2024-02-05 18:07:04 +08:00
feng
3ff6c6fe2f fix: 导出下载更新模版用不同的action 去对应后台serializer 2024-02-05 17:51:56 +08:00
Bai
527cc4d727 fix: 修复用户登录403的问题(DEFAULT组织由后端进行设置) 2024-02-05 16:52:31 +08:00
wangruidong
3b4201d2bf perf: 修改翻译 2024-02-05 15:54:42 +08:00
wangruidong
ba109da324 perf: 禁止用户自身更新自己的某些属性 2024-02-04 18:01:21 +08:00
jiangweidong
2a92c7657c perf: 支持账号批量更新功能 (#3717) 2024-02-04 17:50:22 +08:00
feng626
beb8ace5bd Merge pull request #3723 from jumpserver/pr@dev@asset_select
perf: 改密推送 选择资产支持标签搜索
2024-02-04 17:29:13 +08:00
feng
5e1225524c perf: 改密推送 选择资产支持标签搜索 2024-02-04 17:27:46 +08:00
wangruidong
931042eb2f perf: 国际电话区号选项从api返回 2024-02-04 14:52:50 +08:00
feng626
383577bb18 Merge pull request #3721 from jumpserver/pr@dev@ssh_key
perf: 【模版账号】创建/更新 模版账号表单中,希望增加上传密钥按钮
2024-02-02 18:44:59 +08:00
feng
f9e94386de perf: 【模版账号】创建/更新 模版账号表单中,希望增加上传密钥按钮 2024-02-02 18:41:11 +08:00
feng
5879eed926 perf: 优化用户session 会话过期 2024-02-02 17:54:46 +08:00
wangruidong
e1e54bf7a3 fix: 控制台,审计台仪表盘图表显示不对 2024-01-31 13:55:05 +08:00
ibuler
9e0c43589d perf: 优化首页日期 tab 按钮的颜色 2024-01-31 10:00:11 +08:00
wangruidong
ca3b0cfce5 perf: crontab修改提示翻译 2024-01-30 17:48:25 +08:00
jiangweidong
245b3f4ad2 perf: 资源详情页面标题长度超过一行则省略表示 2024-01-29 16:52:12 +08:00
ibuler
ea575e0515 perf: 优化数量显示,异步获取 hover 的内容 2024-01-29 16:50:42 +08:00
“huailei000”
ff63d2ca39 perf: 优化快捷命令输出框跟随浏览器高度 2024-01-29 16:49:38 +08:00
feng626
045af27999 Merge pull request #3711 from jumpserver/pr@dev@account_template
feat: 账号模版可导入导出
2024-01-29 16:42:45 +08:00
feng626
257365932c Merge pull request #3710 from jumpserver/pr@dev@account_bulk_test
feat: 批量测试账号可连接性
2024-01-29 16:42:18 +08:00
feng
aca4e4077f feat: 账号模版可导入导出 2024-01-29 16:19:19 +08:00
feng
ce80d36b8b feat: 批量测试账号可连接性 2024-01-29 14:40:49 +08:00
wangruidong
7dd5256303 perf: 用户详情 - 授权资产减少默认字段 2024-01-25 17:19:09 +08:00
wangruidong
fcf1093b4c perf: 安全模式返回授权的资产 2024-01-25 17:08:15 +08:00
feng626
5f9e9afffb Merge pull request #3709 from jumpserver/pr@dev@translate
perf: 翻译
2024-01-25 14:36:18 +08:00
feng
1bba9980c2 perf: 翻译 2024-01-25 14:35:13 +08:00
halo
6cbcee7656 perf: 指定账号enter异常刷新整个页面 2024-01-22 06:38:19 +00:00
halo
2084c50f95 perf: 资产授权弹窗选择时点击遮罩退出问题 2024-01-22 06:37:53 +00:00
“huailei000”
20d98bf09e perf: 优化更多操作高度;优化快捷命令右侧图标;优化账号管理列表-定期执行在中、英文状态下的宽度 2024-01-19 10:32:38 +00:00
“huailei000”
05c2f1f859 perf: 快捷命令toolbar增加折叠功能 2024-01-19 10:31:36 +00:00
“huailei000”
1e9107ec4a perf: 兼容luna显示智能问答 2024-01-19 09:23:19 +00:00
wangruidong
96a3f0a334 fix: 网关列表资产数量点击详情中没有资产 2024-01-18 08:11:46 +00:00
“huailei000”
6938299940 perf: 会话详情添加文件传输;操作日志默认显示动作、资源类型 2024-01-18 08:10:58 +00:00
wangruidong
0d1eb82fca perf: The cron interval execution must be greater than 10 minutes 2024-01-18 08:06:14 +00:00
feng626
ddf268e8ec Merge pull request #3695 from jumpserver/revert-3692-pr@dev@login_expire
Revert "perf: 登录过期自动退出"
2024-01-17 21:11:10 +08:00
老广
ae6fb878da Revert "perf: 登录过期自动退出" 2024-01-17 21:07:33 +08:00
feng626
d7099c118b Merge pull request #3692 from jumpserver/pr@dev@login_expire
perf: 登录过期自动退出
2024-01-17 15:58:16 +08:00
feng
1b73591366 perf: 登录过期自动退出 2024-01-17 15:47:21 +08:00
ibuler
6f3f66df73 perf: auto decide create in new page or current page in asset or permission 2024-01-15 19:37:47 +08:00
ibuler
598020a89b perf: revert asset create with labels 2024-01-15 19:36:48 +08:00
“huailei000”
88486e2b00 perf: 优化时间区间选择组件选择时间不准确问题 2024-01-14 22:57:36 -08:00
“huailei000”
faf8521c91 perf: 优化标签列表-资产数量弹窗中的搜索条件不显示在url中 2024-01-14 22:29:42 -08:00
feng
ccd74fb76f fix: m2m 表单渲染错乱 2024-01-14 22:14:48 -08:00
ibuler
d76c6fdbd8 perf: 优化 tag search 避免多次请求 2024-01-14 18:29:12 -08:00
wangruidong
af6a55d3f4 feat: 同步ldap用户消息通知 2024-01-12 12:02:42 +05:00
“huailei000”
c052961efe perf: 优化仪表盘翻译 2024-01-11 14:43:24 +08:00
“huailei000”
57d339f513 perf: 优化批量操作图标大小 2024-01-11 14:43:01 +08:00
feng626
097771175d Merge pull request #3678 from jumpserver/pr@dev@dashboard_translate
perf: 前端dashboard 翻译
2024-01-09 17:49:43 +08:00
feng
0922557abc perf: 前端dashboard 翻译 2024-01-09 17:40:37 +08:00
feng626
6842da1960 Merge pull request #3676 from jumpserver/pr@dev@perm_create
perf: 【资产授权】选择资产弹窗左侧树中,去掉搜索、刷新按钮
2024-01-08 17:26:27 +08:00
feng
eb839b4113 perf: 【资产授权】选择资产弹窗左侧树中,去掉搜索、刷新按钮 2024-01-08 17:25:34 +08:00
“huailei000”
7e3e8fbf2f perf: 替换批量更新图标 2024-01-08 16:04:37 +08:00
jiangweidong
3800151763 perf: 邮箱支持exchange协议 2024-01-08 12:34:50 +05:00
ibuler
0121505f28 perf: 优化页面显示 2024-01-08 14:49:12 +08:00
ibuler
b9a6f5d3ac perf: 修复标签导入和搜索的问题 2024-01-03 17:07:15 +08:00
feng626
179b568b16 Merge pull request #3672 from jumpserver/pr@dev@history_account
feat: 历史账号定期删除 可设置保留数量
2024-01-03 11:04:22 +08:00
feng
c563697efd feat: 历史账号定期删除 可设置保留数量 2024-01-02 19:10:54 +08:00
fit2bot
fa9281aa92 perf: 优化节点树 (#3670)
Co-authored-by: ibuler <ibuler@qq.com>
2024-01-02 16:18:54 +08:00
feng
cc8d94f666 fix: Default组织下,标签关联用户资源,去掉组件用户 2023-12-29 11:14:26 +05:00
“huailei000”
1416405644 perf: 优化批量更新资产不能使用问题 2023-12-29 11:10:04 +05:00
wangruidong
d29b3effbc fix: 更新用户组权限问题 2023-12-29 07:41:28 +05:00
wangruidong
bd9456ba2d fix: 创建用户失败 2023-12-28 15:35:22 +05:00
wangruidong
0c9d5d9b6b fix: 用户角色修改后更新页面与用户列表页显示不一致 2023-12-28 15:35:22 +05:00
Bai
b19ddd6799 fix: 修复组件中没有暴露 SQLServer 端口的问题 2023-12-28 14:52:41 +05:00
wangruidong
ea48b6ebf3 perf: 修改文件上传超时时间 2023-12-27 18:57:17 +08:00
“huailei000”
fba2f77874 perf: 优化智能问答返回显示系统消息 2023-12-27 17:11:20 +08:00
wangruidong
3c900ce387 perf: 账号删除添加提示确认框 2023-12-27 14:07:15 +05:00
“huailei000”
7df11b907f perf: 优化资产详情页面布局 2023-12-27 15:27:43 +08:00
wangruidong
cdd51a9c16 perf: 统计任务执行结果 2023-12-26 15:45:08 +08:00
wangruidong
51a4c013d3 perf: 修改文件上传超时时间 2023-12-26 10:48:36 +08:00
feng
111aafb4bb fix: 【用户登录会话失效问题】SESSION_COOKIE_AGE 配置不生效的问题 2023-12-25 13:24:02 +05:00
“huailei000”
81f0b13730 perf: 优化网关克隆平台显示不正确问题 2023-12-25 15:10:10 +08:00
“huailei000”
a36a9e7645 perf: 智能问答禁止密码自动填充 2023-12-22 13:23:17 +05:00
Eric
cfe6db6ec5 perf: 虚拟应用增加许可证验证 2023-12-22 12:24:08 +05:00
“huailei000”
ccb221559a perf: 账号收集翻译 2023-12-22 11:33:43 +08:00
205 changed files with 5991 additions and 1148 deletions

View File

@@ -10,3 +10,4 @@ jobs:
- uses: jumpserver/action-generic-handler@master
env:
GITHUB_TOKEN: ${{ secrets.PRIVATE_TOKEN }}
I18N_TOKEN: ${{ secrets.I18N_TOKEN }}

View File

@@ -20,20 +20,22 @@
"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",
"diff-i18n": "python ./src/i18n/langs/i18n-util.py diff en ja",
"apply-i18n": "python ./src/i18n/langs/i18n-util.py apply en ja"
"diff-i18n": "python ./src/i18n/langs/i18n-util.py diff en ja zh_Hant",
"apply-i18n": "python ./src/i18n/langs/i18n-util.py apply en ja zh_Hant"
},
"dependencies": {
"@babel/plugin-proposal-optional-chaining": "^7.13.12",
"@traptitech/markdown-it-katex": "^3.6.0",
"@ztree/ztree_v3": "3.5.44",
"axios": "0.21.1",
"axios": "0.28.0",
"axios-retry": "^3.1.9",
"cron-parser": "^4.0.0",
"crypto-js": "^4.1.1",
"css-color-function": "^1.3.3",
"decimal.js": "^10.4.3",
"deepmerge": "^4.2.2",
"echarts": "^4.7.0",
"dompurify": "^3.1.6",
"echarts": "4.7.0",
"element-ui": "2.13.2",
"eslint-plugin-html": "^6.0.0",
"highlight.js": "^11.9.0",

View File

@@ -27,6 +27,9 @@
if(pathname.indexOf('/ui') === -1) {
window.location.href = window.location.origin + '/ui/#' + pathname
}
if (pathname.startsWith('/ui/#/chat')) {
window.location.href = window.location.origin + pathname
}
}
</script>
<div id="app"></div>

View File

@@ -53,12 +53,21 @@ export function createJob(form) {
})
}
export function StopJob(form) {
return request({
url: '/api/v1/ops/job-executions/stop/',
method: 'post',
data: form
})
}
export function JobUploadFile(form) {
return request({
url: '/api/v1/ops/jobs/upload/',
method: 'post',
headers: { 'Content-Type': 'multipart/form-data' },
timeout: 10 * 60 * 1000,
timeout: 60 * 60 * 1000,
data: form
})
}

View File

@@ -12,7 +12,6 @@ export function getProfile(token) {
return request({
url: '/api/v1/users/profile/',
method: 'get'
// params: { token }
})
}

View File

@@ -0,0 +1,191 @@
import { UpdateToken, UploadSecret } from '@/components/Form/FormFields'
import Select2 from '@/components/Form/FormFields/Select2.vue'
import AssetSelect from '@/components/Apps/AssetSelect/index.vue'
import { Required, RequiredChange } from '@/components/Form/DataForm/rules'
import AutomationParamsForm from '@/views/assets/Platform/AutomationParamsSetting.vue'
export const accountFieldsMeta = (vm) => {
const defaultPrivilegedAccounts = ['root', 'administrator']
return {
assets: {
rules: [Required],
component: AssetSelect,
label: vm.$t('assets.Asset'),
el: {
multiple: false
},
hidden: () => {
return vm.platform || vm.asset
}
},
template: {
component: Select2,
rules: [Required],
el: {
multiple: false,
ajax: {
url: '/api/v1/accounts/account-templates/',
transformOption: (item) => {
return { label: item.name, value: item.id }
}
}
},
hidden: () => {
return vm.platform || vm.asset || !vm.addTemplate
}
},
on_invalid: {
rules: [Required],
label: vm.$t('accounts.AccountPolicy'),
helpText: vm.$t('accounts.BulkCreateStrategy'),
hidden: () => {
return vm.platform || vm.asset
}
},
name: {
label: vm.$t('common.Name'),
rules: [RequiredChange],
on: {
input: ([value], updateForm) => {
if (!vm.usernameChanged) {
if (!vm.account?.name) {
updateForm({ username: value })
}
const maybePrivileged = defaultPrivilegedAccounts.includes(value)
if (maybePrivileged) {
updateForm({ privileged: true })
}
}
}
},
hidden: () => {
return vm.addTemplate
}
},
username: {
el: {
disabled: !!vm.account?.name
},
on: {
input: ([value], updateForm) => {
vm.usernameChanged = true
},
change: ([value], updateForm) => {
const maybePrivileged = defaultPrivilegedAccounts.includes(value)
if (maybePrivileged) {
updateForm({ privileged: true })
}
}
},
hidden: () => {
return vm.addTemplate
}
},
privileged: {
label: vm.$t('assets.Privileged'),
hidden: () => {
return vm.addTemplate
}
},
su_from: {
component: Select2,
hidden: (formValue) => {
return !vm.asset?.id || !vm.iPlatform.su_enabled
},
el: {
multiple: false,
clearable: true,
ajax: {
url: `/api/v1/accounts/accounts/su-from-accounts/?account=${vm.account?.id || ''}&asset=${vm.asset?.id || ''}`,
transformOption: (item) => {
return { label: `${item.name}(${item.username})`, value: item.id }
}
}
}
},
su_from_username: {
label: vm.$t('assets.UserSwitchFrom'),
hidden: (formValue) => {
return vm.platform || vm.asset || vm.addTemplate
}
},
password: {
label: vm.$t('assets.Password'),
component: UpdateToken,
hidden: (formValue) => {
return formValue.secret_type !== 'password' || vm.addTemplate
}
},
ssh_key: {
label: vm.$t('assets.PrivateKey'),
component: UploadSecret,
hidden: (formValue) => formValue.secret_type !== 'ssh_key' || vm.addTemplate
},
passphrase: {
label: vm.$t('assets.Passphrase'),
component: UpdateToken,
hidden: (formValue) => formValue.secret_type !== 'ssh_key' || vm.addTemplate
},
token: {
label: vm.$t('assets.Token'),
component: UploadSecret,
hidden: (formValue) => formValue.secret_type !== 'token' || vm.addTemplate
},
access_key: {
id: 'access_key',
label: vm.$t('assets.AccessKey'),
component: UploadSecret,
hidden: (formValue) => formValue.secret_type !== 'access_key' || vm.addTemplate
},
api_key: {
id: 'api_key',
label: vm.$t('assets.ApiKey'),
component: UploadSecret,
hidden: (formValue) => formValue.secret_type !== 'api_key' || vm.addTemplate
},
secret_type: {
type: 'radio-group',
options: [],
hidden: () => {
return vm.addTemplate
}
},
push_now: {
helpText: vm.$t('accounts.AccountPush.WindowsPushHelpText'),
hidden: (formValue) => {
const automation = vm.iPlatform.automation || {}
return !automation.push_account_enabled ||
!automation.ansible_enabled ||
!vm.$hasPerm('accounts.push_account') ||
(formValue.secret_type === 'ssh_key' && vm.iPlatform.type.value === 'windows') ||
vm.addTemplate
}
},
params: {
label: vm.$t('assets.PushParams'),
component: AutomationParamsForm,
el: {},
hidden: (formValue) => {
const automation = vm.iPlatform.automation || {}
vm.fieldsMeta.params.el.method = vm.iPlatform.automation.push_account_method
vm.fieldsMeta.params.el.pushAccountParams = vm.iPlatform.automation.push_account_params
return !formValue.push_now ||
!automation.push_account_enabled ||
!automation.ansible_enabled ||
(formValue.secret_type === 'ssh_key' &&
vm.iPlatform.type.value === 'windows') ||
!vm.$hasPerm('accounts.push_account') ||
vm.addTemplate
}
},
is_active: {
label: vm.$t('common.IsActive')
},
comment: {
label: vm.$t('common.Comment'),
hidden: () => {
return vm.addTemplate
}
}
}
}

View File

@@ -9,12 +9,8 @@
<script>
import AutoDataForm from '@/components/Form/AutoDataForm/index.vue'
import { UpdateToken, UploadSecret } from '@/components/Form/FormFields'
import Select2 from '@/components/Form/FormFields/Select2.vue'
import AssetSelect from '@/components/Apps/AssetSelect/index.vue'
import { encryptPassword } from '@/utils/crypto'
import { Required, RequiredChange } from '@/components/Form/DataForm/rules'
import AutomationParamsForm from '@/views/assets/Platform/AutomationParamsSetting.vue'
import { accountFieldsMeta } from '@/components/Apps/AccountCreateUpdateForm/const'
export default {
name: 'AccountCreateForm',
@@ -48,7 +44,6 @@ export default {
return {
loading: true,
usernameChanged: false,
defaultPrivilegedAccounts: ['root', 'administrator'],
iPlatform: {
automation: {},
su_enabled: false,
@@ -72,179 +67,7 @@ export default {
]],
[this.$t('common.Other'), ['push_now', 'params', 'on_invalid', 'is_active', 'comment']]
],
fieldsMeta: {
assets: {
rules: [Required],
component: AssetSelect,
label: this.$t('assets.Asset'),
el: {
multiple: false
},
hidden: () => {
return this.platform || this.asset
}
},
template: {
component: Select2,
rules: [Required],
el: {
multiple: false,
ajax: {
url: '/api/v1/accounts/account-templates/',
transformOption: (item) => {
return { label: item.name, value: item.id }
}
}
},
hidden: () => {
return this.platform || this.asset || !this.addTemplate
}
},
on_invalid: {
rules: [Required],
label: this.$t('accounts.AccountPolicy'),
helpText: this.$t('accounts.BulkCreateStrategy'),
hidden: () => {
return this.platform || this.asset
}
},
name: {
rules: [RequiredChange],
on: {
input: ([value], updateForm) => {
if (!this.usernameChanged) {
if (!this.account?.name) {
updateForm({ username: value })
}
const maybePrivileged = this.defaultPrivilegedAccounts.includes(value)
if (maybePrivileged) {
updateForm({ privileged: true })
}
}
}
},
hidden: () => {
return this.addTemplate
}
},
username: {
el: {
disabled: !!this.account?.name
},
on: {
input: ([value], updateForm) => {
this.usernameChanged = true
},
change: ([value], updateForm) => {
const maybePrivileged = this.defaultPrivilegedAccounts.includes(value)
if (maybePrivileged) {
updateForm({ privileged: true })
}
}
},
hidden: () => {
return this.addTemplate
}
},
privileged: {
hidden: () => {
return this.addTemplate
}
},
su_from: {
component: Select2,
hidden: (formValue) => {
return !this.asset?.id || !this.iPlatform.su_enabled
},
el: {
multiple: false,
clearable: true,
ajax: {
url: `/api/v1/accounts/accounts/su-from-accounts/?account=${this.account?.id || ''}&asset=${this.asset?.id || ''}`,
transformOption: (item) => {
return { label: `${item.name}(${item.username})`, value: item.id }
}
}
}
},
su_from_username: {
label: this.$t('assets.UserSwitchFrom'),
hidden: (formValue) => {
return this.platform || this.asset || this.addTemplate
}
},
password: {
label: this.$t('assets.Password'),
component: UpdateToken,
hidden: (formValue) => formValue.secret_type !== 'password' || this.addTemplate
},
ssh_key: {
label: this.$t('assets.PrivateKey'),
component: UploadSecret,
hidden: (formValue) => formValue.secret_type !== 'ssh_key' || this.addTemplate
},
passphrase: {
label: this.$t('assets.Passphrase'),
component: UpdateToken,
hidden: (formValue) => formValue.secret_type !== 'ssh_key' || this.addTemplate
},
token: {
label: this.$t('assets.Token'),
component: UploadSecret,
hidden: (formValue) => formValue.secret_type !== 'token' || this.addTemplate
},
access_key: {
id: 'access_key',
label: this.$t('assets.AccessKey'),
component: UploadSecret,
hidden: (formValue) => formValue.secret_type !== 'access_key' || this.addTemplate
},
api_key: {
id: 'api_key',
label: this.$t('assets.ApiKey'),
component: UploadSecret,
hidden: (formValue) => formValue.secret_type !== 'api_key' || this.addTemplate
},
secret_type: {
type: 'radio-group',
options: [],
hidden: () => {
return this.addTemplate
}
},
push_now: {
helpText: this.$t('accounts.AccountPush.WindowsPushHelpText'),
hidden: (formValue) => {
const automation = this.iPlatform.automation || {}
return !automation.push_account_enabled ||
!automation.ansible_enabled ||
!this.$hasPerm('accounts.push_account') ||
(formValue.secret_type === 'ssh_key' && this.iPlatform.type.value === 'windows') ||
this.addTemplate
}
},
params: {
label: this.$t('assets.PushParams'),
component: AutomationParamsForm,
el: {
method: this.asset?.auto_config?.push_account_method
},
hidden: (formValue) => {
const automation = this.iPlatform.automation || {}
return !formValue.push_now ||
!automation.push_account_enabled ||
!automation.ansible_enabled ||
(formValue.secret_type === 'ssh_key' && this.iPlatform.type.value === 'windows') ||
!this.$hasPerm('accounts.push_account') ||
this.addTemplate
}
},
comment: {
hidden: () => {
return this.addTemplate
}
}
},
fieldsMeta: accountFieldsMeta(this),
hasSaveContinue: false
}
},
@@ -252,11 +75,18 @@ export default {
try {
await this.getPlatform()
this.setSecretTypeOptions()
this.getDefaultAssets()
} finally {
this.loading = false
}
},
methods: {
async getDefaultAssets() {
const assetId = this.$route.query.asset_id
if (assetId && !this.form.name) {
this.form.assets = [assetId]
}
},
async getPlatform() {
if (this.platform) {
this.iPlatform = this.platform
@@ -321,6 +151,3 @@ export default {
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,87 @@
<template>
<GenericUpdateFormDialog
v-if="visible"
:form-setting="formSetting"
:selected-rows="selectedRows"
:visible="visible"
v-on="$listeners"
/>
</template>
<script>
import { GenericUpdateFormDialog } from '@/layout/components'
import { accountFieldsMeta } from '@/components/Apps/AccountCreateUpdateForm/const'
import { encryptPassword } from '@/utils/crypto'
export default {
name: 'AccountBulkUpdateDialog',
components: {
GenericUpdateFormDialog
},
props: {
visible: {
type: Boolean,
default: false
},
selectedRows: {
type: Array,
default: () => ([])
}
},
data() {
return {
formSetting: {
url: '/api/v1/accounts/accounts/',
hasSaveContinue: false,
fields: [],
fieldsMeta: accountFieldsMeta(this),
cleanOtherFormValue: (formValue) => {
for (const value of formValue) {
Object.keys(value).forEach((item, index, arr) => {
if (['ssh_key', 'token', 'access_key', 'api_key', 'password'].includes(item)) {
value['secret'] = encryptPassword(value[item])
delete value[item]
}
})
}
return formValue
}
}
}
},
created() {
this.filterFieldsMeta()
},
methods: {
filterFieldsMeta() {
let fields = ['privileged']
const fieldsMeta = {}
const secretFields = ['password', 'ssh_key', 'passphrase', 'token', 'access_key', 'api_key']
const secret_type = this.selectedRows[0].secret_type?.value || 'password'
for (const field of secretFields) {
if (secret_type === 'ssh_key' && field === 'passphrase') {
fields.push('passphrase')
this.formSetting.fieldsMeta['passphrase'].hidden = () => false
continue
}
if (secret_type === field) {
fields.push(field)
this.formSetting.fieldsMeta[field].hidden = () => false
continue
}
delete this.formSetting.fieldsMeta[field]
}
fields = fields.concat(['is_active', 'comment'])
for (const field of fields) {
fieldsMeta[field] = this.formSetting.fieldsMeta[field]
}
this.formSetting.fields = fields
this.formSetting.fieldsMeta = fieldsMeta
}
}
}
</script>
<style scoped>
</style>

View File

@@ -37,6 +37,12 @@
:result="createAccountResults"
:visible.sync="showResultDialog"
/>
<AccountBulkUpdateDialog
v-if="updateSelectedDialogSetting.visible"
:visible.sync="updateSelectedDialogSetting.visible"
v-bind="updateSelectedDialogSetting"
@update="handleAccountBulkUpdate"
/>
</div>
</template>
@@ -49,10 +55,12 @@ import AccountCreateUpdate from './AccountCreateUpdate.vue'
import { connectivityMeta } from './const'
import { openTaskPage } from '@/utils/jms'
import ResultDialog from './BulkCreateResultDialog.vue'
import AccountBulkUpdateDialog from '@/components/Apps/AccountListTable/AccountBulkUpdateDialog.vue'
export default {
name: 'AccountListTable',
components: {
AccountBulkUpdateDialog,
ResultDialog,
ListTable,
UpdateSecretInfo,
@@ -117,6 +125,10 @@ export default {
headerExtraActions: {
type: Array,
default: () => []
},
extraQuery: {
type: Object,
default: () => ({})
}
},
data() {
@@ -138,9 +150,7 @@ export default {
app: 'assets',
resource: 'account'
},
extraQuery: {
order: '-date_updated'
},
extraQuery: this.extraQuery,
columnsExclude: ['spec_info'],
columnsShow: {
min: ['name', 'username', 'actions'],
@@ -239,7 +249,7 @@ export default {
},
{
name: 'Test',
title: this.$t('common.Test'),
title: this.$t('accounts.Test'),
can: ({ row }) =>
!this.$store.getters.currentOrgIsRoot &&
this.$hasPerm('accounts.change_account') &&
@@ -339,6 +349,29 @@ export default {
...this.headerExtraActions
],
extraMoreActions: [
{
name: 'BulkVerify',
title: this.$t('accounts.BulkVerify'),
type: 'primary',
fa: 'fa-link',
can: ({ selectedRows }) => {
return selectedRows.length > 0 &&
['clickhouse', 'redis', 'website', 'chatgpt'].indexOf(selectedRows[0].asset.type.value) === -1 &&
!this.$store.getters.currentOrgIsRoot
},
callback: function({ selectedRows }) {
const ids = selectedRows.map(v => {
return v.id
})
this.$axios.post(
'/api/v1/accounts/accounts/tasks/',
{ action: 'verify', accounts: ids }).then(res => {
openTaskPage(res['task'])
}).catch(err => {
this.$message.error(this.$tc('common.bulkVerifyErrorMsg' + ' ' + err))
})
}.bind(this)
},
{
name: 'ClearSecrets',
title: this.$t('common.ClearSecret'),
@@ -348,7 +381,9 @@ export default {
return selectedRows.length > 0 && vm.$hasPerm('accounts.change_account')
},
callback: function({ selectedRows }) {
const ids = selectedRows.map(v => { return v.id })
const ids = selectedRows.map(v => {
return v.id
})
this.$axios.patch(
'/api/v1/accounts/accounts/clear-secret/',
{ account_ids: ids }).then(() => {
@@ -357,6 +392,21 @@ export default {
this.$message.error(this.$tc('common.bulkClearErrorMsg' + ' ' + err))
})
}.bind(this)
},
{
name: 'actionUpdateSelected',
title: this.$t('accounts.AccountBatchUpdate'),
fa: 'batch-update',
can: ({ selectedRows }) => {
return selectedRows.length > 0 &&
!this.$store.getters.currentOrgIsRoot &&
vm.$hasPerm('accounts.change_account') &&
selectedRows.every(i => i.secret_type.value === selectedRows[0].secret_type.value)
},
callback: ({ selectedRows }) => {
vm.updateSelectedDialogSetting.selectedRows = selectedRows
vm.updateSelectedDialogSetting.visible = true
}
}
],
canBulkDelete: vm.$hasPerm('accounts.delete_account'),
@@ -365,6 +415,10 @@ export default {
exclude: ['asset']
},
hasSearch: true
},
updateSelectedDialogSetting: {
visible: false,
selectedRows: []
}
}
},
@@ -392,9 +446,18 @@ export default {
can: this.$hasPerm('accounts.delete_account'),
type: 'primary',
callback: ({ row }) => {
this.$axios.delete(`/api/v1/accounts/accounts/${row.id}/`).then(() => {
this.$message.success(this.$tc('common.deleteSuccessMsg'))
this.$refs.ListTable.reloadTable()
const msg = this.$t('accounts.AccountDeleteConfirmMsg')
this.$confirm(msg, this.$tc('common.Info'), {
type: 'warning',
confirmButtonClass: 'el-button--danger',
beforeClose: async(action, instance, done) => {
if (action !== 'confirm') return done()
this.$axios.delete(`/api/v1/accounts/accounts/${row.id}/`).then(() => {
done()
this.$refs.ListTable.reloadTable()
this.$message.success(this.$tc('common.deleteSuccessMsg'))
})
}
})
}
}
@@ -423,6 +486,10 @@ export default {
setTimeout(() => {
this.showResultDialog = true
}, 100)
},
handleAccountBulkUpdate() {
this.updateSelectedDialogSetting.visible = false
this.$refs.ListTable.reloadTable()
}
}
}

View File

@@ -83,6 +83,10 @@ export default {
type: String,
default: ''
},
type: {
type: String,
default: 'account'
},
title: {
type: String,
default: function() {
@@ -136,7 +140,8 @@ export default {
name: this.secretInfo.name,
secret: encryptPassword(this.modifiedSecret)
}
this.$axios.patch(`/api/v1/accounts/accounts/${this.account.id}/`, params).then(() => {
const url = this.type === 'account' ? `/api/v1/accounts/accounts` : `/api/v1/accounts/account-templates`
this.$axios.patch(`${url}/${this.account.id}/`, params).then(() => {
this.$message.success(this.$tc('common.updateSuccessMsg'))
})
},

View File

@@ -1,5 +1,7 @@
<template>
<Dialog
:close-on-click-modal="false"
:loading-status="!isLoaded"
:title="$tc('assets.Assets')"
custom-class="asset-select-dialog"
top="2vh"
@@ -17,8 +19,10 @@
:table-config="tableConfig"
:tree-url="`${baseNodeUrl}children/tree/`"
:url="baseUrl"
:tree-setting="treeSetting"
class="tree-table"
v-bind="$attrs"
@loaded="handleTableLoaded"
/>
</Dialog>
</template>
@@ -52,11 +56,16 @@ export default {
disabled: {
type: [Boolean, Function],
default: false
},
treeSetting: {
type: Object,
default: () => ({})
}
},
data() {
const vm = this
return {
isLoaded: false,
dialogVisible: false,
rowSelected: _.cloneDeep(this.value) || [],
rowsAdd: [],
@@ -104,6 +113,7 @@ export default {
headerActions: {
hasLeftActions: false,
hasRightActions: false,
hasLabelSearch: true,
searchConfig: {
getUrlQuery: false
}
@@ -136,6 +146,9 @@ export default {
if (selectValueIndex > -1) {
this.rowSelected.splice(selectValueIndex, 1)
}
},
handleTableLoaded() {
this.isLoaded = true
}
}
}

View File

@@ -13,6 +13,7 @@
ref="dialog"
:base-node-url="baseNodeUrl"
:base-url="baseUrl"
:tree-setting="treeSetting"
:tree-url-query="treeUrlQuery"
:value="value"
:visible.sync="dialogVisible"
@@ -27,7 +28,6 @@
<script>
import Select2 from '@/components/Form/FormFields/Select2.vue'
import AssetSelectDialog from './dialog.vue'
import { b } from 'css-color-function/lib/adjusters'
export default {
componentName: 'AssetSelect',
@@ -48,6 +48,10 @@ export default {
value: {
type: Array,
default: () => []
},
treeSetting: {
type: Object,
default: () => ({})
}
},
data() {
@@ -76,7 +80,6 @@ export default {
}
},
methods: {
b,
handleFocus() {
this.$refs.select2.selectRef.blur()
this.dialogVisible = true

View File

@@ -31,6 +31,10 @@ export default {
type: String,
default: '/api/v1/assets/assets/'
},
typeUrl: {
type: String,
default: '/api/v1/assets/nodes/category/tree/'
},
nodeUrl: {
type: String,
default: '/api/v1/assets/nodes/'
@@ -60,6 +64,7 @@ export default {
const showAssets = this.treeSetting?.showAssets || this.showAssets
const treeUrlQuery = this.setTreeUrlQuery()
const assetTreeUrl = `${this.treeUrl}?assets=${showAssets ? '1' : '0'}&${treeUrlQuery}`
const vm = this
return {
treeTabConfig: {
@@ -81,7 +86,13 @@ export default {
nodeUrl: this.nodeUrl,
treeUrl: assetTreeUrl,
callback: {
onSelected: (event, treeNode) => this.getAssetsUrl(treeNode)
onSelected: (event, treeNode) => this.getAssetsUrl(treeNode),
beforeRefresh: () => {
const query = { ...this.$route.query, node_id: '', asset_id: '' }
setTimeout(() => {
vm.$router.replace({ query: query })
}, 100)
}
},
...this.treeSetting
}
@@ -94,9 +105,9 @@ export default {
showAssets: false,
showSearch: false,
customTreeHeaderName: this.$t('assets.BuiltinTree'),
url: '/api/v1/assets/nodes/category/tree/',
url: this.typeUrl,
nodeUrl: this.treeSetting?.nodeUrl || this.nodeUrl,
treeUrl: `/api/v1/assets/nodes/category/tree/?assets=${showAssets ? '1' : '0'}&count_resource=${this.treeSetting.countResource || 'asset'}`,
treeUrl: `${this.typeUrl}?assets=${showAssets ? '1' : '0'}&count_resource=${this.treeSetting.countResource || 'asset'}`,
callback: {
onSelected: (event, treeNode) => this.getAssetsUrl(treeNode)
}

View File

@@ -0,0 +1,136 @@
<template>
<div>
<Dialog
:destroy-on-close="true"
:show-cancel="false"
:title="title"
:visible.sync="showSecret"
:width="'50'"
v-bind="$attrs"
@confirm="accountConfirmHandle"
v-on="$listeners"
>
<el-form :model="secretInfo" class="password-form" label-position="right" label-width="100px">
<el-form-item :label="$tc('accounts.AccountChangeSecret.OldSecret')">
<ShowKeyCopyFormatter
:cell-value="secretInfo.old_secret"
:col="{ formatterArgs: {
name: 'old_secret'
}}"
/>
</el-form-item>
<el-form-item :label="$tc('accounts.AccountChangeSecret.NewSecret')">
<ShowKeyCopyFormatter
:cell-value="secretInfo.new_secret"
:col="{ formatterArgs: {
name: 'new_secret'
}}"
/>
</el-form-item>
</el-form>
</Dialog>
</div>
</template>
<script>
import Dialog from '@/components/Dialog/index.vue'
import { ShowKeyCopyFormatter } from '@/components/Table/TableFormatters'
export default {
name: 'RecordViewSecret',
components: {
Dialog,
ShowKeyCopyFormatter
},
props: {
visible: {
type: Boolean,
default: false
},
url: {
type: String,
default: ''
},
title: {
type: String,
default: function() {
return this.$tc('common.ViewSecret')
}
}
},
data() {
return {
secretInfo: {},
showSecret: false,
mfaDialogVisible: true
}
},
computed: {
},
mounted() {
this.showSecretDialog()
},
methods: {
accountConfirmHandle() {
this.showSecret = false
this.mfaDialogVisible = false
},
showSecretDialog() {
return this.$axios.get(this.url, { disableFlashErrorMsg: true }).then((res) => {
this.secretInfo = res
this.showSecret = true
})
},
exit() {
this.$emit('update:visible', false)
}
}
}
</script>
<style lang="scss" scoped>
.item-textarea >>> .el-textarea__inner {
height: 110px;
}
.el-form-item {
border-bottom: 1px solid #EBEEF5;
padding: 5px 0;
margin-bottom: 0;
&:last-child {
border-bottom: none;
}
>>> .el-form-item__label {
padding-right: 20px;
line-height: 30px;
}
>>> .el-form-item__content {
line-height: 30px;
pre {
margin: 0;
}
}
}
ul {
margin: 0;
}
li {
display: block;
font-size: 13px;
margin-bottom: 8px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.title {
color: #303133;
font-weight: 500;
}
}
</style>

View File

@@ -42,7 +42,6 @@ const {
addMessageToActiveChat,
newChatAndAddMessageById,
removeLoadingMessageInChat,
removeLoadingAndAddMessageToChat,
updateChaMessageContentById,
addTemporaryLoadingToChat
} = useChat()
@@ -120,12 +119,11 @@ export default {
}
},
onChatMessage(data) {
if (!data.message.content && data.conversation_id) {
if (data.conversation_id) {
setLoading(true)
removeLoadingAndAddMessageToChat(data)
removeLoadingMessageInChat()
this.currentConversationId = data.conversation_id
} else {
updateChaMessageContentById(data.message.id, data.message.content)
updateChaMessageContentById(data.message.id, data)
}
if (data.message?.type === 'finish') {
setLoading(false)

View File

@@ -1,7 +1,7 @@
<template>
<div class="container">
<div class="close-sidebar">
<i class="el-icon-close" @click="onClose" />
<i v-if="hasClose" class="el-icon-close" @click="onClose" />
</div>
<el-tabs v-model="active" :tab-position="'right'" @tab-click="handleClick">
<el-tab-pane v-for="(item) in submenu" :key="item.name" :name="item.name">
@@ -22,6 +22,10 @@ export default {
type: String,
default: 'chat'
},
hasClose: {
type: Boolean,
default: true
},
submenu: {
type: Array,
default: () => []
@@ -48,9 +52,10 @@ export default {
height: 100%;
background-color: #f0f1f5;
.close-sidebar {
height: 48px;
padding: 12px 0;
text-align: center;
font-size: 14px;
padding: 12px 0;
cursor: pointer;
i {
font-size: 16px;

View File

@@ -18,7 +18,7 @@
</div>
</div>
<div class="sidebar">
<Sidebar :active.sync="active" :submenu="submenu" />
<Sidebar v-bind="$attrs" :active.sync="active" :submenu="submenu" />
</div>
</div>
</template>
@@ -62,11 +62,14 @@ export default {
watch: {
drawerPanelVisible(value) {
if (value && !ws) {
this.$refs.component?.init()
this.initWebSocket()
}
}
},
methods: {
initWebSocket() {
this.$refs.component?.init()
},
onClose() {
this.$parent.show = false
},

View File

@@ -59,13 +59,8 @@ export function useChat() {
addChatMessageById(chat)
}
const removeLoadingAndAddMessageToChat = (chat) => {
store.commit('chat/removeLoadingMessageInChat')
store.commit('chat/addMessageToActiveChat', chat)
}
const updateChaMessageContentById = (id, content) => {
store.commit('chat/updateChaMessageContentById', { id, content })
const updateChaMessageContentById = (id, data) => {
store.commit('chat/updateChaMessageContentById', { id, data })
pageScroll('scrollRef')
}
@@ -78,7 +73,6 @@ export function useChat() {
addMessageToActiveChat,
newChatAndAddMessageById,
removeLoadingMessageInChat,
removeLoadingAndAddMessageToChat,
addChatMessageById,
addTemporaryLoadingToChat,
updateChaMessageContentById

View File

@@ -174,7 +174,7 @@ export default {
.handle-button {
position: absolute;
top: 30%;
bottom: 20%;
left: -48px;
width: 48px;
height: 45px;

View File

@@ -5,6 +5,8 @@
<script type="text/jsx">
import TreeTable from '../../Table/TreeTable/index.vue'
import { DetailFormatter } from '@/components/Table/TableFormatters'
import { AccountInfoFormatter } from '@/components/Table/TableFormatters'
import { connectivityMeta } from '@/components/Apps/AccountListTable/const'
export default {
name: 'GrantedAssets',
@@ -57,10 +59,11 @@ export default {
tableConfig: {
url: this.tableUrl,
hasTree: true,
columnsExtra: ['view_account'],
columnsExclude: ['spec_info'],
columnShow: {
columnsShow: {
min: ['name', 'address', 'accounts'],
default: ['name', 'address', 'accounts', 'actions']
default: ['name', 'address', 'platform', 'view_account', 'connectivity']
},
columnsMeta: {
name: {
@@ -71,7 +74,13 @@ export default {
},
actions: {
has: false
}
},
view_account: {
label: this.$t('assets.Account'),
formatter: AccountInfoFormatter,
width: '100px'
},
connectivity: connectivityMeta
}
},
headerActions: {

View File

@@ -82,6 +82,7 @@
</template>
<script>
import Dialog from '@/components/Dialog/index.vue'
import { encryptPassword } from '@/utils/crypto'
export default {
name: 'UserConfirmDialog',
@@ -123,12 +124,15 @@ export default {
mounted() {
this.$eventBus.$on('showConfirmDialog', this.performConfirm)
},
beforeDestroy() {
this.$eventBus.$off('showConfirmDialog', this.performConfirm)
},
methods: {
handleSubTypeChange(val) {
this.inputPlaceholder = this.subTypeChoices.filter(item => item.name === val)[0]?.placeholder
this.smsWidth = val === 'sms' ? 6 : 0
},
performConfirm({ response, callback, cancel }) {
performConfirm: _.throttle(function({ response, callback, cancel }) {
if (this.processing || this.visible) {
return
}
@@ -164,7 +168,7 @@ export default {
}).finally(() => {
this.processing = false
})
},
}, 300),
logout() {
window.location.href = `${process.env.VUE_APP_LOGOUT_PATH}?next=${this.$route.fullPath}`
},
@@ -196,7 +200,7 @@ export default {
const data = {
confirm_type: this.confirmTypeRequired,
mfa_type: this.confirmTypeRequired === 'mfa' ? this.subTypeSelected : '',
secret_key: this.secretValue
secret_key: this.confirmTypeRequired === 'password' ? encryptPassword(this.secretValue) : this.secretValue
}
this.$axios.post(`/api/v1/authentication/confirm/`, data).then(res => {
this.callback()

View File

@@ -106,16 +106,28 @@ export default {
if (Array.isArray(value)) {
if (typeof value[0] === 'object') {
value.forEach(item => {
const fieldName = `${name}.${item.name}`
if (excludes.includes(fieldName)) {
return
}
this.items.push({
key: item.label,
value: item.value
const firstValue = value[0]
if (firstValue.hasOwnProperty('name')) {
value.forEach(item => {
const fieldName = `${name}.${item.name}`
if (excludes.includes(fieldName)) {
return
}
this.items.push({
key: item.label,
value: item.value
})
})
})
} else {
value.forEach((item, index) => {
const v = Object.entries(item).map(([key, value]) => `${key}:${value}`).join(', ')
const data = { value: v }
if (index === 0) {
data['key'] = label
}
this.items.push(data)
})
}
} else if (typeof value[0] === 'string') {
value.forEach((item, index) => {
let data = {}

View File

@@ -1,7 +1,7 @@
<template>
<IBox :fa="fa" :title="title">
<el-form class="content" label-position="left" label-width="25%">
<el-form-item v-for="item in items" :key="item.key" :label="item.key">
<el-form-item v-for="item in iItems" :key="item.key" :label="item.key">
<ItemValue :value="item.value" class="item-value" v-bind="item" />
</el-form-item>
</el-form>
@@ -35,6 +35,13 @@ export default {
type: String,
default: 'left'
}
},
data() {
return {
iItems: this.items.filter(item => {
return !item.hasOwnProperty('has') || item.has === true
})
}
}
}
</script>

View File

@@ -9,11 +9,13 @@
v-bind="$attrs"
v-on="$listeners"
>
<slot />
<div v-loading="loadingStatus">
<slot />
</div>
<div slot="footer" class="dialog-footer">
<slot name="footer">
<el-button v-if="showCancel && showButtons" @click="onCancel">{{ cancelTitle }}</el-button>
<el-button v-if="showConfirm && showButtons" :loading="loadingStatus" type="primary" @click="onConfirm">
<el-button v-if="showConfirm && showButtons" :disabled="loadingStatus" type="primary" @click="onConfirm">
{{ confirmTitle }}
</el-button>
</slot>
@@ -71,13 +73,16 @@ export default {
}
},
data() {
return {}
return {
}
},
computed: {
iWidth() {
return this.$store.getters.isMobile ? '1000px' : this.width
}
},
mounted() {
},
methods: {
onCancel() {
this.$emit('cancel')
@@ -92,7 +97,7 @@ export default {
<style lang="scss" scoped>
.dialog >>> .el-dialog {
border-radius: 0.3em;
max-width: 1500px;
max-width: min(100vw, 1500px);
.el-icon-circle-check {
display: none;
@@ -119,9 +124,14 @@ export default {
justify-content: flex-end;
}
}
@media (max-width: 900px) {
.dialog >>> .el-dialog {
max-width: calc(100% - 30px);
}
}
.dialog-footer >>> button.el-button {
font-size: 13px;
padding: 10px 20px;
}
</style>

View File

@@ -5,7 +5,6 @@ import Switcher from '@/components/Form/FormFields/Switcher.vue'
import rules from '@/components/Form/DataForm/rules'
import BasicTree from '@/components/Form/FormFields/BasicTree.vue'
import JsonEditor from '@/components/Form/FormFields/JsonEditor.vue'
import TransferSelect from '@/components/Form/FormFields/TransferSelect.vue'
import { assignIfNot } from '@/utils/common'
import TagInput from '@/components/Form/FormFields/TagInput.vue'
@@ -45,7 +44,7 @@ export class FormFieldGenerator {
break
case 'field':
type = ''
field.component = TransferSelect
field.component = ObjectSelect2
if (fieldRemoteMeta.required) {
field.el.clearable = false
}
@@ -76,7 +75,7 @@ export class FormFieldGenerator {
field.component = ObjectSelect2
break
case 'm2m_related_field':
field.component = TransferSelect
field.component = ObjectSelect2
field.el.label = field.label
break
case 'nested object':

View File

@@ -100,7 +100,7 @@
<div style="font-size: 13px;">{{ contabValueString }}</div>
</div>
</div>
<CrontabResult :ex="contabValueString" />
<CrontabResult :ex="contabValueString" @crontabDiffChange="crontabDiffChangeHandle" />
<div class="pop_btn">
<el-button
@@ -167,7 +167,8 @@ export default {
week: '*'
// year: "",
},
newContabValueString: ''
newContabValueString: '',
crontabDiff: 0
}
},
computed: {
@@ -364,6 +365,12 @@ export default {
},
// 填充表达式
submitFill() {
const crontabDiffMin = this.crontabDiff / 1000 / 60
if (crontabDiffMin > 0 && crontabDiffMin < 10) {
const msg = this.$tc('common.crontabDiffError')
this.$message.error(msg)
return
}
this.$emit('fill', this.contabValueString)
this.hidePopup()
},
@@ -381,6 +388,9 @@ export default {
for (const j in this.contabValueObj) {
this.changeRadio(j, this.contabValueObj[j])
}
},
crontabDiffChangeHandle(diff) {
this.crontabDiff = diff
}
}
}
@@ -454,7 +464,7 @@ export default {
}
.crontab-panel {
>>> .el-input-number {
> > > .el-input-number {
margin: 0 5px
}
}

View File

@@ -7,18 +7,11 @@
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="2">
{{ this.$t('common.CronTab.from') }}
<el-input-number v-model="cycle01" :max="60" :min="0" size="mini" /> -
<el-input-number v-model="cycle02" :max="60" :min="0" size="mini" /> {{ this.$t('common.CronTab.min') }}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="3">
{{ this.$t('common.CronTab.from') }}
<el-input-number v-model="average02" :max="60" :min="1" size="mini" /> {{ this.$t('common.CronTab.min') }}{{ this.$t('common.CronTab.executeOnce') }}
<el-input-number v-model="average02" :max="60" :min="1" size="mini" />
{{ this.$t('common.CronTab.min') }}{{ this.$t('common.CronTab.executeOnce') }}
</el-radio>
</el-form-item>
@@ -33,7 +26,7 @@
size="small"
style="width:100%"
>
<el-option v-for="item in 60" :key="item" :value="item-1">{{ item-1 }}</el-option>
<el-option v-for="item in 60" :key="item" :value="item-1">{{ item - 1 }}</el-option>
</el-select>
</el-radio>
</el-form-item>
@@ -158,7 +151,7 @@ export default {
</script>
<style scoped>
.el-form-item--small.el-form-item {
margin-bottom: 10px;
}
.el-form-item--small.el-form-item {
margin-bottom: 10px;
}
</style>

View File

@@ -14,6 +14,7 @@
<script>
import parser from 'cron-parser'
import { toSafeLocalDateStr } from '@/utils/common'
export default {
name: 'CrontabResult',
props: {
@@ -51,6 +52,10 @@ export default {
const cur = interval.next().toString()
this.resultList.push(toSafeLocalDateStr(cur))
}
const first = new Date(this.resultList[0])
const second = new Date(this.resultList[1])
const diff = Math.abs(second - first)
this.$emit('crontabDiffChange', diff)
} catch (error) {
this.isShow = false
// debug(error, 'error')

View File

@@ -7,13 +7,11 @@
v-bind="data.attrs"
>
<template v-if="data.helpTips" #label>
{{ data.label }}
<el-tooltip placement="top" effect="light" popper-class="help-tips">
<div slot="content" v-html="data.helpTips" />
<el-button style="padding: 0">
<i class="fa fa-question-circle" />
</el-button>
<i class="fa fa-question-circle-o" />
</el-tooltip>
{{ data.label }}
</template>
<template v-if="readonly && hasReadonlyContent">
<div
@@ -70,7 +68,8 @@
:key="opt.label"
v-bind="opt"
:label="'value' in opt ? opt.value : opt.label"
>{{ opt.label }}</el-radio>
>{{ opt.label }}
</el-radio>
</template>
</custom-component>
<div v-if="data.helpText" class="help-block" v-html="data.helpText" />

View File

@@ -2,7 +2,7 @@
<div class="code-editor" style="font-size: 12px">
<div class="toolbar">
<div
v-for="(item,index) in toolbar.left"
v-for="(item,index) in iActions"
:key="index"
style="display: inline-block; margin: 0 2px"
>
@@ -93,6 +93,16 @@
</el-tooltip>
</div>
<div v-if="toolbar.hasOwnProperty('fold')" class="fold">
<el-tooltip :content="$tc('common.MoreActions')" placement="top">
<i
class="fa"
:class="[isFold ? 'fa-angle-double-right': 'fa-angle-double-down']"
@click="onChangeFold"
/>
</el-tooltip>
</div>
<div class="right-side" style="float: right">
<div
v-for="(item,index) in toolbar.right"
@@ -154,9 +164,19 @@ export default {
}
},
data() {
return {}
return {
isFold: true
}
},
computed: {
iActions() {
let actions = this.toolbar.left || {}
const fold = this.toolbar.fold || {}
if (!this.isFold) {
actions = { ...actions, ...fold }
}
return actions
},
iValue: {
get() {
return this.value
@@ -179,6 +199,9 @@ export default {
}
},
methods: {
onChangeFold() {
this.isFold = !this.isFold
},
getLabel(value, items) {
for (const item of items) {
if (item.value === value) {
@@ -205,6 +228,16 @@ export default {
margin-bottom: 5px;
}
.fold {
display: inline-block;
padding-left: 4px;
i {
font-weight: bold;
font-size: 15px;
cursor: pointer;
}
}
> > > .CodeMirror pre.CodeMirror-line,
> > > .CodeMirror-linenumber.CodeMirror-gutter-elt {
line-height: 18px !important;

View File

@@ -24,6 +24,7 @@
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'PhoneInput',
@@ -35,21 +36,7 @@ export default {
},
data() {
return {
rawValue: {},
countries: [
{ name: 'China(中国)', value: '+86' },
{ name: 'HongKong(中国香港)', value: '+852' },
{ name: 'Macao(中国澳门)', value: '+853' },
{ name: 'Taiwan(中国台湾)', value: '+886' },
{ name: 'America(America)', value: '+1' },
{ name: 'Russia(Россия)', value: '+7' },
{ name: 'France(français)', value: '+33' },
{ name: 'Britain(Britain)', value: '+44' },
{ name: 'Germany(Deutschland)', value: '+49' },
{ name: 'Japan(日本)', value: '+81' },
{ name: 'Korea(한국)', value: '+82' },
{ name: 'India(भारत)', value: '+91' }
]
rawValue: {}
}
},
computed: {
@@ -58,7 +45,13 @@ export default {
return ''
}
return `${this.rawValue.code}${this.rawValue.phone}`
}
},
countries: {
get() {
return this.publicSettings.COUNTRY_CALLING_CODES
}
},
...mapGetters(['publicSettings'])
},
mounted() {
this.rawValue = this.value || { code: '+86', phone: '' }

View File

@@ -225,9 +225,6 @@ export default {
handler(newValue, oldValue) {
},
deep: true
},
iOptions(val) {
this.remote = val.length !== 0
}
},
async mounted() {
@@ -359,7 +356,7 @@ export default {
})
},
clearSelected() {
this.iValue = []
this.iValue = this.multiple ? [] : ''
},
checkDisabled(item) {
return item.disabled === undefined ? this.disabledValues.indexOf(item.value) !== -1 : item.disabled

View File

@@ -11,11 +11,15 @@
/>
<Dialog
v-if="showTransfer"
:loading-status="!isLoaded"
:close-on-click-modal="false"
:title="label"
:visible.sync="showTransfer"
class="the-dialog"
width="730px"
@cancel="handleTransCancel"
@confirm="handleTransConfirm"
v-on="$listeners"
>
<krryPaging v-if="selectInitialized" ref="pageTransfer" class="transfer" v-bind="pagingTransfer" />
</Dialog>
@@ -75,13 +79,16 @@ export default {
if (keyword) {
params['search'] = keyword
}
this.isLoaded = false
const data = await this.$axios.get(url, { params })
this.isLoaded = true
return data['results'].map(item => {
const n = transformOption(item)
return { id: n.value, label: n.label }
})
}
return {
isLoaded: false,
showTransfer: false,
selectInitialized: false,
select2: {
@@ -125,13 +132,17 @@ export default {
return _.uniq(value)
},
set(val) {
this.$emit('input', val)
this.emit(val)
}
}
},
methods: {
emit(val) {
const value = _.uniq(val)
this.$emit('input', value)
},
onInputChange(val) {
this.$emit('input', val)
this.emit(val)
},
handleFocus() {
this.$refs.select2.selectRef.blur()
@@ -149,18 +160,14 @@ export default {
this.showTransfer = false
},
handleTransConfirm() {
const selectedData = this.$refs.pageTransfer.checkedData
const selectedData = this.$refs.pageTransfer.selectListCheck
const options = selectedData.map(item => {
return { value: item.id, label: item.label }
})
this.select2.options = options
this.$emit('input', options.map(item => item.value))
this.emit(options.map(item => item.value))
this.showTransfer = false
}
}
}
</script>
<style scoped>
</style>

View File

@@ -8,7 +8,7 @@
<div v-if="tip !== ''" class="help-block">{{ tip }}</div>
<input v-model="value" hidden type="text" v-on="$listeners">
<div>
<img :src="preview" v-bind="$attrs">
<img :class="showBG ? 'show-bg' : ''" :src="preview" v-bind="$attrs">
</div>
</div>
</template>
@@ -27,6 +27,10 @@ export default {
accept: {
type: String,
default: '*'
},
showBG: {
type: Boolean,
default: false
}
},
data() {
@@ -74,6 +78,8 @@ export default {
}
</script>
<style scoped>
<style lang="scss" scoped>
.show-bg {
background-color: var(--banner-bg);
}
</style>

View File

@@ -42,7 +42,7 @@ export default {
patterns.push([/\d/, i18n.t('common.password.NUMBER_REQUIRED')])
}
if (passwordRule['SECURITY_PASSWORD_SPECIAL_CHAR']) {
const pattern = new RegExp("[`~!@#$^&*()=|{}':;',\\[\\].<>/?~@#¥……&*()——|{}【】‘;:”“'。,、?]")
const pattern = new RegExp("[`~!@#$^&*()=|{}':;',\\[\\].<>/?~@#¥……&*()——|{}【】‘;:”“'。,、?_+-]")
patterns.push([pattern, i18n.t('common.password.SPECIAL_CHAR_REQUIRED')])
}
for (const [pattern, msg] of patterns) {

View File

@@ -204,7 +204,12 @@ export default {
},
formatWeektime(col) {
const timeStamp = 1542384000000 // '2018-11-17 00:00:00'
const beginStamp = timeStamp + col * 1800000 // col * 30 * 60 * 1000
const timezone = 8
const offsetGMT = new Date().getTimezoneOffset() // 本地时间和格林威治的时间差,单位为分钟
const nowDate = new Date(timeStamp).getTime()
const targetStamp = new Date(nowDate + offsetGMT * 60 * 1000 + timezone * 60 * 60 * 1000).getTime()
const beginStamp = targetStamp + col * 1800000 // col * 30 * 60 * 1000
const endStamp = beginStamp + 1800000
const begin = this.formatDate(new Date(beginStamp), 'hh:mm')

View File

@@ -53,27 +53,34 @@ export default {
},
props: {
boxTitle: {
type: Array
type: Array,
default: () => []
},
boxOperation: {
type: Array
type: Array,
default: () => []
},
// 地域数据
dataObj: {
type: Object
type: Object,
default: () => {}
},
// 已选数据
selectedData: {
type: Array
type: Array,
default: () => []
},
onChangeSelected: {
type: Function
type: Function,
default: () => () => {}
},
filterable: {
type: Boolean
type: Boolean,
default: () => false
},
filterPlaceholder: {
type: String
type: String,
default: () => ''
}
},
data() {

View File

@@ -46,7 +46,7 @@
<div class="vip-footer">
<el-button
type="text"
:disabled="selectedDistrict.length > 0 ? false : true"
:disabled="selectedDistrict.length<=0"
size="small"
round
@click="checkedSelected"
@@ -62,23 +62,29 @@ export default {
components: {},
props: {
title: {
type: String
type: String,
default: () => ''
},
operation: {
type: String
type: String,
default: () => ''
},
operateId: {
type: Number
type: Number,
default: () => 0
},
// 区域数据
districtList: {
type: Array
type: Array,
default: () => []
},
filterable: {
type: Boolean
type: Boolean,
default: () => false
},
filterPlaceholder: {
type: String
type: String,
default: () => ''
}
},
data() {

View File

@@ -1,61 +1,69 @@
<template>
<div class="krry-main">
<krry-box
ref="noSelect"
:async="async"
:async-search-flag="asyncSearchFlag"
:data-show-list="notSelectDataList"
:filter-placeholder="filterPlaceholder[0] || $tc('common.Search')"
:filterable="filterable"
:highlight-color="highlightColor"
:is-highlight="isHighlight"
:is-last-page="isLastPage"
:operate-id="0"
:page-size="pageSize"
:page-texts="pageTexts"
:show-clear-btn="showClearBtn"
:title="boxTitle[0] || $tc('common.Selection')"
@check-district="noCheckSelect"
@search-word="searchWord"
@check-disable="checkDisable"
@get-data="getData"
@get-data-by-keyword="getDataByKeyword"
@clear-input="clearQueryInp('left')"
/>
<div class="opera">
<el-button
:disabled="disablePre"
class="el-transfer__button"
icon="el-icon-arrow-left"
size="mini"
@click="deleteData"
/>
<el-button
:disabled="disableNex"
class="el-transfer__button"
icon="el-icon-arrow-right"
size="mini"
type="primary"
@click="addData"
/>
</div>
<krry-box
ref="hasSelect"
:data-show-list="checkedData"
:filter-placeholder="filterPlaceholder[1] || $tc('common.Search')"
:filterable="filterable"
:highlight-color="highlightColor"
:is-highlight="isHighlight"
:operate-id="1"
:page-size="pageSize"
:page-texts="pageTexts"
:show-clear-btn="showClearBtn"
:title="boxTitle[1] || $tc('common.Selected')"
@check-district="hasCheckSelect"
@search-word="searchWord"
@check-disable="checkDisable"
@clear-input="clearQueryInp('right')"
/>
<el-row :gutter="10">
<el-col :md="10" :sm="24">
<krry-box
ref="noSelect"
:async="async"
:async-search-flag="asyncSearchFlag"
:data-show-list="notSelectDataList"
:filter-placeholder="filterPlaceholder[0] || $tc('common.Search')"
:filterable="filterable"
:highlight-color="highlightColor"
:is-highlight="isHighlight"
:is-last-page="isLastPage"
:operate-id="0"
:page-size="pageSize"
:page-texts="pageTexts"
:show-clear-btn="showClearBtn"
:title="boxTitle[0] || $tc('common.Selection')"
@check-district="noCheckSelect"
@search-word="searchWord"
@check-disable="checkDisable"
@get-data="getData"
@get-data-by-keyword="getDataByKeyword"
@clear-input="clearQueryInp('left')"
/>
</el-col>
<el-col :md="4" :sm="24" class="buttons">
<div class="opera">
<el-button
:disabled="disablePre"
class="el-transfer__button"
icon="el-icon-arrow-left"
size="mini"
@click="deleteData"
/>
<el-button
:disabled="disableNex"
class="el-transfer__button"
icon="el-icon-arrow-right"
size="mini"
type="primary"
@click="addData"
/>
</div>
</el-col>
<el-col :md="10" :sm="24">
<krry-box
ref="hasSelect"
:data-show-list="checkedData"
:filter-placeholder="filterPlaceholder[1] || $tc('common.Search')"
:filterable="filterable"
:highlight-color="highlightColor"
:is-highlight="isHighlight"
:operate-id="1"
:page-size="pageSize"
:page-texts="pageTexts"
:show-clear-btn="showClearBtn"
:title="boxTitle[1] || $tc('common.Selected')"
@check-district="hasCheckSelect"
@search-word="searchWord"
@check-disable="checkDisable"
@clear-input="clearQueryInp('right')"
/>
</el-col>
</el-row>
</div>
</template>
@@ -189,7 +197,7 @@ export default {
}
},
created() {
this.async ? this.getData(1) : this.initData()
this.async ? this.getData(1, true) : this.initData(true)
},
methods: {
// 分页数据,初始化数据,过滤已选数据
@@ -370,7 +378,7 @@ export default {
await this.getData(1)
}
},
async getData(pageIndex) {
async getData(pageIndex, changed = false) {
this.$nextTick(() => {
// 设置异步分页的 pageIndex
this.$refs.noSelect.asyncPageIndex = pageIndex
@@ -383,7 +391,8 @@ export default {
if (Array.isArray(resData) && resData.length) {
this.asyncDataList = resData
this.notSelectDataList = resData
this.initData(false)
// 这里必须是 true否则右侧不能搜索, 一搜索确认就不行了
this.initData(changed)
this.isLastPage = resData.length < this.pageSize
} else {
this.notSelectDataList = []
@@ -401,18 +410,27 @@ export default {
.inner-center {
margin: 0 5px;
}
.buttons {
vertical-align: middle;
}
.opera {
position: relative;
display: inline-block;
vertical-align: middle;
margin: 0 8px;
text-align: center;
margin: 180px 8px;
width: 100%;
@media screen and (max-width: 992px) {
margin: 8px 8px;
text-align:start
}
.el-button.is-circle {
border-radius: 50%;
padding: 12px;
display: block;
margin: 25px auto;
}
.el-transfer__button {
padding: 5px;

View File

@@ -52,7 +52,8 @@ export default {
},
data() {
return {
empty: () => {}
empty: () => {
}
}
},
computed: {

View File

@@ -3,7 +3,7 @@
<el-button v-if="shouldFold" circle class="search-btn" size="mini" @click="handleManualSearch">
<svg-icon icon-class="search" />
</el-button>
<TagSearch v-else :options="iOption" v-bind="$attrs" @tagSearch="handleTagSearch" v-on="$listeners" />
<TagSearch v-else :options="iOption" v-bind="$attrs" v-on="$listeners" @tag-search="handleTagSearch" />
</span>
</template>
@@ -68,6 +68,9 @@ export default {
},
methods: {
handleTagSearch(tags) {
if (_.isEqual(tags, this.tags)) {
return
}
this.tags = tags
if (tags.length === 0) {
this.manualSearch = false

View File

@@ -34,12 +34,14 @@ class StrategyNormal extends StrategyAbstract {
onSelectionChange(val) {
this.elDataTable.selected = val
}
/**
* toggleRowSelection和clearSelection的表现与el-table一致
*/
toggleRowSelection(...args) {
return this.elTable.toggleRowSelection(...args)
}
clearSelection() {
return this.elTable.clearSelection()
}
@@ -50,12 +52,12 @@ class StrategyNormal extends StrategyAbstract {
*/
class StrategyPersistSelection extends StrategyAbstract {
/**
* el-tableselection-change事件不适用于开启跨页保存的情况。
* 比如当开启persistSelection时发生以下两个场景
* el-tableselection-change 事件不适用于开启跨页保存的情况。
* 比如,当开启 persistSelection时发生以下两个场景
* 1. 用户点击翻页
* 2. 用户点击行首的切换全选项按钮,清空当前页多选项数据
* 其中场景1应该保持selected不变而场景2只应该从selected移除当前页所有行保留其他页面的多选状态。
* 但el-tableselection-change事件在两个场景中无差别发生所以这里不处理这个事件
* 其中场景 1 应该保持 selected 不变;而场景 2 只应该从 selected 移除当前页所有行,保留其他页面的多选状态。
* 但 el-tableselection-change 事件在两个场景中无差别发生,所以这里不处理这个事件
*/
/**
@@ -63,53 +65,106 @@ class StrategyPersistSelection extends StrategyAbstract {
*/
onSelect(selection, row) {
const isChosen = selection.indexOf(row) > -1
this.toggleRowSelection(row, isChosen)
}
/**
* 用户切换当前页的多选
*/
onSelectAll(selection, selectable = () => true) {
const isSelected = !!selection.length
this.elDataTable.data.forEach(r => {
const { id, selected, data } = this.elDataTable
const selectedIds = new Set(selected.map(r => r[id]))
// 获取当前所有已选择的项
const selectedRows = data.filter(r => selection.includes(r))
// 判断是否已全选
const isSelected = data.every(r => selectable(r) && selectedRows.includes(r))
const rowsToSelect = []
const rowsToDeselect = []
data.forEach(r => {
if (selectable(r)) {
this.toggleRowSelection(r, isSelected)
const isRowSelected = selectedIds.has(r[id])
if (isSelected && !isRowSelected) {
rowsToSelect.push(r)
} else if (!isSelected && isRowSelected) {
rowsToDeselect.push(r)
}
}
})
if (isSelected) {
rowsToSelect.forEach(row => {
selected.push(row)
selectedIds.add(row[id])
})
rowsToDeselect.forEach(row => {
this.elDataTable.toggleRowSelection(row, true)
})
} else {
rowsToDeselect.forEach(row => {
const index = selected.findIndex(item => item[id] === row[id])
if (index !== -1) {
selected.splice(index, 1)
}
selectedIds.delete(row[id])
})
rowsToSelect.forEach(row => {
this.elDataTable.toggleRowSelection(row, false)
})
}
// this.elTable.selected = Array.from(selectedIds).map(id => {
// return data.find(r => r[id] === id)
// })
}
/**
* toggleRowSelectionclearSelection管理elDataTableselected数组
* 记得最后要将状态同步到el-table中
* toggleRowSelectionclearSelection 管理 elDataTableselected 数组
* 记得最后要将状态同步到 el-table
*/
toggleRowSelection(row, isSelected) {
const { id, selected } = this.elDataTable
const foundIndex = selected.findIndex(r => r[id] === row[id])
if (typeof isSelected === 'undefined') {
isSelected = foundIndex <= -1
}
if (isSelected && foundIndex === -1) {
selected.push(row)
} else if (!isSelected && foundIndex > -1) {
selected.splice(foundIndex, 1)
}
this.elDataTable.$emit('toggle-row-selection', isSelected, row)
this.updateElTableSelection()
}
clearSelection() {
this.elDataTable.selected = []
this.updateElTableSelection()
}
/**
* 将selected状态同步到el-table中
*/
updateElTableSelection() {
const { data, id, selected } = this.elDataTable
// 历史勾选的行已经不在当前页了所以要将当前页的行数据和selected合并
const mergeData = _.uniqWith([...data, ...selected], _.isEqual)
mergeData.forEach(r => {
const isSelected = !!selected.find(r2 => r[id] === r2[id])
if (!this.elTable) {
return
}
this.elTable.toggleRowSelection(r, isSelected)
})
}

View File

@@ -153,6 +153,8 @@ export default {
this.toggleRowSelection(row, true)
}
}
this.$emit('loaded')
},
handleSizeChange(val) {
localStorage.setItem('paginationSize', val)

View File

@@ -48,6 +48,7 @@
import Dialog from '@/components/Dialog/index.vue'
import { createSourceIdCache } from '@/api/common'
import * as queryUtil from '@/components/Table/DataTable/compenents/el-data-table/utils/query'
import { download } from '@/utils/common'
export default {
name: 'ExportDialog',
@@ -187,10 +188,7 @@ export default {
})
},
downloadCsv(url) {
const a = document.createElement('a')
a.href = url
a.click()
window.URL.revokeObjectURL(url)
download(url)
},
async defaultPerformExport(selectRows, exportOption, q, exportTypeOption) {
const url = (process.env.VUE_APP_ENV === 'production') ? (`${this.url}`) : (`${process.env.VUE_APP_BASE_API}${this.url}`)

View File

@@ -68,7 +68,7 @@
<script>
import Dialog from '@/components/Dialog/index.vue'
import ImportTable from '@/components/Table/ListTable/TableAction/ImportTable.vue'
import { getErrorResponseMsg } from '@/utils/common'
import { download, getErrorResponseMsg } from '@/utils/common'
import { createSourceIdCache } from '@/api/common'
export default {
@@ -199,7 +199,8 @@ export default {
},
async getDownloadTemplateUrl(tp) {
const template = this.importOption === 'create' ? 'import' : 'update'
let query = `format=${tp}&template=${template}`
const action = this.importOption === 'create' ? 'create' : 'partial_update'
let query = `format=${tp}&template=${template}&action=${action}`
if (this.importOption === 'update' && this.selectedRows.length > 0) {
const resources = []
for (const item of this.selectedRows) {
@@ -220,10 +221,7 @@ export default {
this.$message.success(msg)
},
downloadCsv(url) {
const a = document.createElement('a')
a.href = url
a.click()
window.URL.revokeObjectURL(url)
download(url)
},
async handleImportConfirm() {
await this.$refs['importTable'].performUpload()

View File

@@ -1,6 +1,6 @@
<template>
<div>
<el-row>
<el-row type="flex" align="center">
<el-col :md="8" :sm="24">
<div class="tableFilter">
<el-radio-group v-model="importStatusFilter" size="small">
@@ -11,7 +11,7 @@
</el-radio-group>
</div>
</el-col>
<el-col :md="8" :sm="24" style="text-align: center">
<el-col :md="16" :sm="24" style="text-align: center; display: flex; align-items: 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>

View File

@@ -104,7 +104,7 @@ export default {
title: this.$t('common.BatchUpdate'),
name: 'actionUpdateSelected',
has: this.hasBulkUpdate,
icon: 'fa fa-refresh',
fa: 'batch-update',
can: function({ selectedRows }) {
let canBulkUpdate = vm.canBulkUpdate
if (typeof canBulkUpdate === 'function') {

View File

@@ -122,6 +122,9 @@ export default {
return this.hasLeftActions ? 'right' : 'left'
}
},
created() {
this.$emit('done')
},
methods: {
handleTagSearch(val) {
this.searchTable(val)
@@ -144,113 +147,121 @@ export default {
</script>
<style lang='scss' scoped>
.table-header {
/*display: flex;*/
/*flex-direction: row;*/
/*justify-content: space-between;*/
}
.table-header {
/*display: flex;*/
/*flex-direction: row;*/
/*justify-content: space-between;*/
}
.right-side-item {
}
.right-side-item {
}
.right-side-actions >>> .el-button {
border: none;
padding: 5px;
font-size: 14px;
width: 26px;
height: 26px;
color: #888;
background-color: transparent;
}
.right-side-actions > > > .el-button {
border: none;
padding: 5px;
font-size: 14px;
width: 26px;
height: 26px;
color: #888;
background-color: transparent;
}
.right-side-actions >>> .fa {
height: 16px;
width: 16px;
}
.right-side-actions > > > .fa {
height: 16px;
width: 16px;
}
.right-side-actions >>> .el-button:hover {
background-color: rgb(0, 0, 0, 0.05);
}
.right-side-actions > > > .el-button:hover {
background-color: rgb(0, 0, 0, 0.05);
}
.action-search >>> .el-input__suffix i {
font-weight: 500;
color: #888;
}
.action-search > > > .el-input__suffix i {
font-weight: 500;
color: #888;
}
.action-search >>> .el-cascader {
line-height: 32px !important;
}
.action-search > > > .el-cascader {
line-height: 32px !important;
}
.right-side-actions {
display: flex;
padding-left: 10px;
align-items: center;
justify-content: center;
}
.right-side-actions {
display: flex;
padding-left: 10px;
align-items: center;
justify-content: center;
}
.table-action-right-side {
display: flex;
justify-content: center;
}
.table-action-right-side {
display: flex;
justify-content: center;
}
.export-item {
display: block;
padding: 5px 20px;
}
.export-item {
display: block;
padding: 5px 20px;
}
.datepicker {
margin-left: 10px;
}
.datepicker {
margin-left: 10px;
}
.table-header {
line-height: 32px;
}
.table-header {
line-height: 32px;
}
.left-side {
float: left;
display: block;
}
.left-side {
float: left;
display: block;
.right-side {
float: right;
}
& > > > .action-item.el-dropdown {
height: 33px;
.search {
display: flex;
flex-direction: row;
& > .el-button {
height: 100%;
}
}
}
.mobile .search {
display: inherit;
}
.right-side {
float: right;
}
.mobile .search .datepicker {
margin-left: 0;
}
.search {
display: flex;
flex-direction: row;
}
.search.left {
float: left;
padding: 0 !important;
}
.mobile .search {
display: inherit;
}
.search.right {
float: right;
}
.mobile .search .datepicker {
margin-left: 0;
}
.mobile .search.right {
float: none;
}
.search.left {
float: left;
padding: 0 !important;
}
.mobile .search.right .action-search {
width: 100%;
}
.search.right {
float: right;
}
.mobile .right-side {
padding-top: 5px;
}
.mobile .search.right {
float: none;
}
.filter-field.right-side-item.action-search {
height: 34px;
}
.mobile .search.right .action-search {
width: 100%;
}
.mobile .right-side {
padding-top: 5px;
}
.filter-field.right-side-item.action-search {
height: 34px;
}
</style>

View File

@@ -8,9 +8,11 @@
:selected-rows="selectedRows"
:table-url="tableUrl"
v-bind="iHeaderActions"
@done="handleActionInitialDone"
/>
<IBox class="table-content">
<AutoDataTable
v-if="actionInit"
ref="dataTable"
:config="iTableConfig"
:filter-table="filter"
@@ -73,7 +75,10 @@ export default {
return {
selectedRows: [],
init: false,
extraQuery: extraQuery
isDeactivated: false,
extraQuery: extraQuery,
actionInit: this.headerActions.has === false,
initQuery: {}
}
},
computed: {
@@ -166,19 +171,43 @@ export default {
}
},
methods: {
handleActionInitialDone() {
setTimeout(() => {
this.actionInit = true
}, 100)
},
handleSelectionChange(val) {
this.selectedRows = val
},
reloadTable() {
this.dataTable.getList()
},
updateInitQuery(attrs) {
if (!this.actionInit) {
this.initQuery = attrs
for (const key in attrs) {
this.$set(this.extraQuery, key, attrs[key])
}
return true
}
const removeKeys = Object.keys(this.initQuery).filter(key => !attrs[key])
for (const key of removeKeys) {
this.$delete(this.extraQuery, key)
}
},
search(attrs) {
this.$log.debug('ListTable: search table', attrs)
const init = this.updateInitQuery(attrs)
if (init) {
return
}
this.$emit('TagSearch', attrs)
return this.dataTable?.search(attrs, true)
this.$refs.dataTable?.$refs.dataTable?.search(attrs, true)
},
filter(attrs) {
this.$emit('TagFilter', attrs)
this.$refs.dataTable.$refs.dataTable.search(attrs, true)
this.$log.debug('ListTable: found filter change', attrs)
this.search(attrs)
},
hasActionPerm(action) {
const permRequired = this.permissions[action]

View File

@@ -0,0 +1,62 @@
<template>
<el-popover
:title="title"
placement="left-start"
trigger="click"
@show="getAsyncItems"
>
<div class="detail-content">
<div v-for="account of accountData" :key="account.id" class="detail-item">
<span>{{ account.name }}({{ account.username }})</span>
</div>
</div>
<el-button slot="reference" size="mini" type="primary">{{ $t('common.View') }}</el-button>
</el-popover>
</template>
<script>
import BaseFormatter from './base.vue'
export default {
name: 'SwitchFormatter',
extends: BaseFormatter,
data() {
return {
formatterArgs: Object.assign(this.formatterArgsDefault, this.col.formatterArgs),
value: this.cellValue,
accountData: []
}
},
computed: {
title() {
return this.formatterArgs.title || this.col.label
}
},
methods: {
async getAsyncItems() {
const userId = this.$route.params.id
const url = `/api/v1/perms/users/${userId}/assets/${this.row.id}`
this.$axios.get(url).then(res => {
this.accountData = res?.permed_accounts || []
})
}
}
}
</script>
<style scoped>
.detail-content {
max-height: 150px;
overflow-y: auto;
}
.detail-item {
border-bottom: 1px solid #EBEEF5;
padding: 5px 0;
margin-bottom: 0;
&:hover {
background-color: #F5F7FA;
}
}
</style>

View File

@@ -3,17 +3,19 @@
<template>
<el-popover
:disabled="!showItems"
:open-delay="500"
:title="title"
placement="top-start"
trigger="hover"
width="400"
@show="getAsyncItems"
>
<div class="detail-content">
<div v-for="item of items" :key="getKey(item)" class="detail-item">
<span class="detail-item-name">{{ item }}</span>
</div>
</div>
<span slot="reference">{{ items && items.length }}</span>
<span slot="reference">{{ amount }}</span>
</el-popover>
</template>
</DetailFormatter>
@@ -37,55 +39,111 @@ export default {
showItems: true,
getItem(item) {
return item.name
}
},
async: false,
ajax: {},
title: ''
}
}
}
},
data() {
const formatterArgs = Object.assign(this.formatterArgsDefault, this.col.formatterArgs || {})
return {
formatterArgs: Object.assign(this.formatterArgsDefault, this.col.formatterArgs || {})
formatterArgs: formatterArgs,
data: formatterArgs.async ? [] : (this.cellValue || []),
amount: '',
asyncGetDone: false
}
},
computed: {
title() {
return this.formatterArgs.title || ''
return this.formatterArgs.title || this.col.label.replace('amount', '').replace('数量', '')
},
cellValueToRemove() {
return this.formatterArgs.cellValueToRemove || []
},
items() {
if (this.formatterArgs.async && !this.asyncGetDone) {
return [this.$t('common.tree.Loading') + '...']
}
const getItem = this.formatterArgs.getItem || (item => item.name)
let data = this.cellValue?.map(item => getItem(item)) || []
let data = []
if (Array.isArray(this.data)) {
data = this.data.map(item => getItem(item)) || []
} else {
// object {key: [value]}
data = Object.entries(this.data).map(([key, value]) => {
const item = { key: key, value: value }
return getItem(item)
}) || []
}
data = data.filter(Boolean)
return data
},
showItems() {
return this.formatterArgs.showItems !== false && this.cellValue?.length > 0
return this.amount !== 0 && this.amount !== ''
}
},
async mounted() {
if (this.formatterArgs.async) {
this.amount = this.cellValue
} else {
let cellValue = []
if (Array.isArray(this.cellValue)) {
cellValue = this.cellValue
} else {
// object {key: [value]}
cellValue = Object.keys(this.cellValue)
}
this.amount = (cellValue?.filter(value => !this.cellValueToRemove.includes(value)) || []).length
}
},
methods: {
getKey(item) {
const id = Math.random().toString(36).substring(2)
const id = Math.random().toString(36).substring(16)
return id + item
},
getDefaultUrl() {
const url = new URL(this.url, location.origin)
url.pathname += this.row.id + '/'
return url.pathname
},
async getAsyncItems() {
if (!this.formatterArgs.async) {
return
}
if (this.asyncGetDone) {
return
}
const url = this.formatterArgs.ajax.url || this.getDefaultUrl()
const params = this.formatterArgs.ajax.params || {}
const transform = this.formatterArgs.ajax.transform || (resp => resp[this.col.prop.replace('_amount', '')])
const response = await this.$axios.get(url, { params: params })
this.data = transform(response)
this.asyncGetDone = true
}
}
}
</script>
<style lang="scss" scoped>
.detail-content {
padding: 20px 10px;
padding: 5px 10px;
max-height: 60vh;
overflow-y: auto;
}
.detail-item {
border-bottom: 1px solid #EBEEF5;
padding: 5px 0;
margin-bottom: 0;
&:hover {
background-color: #F5F7FA;
background-color: #F5F7FA;
}
}
.detail-item:first-child {
border-top: 1px solid #EBEEF5;
//border-top: 1px solid #EBEEF5;
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<span>{{ value }}</span>
<span class="date">{{ dateValue }}</span>
</template>
<script>
@@ -10,24 +10,31 @@ export default {
name: 'DateFormatter',
extends: BaseFormatter,
data() {
let value
if (this.cellValue) {
value = toSafeLocalDateStr(this.cellValue)
} else {
value = '-'
}
// let value
// if (this.cellValue) {
// value = toSafeLocalDateStr(this.cellValue)
// } else {
// value = '-'
// }
// const locale = this.$i18n.locale
// const value = dt.toLocaleString(locale, { hourCycle: 'h23' })
// debug(this.$i18n.locale)
return {
value: value
}
// return {
// value: value
// }
// return {
// value: `${year}-${month}-${date} ${hour}:${minutes}:${seconds}`
// }
return {}
},
computed: {
dateValue() {
if (this.cellValue) {
return toSafeLocalDateStr(this.cellValue)
} else {
return '-'
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -5,6 +5,7 @@
:disabled="disabled"
:type="col.type || 'info'"
class="detail"
:class="{ 'clicked': linkClicked }"
@click="goDetail"
>
<slot>
@@ -30,6 +31,7 @@ export default {
routeQuery: null,
can: true,
openInNewPage: false,
removeColorOnClick: false,
getTitle({ col, row, cellValue }) {
return cellValue
},
@@ -43,6 +45,7 @@ export default {
data() {
const formatterArgs = Object.assign(this.formatterArgsDefault, this.col.formatterArgs)
return {
linkClicked: false,
formatterArgs: formatterArgs
}
},
@@ -100,6 +103,7 @@ export default {
methods: {
goDetail() {
if (this.formatterArgs.openInNewPage) {
this.linkClicked = this.formatterArgs.removeColorOnClick
const { href } = this.$router.resolve(this.detailRoute)
window.open(href, '_blank')
} else {
@@ -125,6 +129,11 @@ export default {
font-size: 13px;
}
.clicked,
.el-link.el-link--info.clicked {
color: inherit !important;
}
.icon {
width: 28px;
height: 28px;

View File

@@ -64,7 +64,7 @@ export default {
}
return text
}
return '-'
return this.items?.distribution || '-'
}
}
}

View File

@@ -25,7 +25,14 @@
</div>
</div>
</a>
<a class="edit-btn" style="padding-left: 5px" @click="showDialog = true"> <i class="fa fa-edit" /></a>
<a
v-if="formatterArgs.showEditBtn"
:class="[{ 'disabled-link': this.$store.getters.currentOrgIsRoot },'edit-btn']"
style="padding-left: 5px"
@click="showDialog = true"
>
<i class="fa fa-edit" />
</a>
<Dialog
v-if="showDialog"
:title="$tc('labels.BindLabel')"
@@ -96,7 +103,8 @@ export default {
getLabels(cellValue) {
return cellValue
},
config: {}
config: {},
showEditBtn: true
}
}
}
@@ -261,4 +269,11 @@ export default {
.tag-tip {
margin-top: 10px;
}
.disabled-link {
pointer-events: none;
color: grey;
cursor: default;
text-decoration: none;
}
</style>

View File

@@ -0,0 +1,64 @@
<template>
<div v-if="display">
<el-switch v-model="value" @change="onChange" />
</div>
<span v-else>-</span>
</template>
<script>
import BaseFormatter from './base.vue'
export default {
name: 'SwitchFormatter',
extends: BaseFormatter,
props: {
formatterArgsDefault: {
type: Object,
default() {
return {
getPatchUrl(row) {
return ''
},
getPatchData(row) {
return {}
},
isDisplay(row) {
return true
}
}
}
}
},
data() {
return {
formatterArgs: Object.assign(this.formatterArgsDefault, this.col.formatterArgs),
value: this.cellValue
}
},
computed: {
patchUrl() {
return this.formatterArgs.getPatchUrl(this.row)
},
patchData() {
return this.formatterArgs.getPatchData(this.row)
},
display(row) {
return this.formatterArgs.isDisplay(this.row)
}
},
methods: {
onChange(val) {
this.$axios.patch(this.patchUrl, this.patchData).then(res => {
this.$message.success(this.$t('common.updateSuccessMsg'))
}).catch(err => {
this.value = !val
this.$message.error(this.$t('common.updateErrorMsg' + ' ' + err))
})
}
}
}
</script>
<style scoped>
</style>

View File

@@ -16,6 +16,8 @@ import ObjectRelatedFormatter from './ObjectRelatedFormatter.vue'
import TwoTabFormatter from './TwoTabFormatter.vue'
import ProtocolsFormatter from './ProtocolsFormatter.vue'
import TagChoicesFormatter from './TagChoicesFormatter.vue'
import SwitchFormatter from './SwitchFormatter.vue'
import AccountInfoFormatter from './AccountInfoFormatter.vue'
export default {
DetailFormatter,
@@ -35,7 +37,9 @@ export default {
TwoTabFormatter,
ProtocolsFormatter,
TagChoicesFormatter,
LabelsFormatter
LabelsFormatter,
SwitchFormatter,
AccountInfoFormatter
}
export {
@@ -56,5 +60,7 @@ export {
TwoTabFormatter,
ProtocolsFormatter,
TagChoicesFormatter,
LabelsFormatter
LabelsFormatter,
SwitchFormatter,
AccountInfoFormatter
}

View File

@@ -10,14 +10,14 @@
<el-tag
v-for="(v, k) in filterTags"
:key="k"
:disable-transitions="true"
:name="k"
class="filter-tag"
closable
size="small"
class="filter-tag"
type="info"
:disable-transitions="true"
@close="handleTagClose(k)"
@click="handleTagClick(v,k)"
@close="handleTagClose(k)"
>
<strong v-if="v.label">{{ v.label + ':' }}</strong>
<span v-if="v.valueLabel">{{ v.valueLabel }}</span>
@@ -27,14 +27,14 @@
<el-input
ref="SearchInput"
v-model="filterValue"
:placeholder="placeholder"
class="search-input"
:class="options.length < 1 ? 'search-input2': ''"
:placeholder="placeholder"
:validate-event="false"
class="search-input"
suffix-icon="el-icon-search"
@blur="focus = false"
@focus="focus = true"
@change="handleConfirm"
@focus="focus = true"
@keyup.enter.native="handleConfirm"
@keyup.delete.native="handleDelete"
/>
@@ -48,7 +48,8 @@ export default {
props: {
config: {
type: Object,
default: () => {}
default: () => {
}
},
options: {
type: Array,
@@ -56,7 +57,7 @@ export default {
},
getUrlQuery: {
type: Boolean,
default: () => true
default: () => false
},
default: {
type: Object,
@@ -122,6 +123,12 @@ export default {
},
deep: true
},
filterTags: {
handler() {
this.$emit('tag-search', this.filterMaps)
},
deep: true
},
filterValue(newValue, oldValue) {
if (newValue === '' && oldValue !== '') {
this.emptyCount = 1
@@ -210,11 +217,6 @@ export default {
...asFilterTags,
...routeFilter
}
if (Object.keys(this.filterTags).length > 0) {
setTimeout(() => {
return this.$emit('tagSearch', this.filterMaps)
}, 400)
}
},
getValueLabel(key, value) {
for (const field of this.options) {
@@ -252,7 +254,7 @@ export default {
if (this.getUrlQuery) {
this.checkUrlFields(evt)
}
this.$emit('tagSearch', this.filterMaps)
// this.$emit('tagSearch', this.filterMaps)
return true
},
handleDelete() {
@@ -284,7 +286,7 @@ export default {
valueLabel: this.valueLabel
}
this.$set(this.filterTags, this.filterKey, tag)
this.$emit('tagSearch', this.filterMaps)
// this.$emit('tagSearch', this.filterMaps)
// 修改查询参数时改变url中保存的参数
if (this.getUrlQuery) {
@@ -342,70 +344,77 @@ export default {
</script>
<style lang="scss" scoped>
.filter-field {
display: flex;
align-items: center;
min-width: 198px;
border: 1px solid #dcdee2;
border-radius: 3px;
background-color:#fff;
.filter-field {
display: flex;
align-items: center;
min-width: 198px;
border: 1px solid #dcdee2;
border-radius: 3px;
background-color: #fff;
}
.search-input >>> .el-input__suffix {
cursor: pointer;
}
.search-input2 >>> .el-input__inner {
text-indent: 5px;
}
.search-input >>> .el-input__inner {
/*max-width:inherit !important;*/
}
max-width: 200px;
border: none;
padding-left: 5px;
}
.el-input >>> .el-input__inner{
border: none !important;
font-size: 13px;
}
.search-input > > > .el-input__suffix {
cursor: pointer;
}
.filterTitle {
padding-right: 2px;
line-height: 100%;
text-align: center;
flex-shrink: 0;
border-collapse: separate;
box-sizing: border-box;
color: rgb(96, 98, 102);
display: inline;
font-size: 13px;
height: auto;
}
.filter-tag{
margin: 2px 4px 2px 0;
}
.el-icon--right{
margin-left: 5px;
margin-right: 5px;
}
a {
color: #000;
}
.search-input2 > > > .el-input__inner {
text-indent: 5px;
}
.filter-field >>> .el-cascader .el-input--suffix .el-input__inner {
padding-right: 20px;
}
.search-input > > > .el-input__inner {
/*max-width:inherit !important;*/
.filter-field >>> .el-cascader .el-input input {
width: 0;
border: none;
}
max-width: 200px;
border: none;
padding-left: 5px;
}
.filter-field >>> .el-input__inner {
height: 30px;
}
.el-input > > > .el-input__inner {
border: none !important;
font-size: 13px;
}
.el-cascader-menu__wrap {
height: inherit;
}
.filterTitle {
padding-right: 2px;
line-height: 100%;
text-align: center;
flex-shrink: 0;
border-collapse: separate;
box-sizing: border-box;
color: rgb(96, 98, 102);
display: inline;
font-size: 13px;
height: auto;
}
.filter-tag {
margin: 2px 4px 2px 0;
}
.el-icon--right {
margin-left: 5px;
margin-right: 5px;
}
a {
color: #000;
}
.filter-field > > > .el-cascader .el-input--suffix .el-input__inner {
padding-right: 20px;
}
.filter-field > > > .el-cascader .el-input input {
width: 0;
border: none;
}
.filter-field > > > .el-input__inner {
height: 30px;
}
.el-cascader-menu__wrap {
height: inherit;
}
</style>

View File

@@ -91,7 +91,7 @@ export default {
let treeUrl
this.loading = true
if (refresh && this.treeSetting.treeUrl.indexOf('/perms/') !== -1 &&
this.treeSetting.treeUrl.indexOf('rebuild_tree') === -1
this.treeSetting.treeUrl.indexOf('rebuild_tree') === -1
) {
treeUrl = (this.treeSetting.treeUrl.indexOf('?') === -1)
? `${this.treeSetting.treeUrl}?rebuild_tree=1`
@@ -162,11 +162,15 @@ export default {
</span>`
if (rootNode) {
const $rootNodeRef = $('#' + rootNode.tId + '_a')
$rootNodeRef.css({ 'width': 'calc(100% - 68px)', 'overflow': 'hidden', 'text-overflow': 'ellipsis' })
$rootNodeRef.after(icons)
}
},
async refresh() {
this.treeSearchValue = ''
if (this.treeSetting?.callback?.beforeRefresh) {
this.treeSetting.callback.beforeRefresh()
}
if (this.treeSetting?.callback?.refresh) {
await this.treeSetting.callback.refresh()
}
@@ -387,6 +391,8 @@ div.rMenu li {
text-shadow: none;
top: 100%;
z-index: 1000;
height: 300px;
overflow: auto;
}
.ztree ::v-deep .fa {

View File

@@ -39,7 +39,7 @@ export default {
showRenameBtn: false,
drag: {
isCopy: false,
isMove: false
isMove: true
}
},
callback: {

View File

@@ -18,14 +18,15 @@
/>
</el-col>
<el-col v-show="isShow" :span="span">
<VueMarkdown class="result-html" :source="iValue" :show="true" :html="true" />
<VueMarkdown class="result-html" :source="sanitizedValue" :html="false" :show="true" />
</el-col>
</el-row>
<VueMarkdown v-else class="source" :source="iValue" :html="true" />
<VueMarkdown v-else class="source" :html="false" :source="sanitizedValue" />
</div>
</template>
<script>
import DOMPurify from 'dompurify'
import VueMarkdown from 'vue-markdown'
import 'github-markdown-css/github-markdown-light.css'
@@ -56,6 +57,17 @@ export default {
iValue: this.value
}
},
computed: {
sanitizedValue() {
// 转义特殊字符
let content = this.iValue.replace(/\\/g, '\\\\').replace(/\$/g, '\\$')
// 使用 DOMPurify 进行 XSS 过滤
content = DOMPurify.sanitize(content)
return content
}
},
mounted() {
this.$nextTick(() => {
this.resizeObserver = new ResizeObserver(entries => {

View File

@@ -25,6 +25,7 @@
import 'xterm/css/xterm.css'
import { Terminal } from 'xterm'
import { FitAddon } from 'xterm-addon-fit'
import { downloadText } from '@/utils/common'
export default {
name: 'Term',
@@ -47,6 +48,7 @@ export default {
fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
lineHeight: 1.2,
fontSize: 13,
scrollback: 9999999,
rightClickSelectsWord: true,
theme: {
background: '#fff',
@@ -75,6 +77,16 @@ export default {
callback: () => {
this.xterm.reset()
}
},
{
tip: this.$tc('common.Export'),
icon: 'download',
callback: () => {
this.xterm.selectAll()
const text = this.xterm.getSelection()
const filename = `${this.$route.query?.type}_${this.$route.query?.taskId}.log`
downloadText(text, filename)
}
}
]
}

View File

@@ -27,6 +27,20 @@ export default {
hour: 'numeric', minute: 'numeric', hour12: true
}
},
'zh_hant': {
short: {
year: 'numeric', month: 'short', day: 'numeric'
},
medium: {
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit',
hourCycle: 'h23', hour12: false
},
long: {
year: 'numeric', month: 'short', day: 'numeric',
hour: 'numeric', minute: 'numeric', hour12: true
}
},
'ja': {
short: {
year: 'numeric', month: 'short', day: 'numeric'

View File

@@ -10,7 +10,12 @@ Vue.use(VueI18n)
const cookieLang = VueCookie.get('django_language')
const browserLang = navigator.systemLanguage || navigator.language
let lang = cookieLang || browserLang || 'zh'
lang = lang.slice(0, 2)
if (lang === 'zh-hant') {
lang = 'zh_hant'
} else {
lang = lang.slice(0, 2)
}
const i18n = new VueI18n({
locale: lang,
fallbackLocale: 'en',

View File

@@ -1,6 +1,8 @@
{
"": "",
"accounts": {
"AccountName": "Account name",
"AccountBatchUpdate": "Batch update(same type)",
"SuFrom": "Su from",
"GenerateSuccessMsg": "Accounts generated successfully",
"GenerateAccounts": "Regenerate accounts",
@@ -31,6 +33,8 @@
"AccountGatherList": "Gather task"
},
"AccountChangeSecret": {
"OldSecret": "Old secret",
"NewSecret": "New secret",
"Result": "Result",
"ParamsHelpText": "The change secret parameter settings are currently only effective for assets with a platform type of host.",
"ExecutionTimes": "Execution times",
@@ -77,6 +81,7 @@
"ExecutionDetail": "Execution detail",
"Name": "Name",
"Retry": "Retry",
"BatchRetry": "Batch retry",
"Timer": "Timed execution",
"Detail": "Detail",
"TimeDelta": "Time delta",
@@ -109,7 +114,7 @@
"ExecutionList": "Execution list",
"Reason": "Reason",
"AccountBackup": "Account backup",
"RecipientHelpText": "If both recipients A and B are set, the account key will be split into two parts: front and back",
"RecipientHelpText": "If both recipients A and B are set, the account key will be split into two parts: front and back. If the user has not set an encryption password, please go to Personal Information ->Preferences ->Set Encryption Password",
"RecipientServer": "Receiving server"
},
"DynamicUsername": "Dynamic username",
@@ -124,7 +129,11 @@
"AddAccountResult": "Add account result",
"AutoPush": "Auto Push",
"GeneralAccounts": "General Accounts",
"VirtualAccounts": "Virtual Accounts"
"VirtualAccounts": "Virtual Accounts",
"AccountDeleteConfirmMsg": "Delete account, do you want to continue?",
"Test": "Test",
"QuickTest": "Quick Test",
"BulkVerify": "Bulk Verify Connectivity"
},
"acl": {
"CommandFilterACLHelpMsg": "You can control whether commands can be executed on assets. Based on the rules, certain commands can be allowed while others are prohibited.",
@@ -254,6 +263,7 @@
"SecretType": "Secret type",
"PrivilegedTemplate": "Privileged",
"InitialDeploy": "Initial deploy",
"OnlyInitialDeploy": "Initial Setup",
"Address": "Address",
"PrivateKey": "Private key",
"Secret": "Secret",
@@ -475,7 +485,11 @@
"WebUpdate": "Update asset - Web",
"DatabaseUpdate": "Update asset - Database",
"GPTCreate": "Create asset - GPT",
"AppletHostDomainHelpText": "These domains are in System Organization"
"AppletHostDomainHelpText": "These domains are in System Organization",
"OracleDBNameHelpText": "Tips: Enter the Oracle database's SID or Service Name",
"Remove": "Remove",
"AddGatewayInDomain": "Add gateway",
"AddAssetInDomain": "Add asset"
},
"audits": {
"ChangeField": "Change field",
@@ -553,6 +567,7 @@
"Automations": "Automation",
"Sync": "Sync",
"Deploy": "Deploy",
"Uninstall": "Uninstall",
"Detail": "Detail",
"Selector": "Selector",
"NoContent": "No content",
@@ -706,6 +721,7 @@
"BatchActivate": "Batch activate",
"SyncSelected": "Sync selected",
"bulkDeploy": "Bulk deploy",
"BulkVerify": "Bulk verify",
"bulkDeleteErrorMsg": "Bulk delete failed: ",
"bulkDeleteSuccessMsg": "Bulk delete success",
"bulkRemoveErrorMsg": "Bulk remove failed: ",
@@ -746,6 +762,7 @@
"Prompt": "Prompt",
"InputMessage": "Input message...",
"CollapseSidebar": "Collapse the sidebar",
"crontabDiffError": "Please ensure that the interval for scheduled execution is no less than ten minutes!",
"introduction": {
"ConceptTitle": "🤔 Python interpreter",
"ConceptContent": "I want you to act like a Python interpreter. I will give you Python code, and you will execute it. Do not provide any explanations. Do not respond with anything except the output of the code. ",
@@ -968,29 +985,30 @@
"LoginCount": "Login count",
"LoginOverview": "Sessions overview",
"LoginTo": "Login to",
"LoginUsers": "Active accounts",
"ActiveUsers": "Active users",
"Monthly": "Monthly",
"CurrentConnections": "Current connections",
"TodayFailedConnections": "Connections failed today",
"CurrentConnectionUsers": "Current connection users",
"TodayFailedConnections": "Number of failed sessions today",
"OnlineSessions": "Online sessions",
"OnlineUserDevices": "Online user devices",
"RealTimeData": "Real-time data",
"UserAssetActivity": "Account/Asset activity",
"UserData": "Account data",
"LoginUserToday": "Login account today",
"UserAssetActivity": "User/asset activity status",
"UserData": "User data",
"LoginUserToday": "Login accounts today",
"AssetData": "Asset data",
"LoginAssetToday": "Active assets today",
"WeekAdd": "New this week",
"ProportionOfAssetTypes": "Proportion of asset types",
"Proportion": "Proportion",
"LoginUserRanking": "Login account ranking",
"ActiveAssetRanking": "Login asset ranking",
"LoginUserRanking": "Session user ranking",
"ActiveAssetRanking": "Session asset ranking",
"AssetName": "Asset name",
"NumberOfVisits": "Number of visits",
"ranking": "Ranking",
"Today": "Today",
"Last7Days": "Last 7 days",
"Last30Days": "Last30 days",
"Last7Days": "Last 7d",
"Last30Days": "Last 30d",
"OnlineUsers": "Online accounts",
"ConnectUsers": "Connect accounts",
"Num": "Num",
@@ -1008,13 +1026,14 @@
"BatchCommandNotExecuted": "Batch command not executed",
"ExecuteFailedCommand": "Execute failed command",
"SessionTrend": "Session trend",
"SessionConnectTrend": "Session connection trends",
"UserLoginTrend": "Account login trend",
"TimesWeekUnit": "times/week",
"TopAssetsOfWeek": "Top assets of week",
"TopUsersOfWeek": "Top user of week",
"User": "User",
"UserRatio": "User Ratio",
"UsersTotal": "Accounts total",
"UsersTotal": "User total",
"Weekly": "Weekly",
"TotalJobFailed": "Total job failed",
"TotalJobRunning": "Total job running",
@@ -1193,7 +1212,9 @@
"FileSizeExceedsLimit": "File size exceeds limit",
"runSucceed": "Task executed successfully",
"EnterUploadPath": "Enter the upload path",
"FileNameTooLong": "File name too long"
"FileNameTooLong": "File name too long",
"StopJob": "Stop job",
"StopLogOutput": "Task Canceled: The current task (currentTaskId) has been manually stopped. Since the progress of each task varies, the following is the final execution result of the task. A failed execution indicates that the task has been successfully stopped."
},
"perms": {
"": "",
@@ -1471,7 +1492,7 @@
"TicketsDone": "Ticket Done",
"TemplateUpdate": "Update template",
"Session": "Session",
"Templates": "模版管理",
"Templates": "Templates",
"AssetUserList": "Asset user",
"UserLoginACLUpdate": "Update User Login ACL",
"JobCreate": "Create job",
@@ -1598,7 +1619,8 @@
"remoteAddr": "Remote addr",
"replay": "replay",
"replaySession": "Replay session",
"replayStorage": "Object storage",
"replayStorage": "Replay storage",
"objectStorage": "Object storage",
"riskLevel": "Risk level",
"session": "Session",
"sshPort": "SSH port",
@@ -1757,6 +1779,7 @@
"LDAPServerInfo": "LDAP Server",
"LDAPUser": "LDAP User",
"ChatAI": "Chat ai",
"Example": "Example: {example}",
"InsecureCommandAlert": "Insecure command alert",
"helpText": {
"TempPassword": "For a while, there is a period of 300 seconds, failure immediately after use",
@@ -1908,6 +1931,7 @@
"CheckViewAcceptor": "View more acceptor",
"Assignees": "Assignees",
"Close": "Close",
"CancelTicket": "Cancel Ticket",
"OpenStatus": "Open",
"CloseStatus": "Close",
"Comment": "Comment",
@@ -1978,6 +2002,7 @@
"UpdateNodeAssetHardwareInfo": "Update node asset hardware information"
},
"users": {
"OrgsAndRoles": "Organizations and roles",
"LunaSettingUpdate": "Luna setting",
"KokoSettingUpdate": "Koko setting",
"UserSetting": "User setting",
@@ -2000,7 +2025,7 @@
"Account": "Account",
"Existing": "Existing",
"UserInformation": "User information",
"Authentication": "Account",
"Authentication": "Authentication",
"Comment": "Comment",
"ConfirmPassword": "Confirm password",
"DateExpired": "Date expired",
@@ -2010,6 +2035,7 @@
"setWeCom": "Set wecom login",
"setDingTalk": "Set dingtalk login",
"setFeiShu": "Set feishu login",
"setLark": "Set lark login",
"setSlack": "Set Slack login",
"DatePasswordUpdated": "Date password updated",
"DescribeOfGuide": "Welcome to JumpServer. Click here for more information",
@@ -2110,7 +2136,9 @@
"passwordWillExpiredPrefixMsg": "The password will expire in ",
"passwordWillExpiredSuffixMsg": " days.Please change your password as soon as possible.",
"dateLastLogin": "Date last login",
"AddAllMembersWarningMsg": "Are you sure you want to add all members"
"AddAllMembersWarningMsg": "Are you sure you want to add all members?",
"disallowSelfUpdateFields": "Not allowed to modify the current fields oneself",
"GlobalDisableMfaMsg": "Global enforcement has been enabled"
},
"notifications": {
"MessageType": "Message Type",
@@ -2232,6 +2260,7 @@
"ZStack": "ZStack",
"GCP": "Google Cloud Platform",
"UCloud": "UCloud Platform",
"Volcengine": "Volcengine",
"FC": "Fusion Compute",
"AWS_China": "AWS(China)",
"AWS_Int": "AWS(International)",
@@ -2245,6 +2274,8 @@
"HostnameStrategy": "Used to produce the asset hostname. For example, 1. Instance name (instanceDemo)2. Instance name and Partial IP (instanceDemo-250.1)",
"IsAlwaysUpdate": "Keep assets up to date",
"FullySynchronous": "Assets fully synchronized",
"ReleaseAssets": "Release assets",
"ReleaseAssetsHelpTips": "Whether to automatically delete assets synchronized through this task and released on the cloud at the end of the task",
"AccountCreate": "Create account",
"AccountList": "Account list",
"AccountUpdate": "Update account",
@@ -2385,7 +2416,7 @@
"Template": {
"Template": "Template"
},
"Beian": "Registration"
"Footer": "Footer"
},
"applets": {
"PublishStatus": "Publish status",

View File

@@ -29,7 +29,8 @@ actions_display_mapper = {
}
langs_display_map = {
'en': '英文',
'ja': '日文'
'ja': '日文',
'zh_Hant': '繁体中文',
}
@@ -114,7 +115,7 @@ if __name__ == '__main__':
'action', type=str, choices=("diff", "apply"),
)
parser.add_argument(
'langs', type=str, choices=("en", "ja"), nargs='*'
'langs', type=str, choices=("en", "ja", "zh_Hant"), nargs='*'
)
args = parser.parse_args()
action = args.action

View File

@@ -1,7 +1,9 @@
import zhLocale from 'element-ui/lib/locale/lang/zh-CN'
import zhTWLocale from 'element-ui/lib/locale/lang/zh-TW'
import enLocale from 'element-ui/lib/locale/lang/en'
import jaLocale from 'element-ui/lib/locale/lang/ja'
import zh from './zh.json'
import zhHant from './zh_Hant.json'
import en from './en.json'
import ja from './ja.json'
@@ -10,6 +12,10 @@ export default {
...zhLocale,
...zh
},
zh_hant: {
...zhTWLocale,
...zhHant
},
en: {
...enLocale,
...en

View File

@@ -1,6 +1,8 @@
{
"": "",
"accounts": {
"AccountName": "Account name",
"AccountBatchUpdate": "ロット更新(同じタイプです)",
"Accounts": "アカウント",
"SuFrom": "から",
"AccountTemplateUpdateSecretHelpText": "アカウントリストには、テンプレートで作成されたアカウントが表示されます。暗号文を更新すると、テンプレートで作成されたアカウントの暗号文が更新されます。",
@@ -31,6 +33,8 @@
"AccountGatherList": "収集タスク"
},
"AccountChangeSecret": {
"OldSecret": "古い秘密",
"NewSecret": "新しい秘密",
"Result": "結果",
"ParamsHelpText": "改密パラメータの設定は、プラットフォームの種類がホストである資産に対してのみ有効です。",
"ExecutionTimes": "実行時間",
@@ -77,6 +81,7 @@
"ExecutionDetail": "実行の詳細",
"Name": "名前",
"Retry": "リトライ",
"BatchRetry": "一括リトライ",
"Timer": "時限実行",
"Detail": "詳細",
"TimeDelta": "営業時間",
@@ -109,14 +114,14 @@
"ExecutionList": "実行リスト",
"Reason": "理由",
"AccountBackup": "アカウントのバックアップ",
"RecipientHelpText": "受信者A Bが設定されている場合、アカウントの鍵は前後2つに分割されます",
"RecipientHelpText": "受信者A Bが設定されている場合、アカウントの鍵は前後2つに分割されます。ユーザーが暗号化パスワードを設定していない場合-個人情報->プリファレンス設定で暗号化パスワードを設定してください",
"RecipientServer": "受信サーバー"
},
"DynamicUsername": "動的ユーザー名",
"AutoCreate": "自動作成",
"AccountExportTips": "エクスポート情報には機密情報を含むアカウント暗号文が含まれており、エクスポートされたフォーマットは暗号化されたzipファイルです暗号化パスワードが設定されていない場合は、個人情報にファイル暗号化パスワードを設定してください。",
"TaskID": "タスク ID",
"AccountTemplate": "账号模",
"AccountTemplate": "账号模",
"Sync": "同期",
"SyncDelete": "同期削除",
"BulkSyncDelete": "一括同期削除",
@@ -124,7 +129,11 @@
"AddAccountResult": "账号批量添加结果",
"AutoPush": "自動プッシュ",
"GeneralAccounts": "一般アカウント",
"VirtualAccounts": "仮想アカウント"
"VirtualAccounts": "仮想アカウント",
"AccountDeleteConfirmMsg": "アカウントを削除します,続行しますか?",
"Test": "テスト",
"QuickTest": "クイックテスト",
"BulkVerify": "一括接続性テスト"
},
"acl": {
"CommandFilterACLHelpMsg": "コマンドフィルタリングを使用すると、コマンドがアセット上で実行されるかどうかを制御できます。ルールに基づいて、特定のコマンドは許可され、他のコマンドは禁止されることがあります。",
@@ -457,6 +466,7 @@
"Token": "トークン",
"GatewayList": "ゲートウェイ一覧",
"InitialDeploy": "初期展開",
"OnlyInitialDeploy": "初期設定のみ",
"PrivateKey": "鍵",
"Category": "カテゴリー",
"SSHPort": "SSH ポート",
@@ -475,7 +485,11 @@
"WebUpdate": "資産の更新 - Web",
"DatabaseUpdate": "資産の更新 - データベース",
"GPTCreate": "資産の作成 - GPT",
"AppletHostDomainHelpText": "これらのドメインはシステム組織にあります"
"AppletHostDomainHelpText": "これらのドメインはシステム組織にあります",
"OracleDBNameHelpText": "Oracle データベースの SID またはサービス名を入力してください",
"Remove": "取り除く",
"AddGatewayInDomain": "ゲートウェイの追加",
"AddAssetInDomain": "アセットの追加"
},
"audits": {
"ChangeField": "フィールドを変更します",
@@ -703,6 +717,7 @@
"SyncSuccessMsg": "同期に成功しました",
"SyncSelected": "選択した同期",
"bulkDeploy": "一括デプロイ",
"BulkVerify": "一括テスト",
"bulkSyncErrorMsg": "一括同期に失敗しました:",
"bulkDeleteErrorMsg": "一括削除に失敗しました:",
"bulkDeleteSuccessMsg": "一括削除に成功しました",
@@ -747,6 +762,7 @@
"Prompt": "ヒント",
"InputMessage": "メッセージの入力...",
"CollapseSidebar": "サイドバーを閉じる",
"crontabDiffError": "定期実行の間隔が10分以上であることをご確認ください",
"introduction": {
"ConceptTitle": "🤔 Python インタプリタ",
"IdeaContent": "私はあなたに Linux ターミナルの役割を果たしてもらいたいです。私はコマンドを入力し、ターミナルが表示すべき内容を返してもらいます。あなたにはユニークなコードブロック内でのみターミナルの出力に応答してほしいです。説明は書かないでください。私が何かを英語で伝える必要がある場合、中括弧で囲んでテキストを入れます {コメントテキスト}。",
@@ -876,6 +892,7 @@
"failedConditions": "条件に達していない結果!"
},
"Deploy": "配備",
"Uninstall": "アンインストールする",
"Publish": "リリース",
"Icon": "アイコン",
"Automations": "オートメーション",
@@ -970,22 +987,23 @@
"LoginCount": "ログイン回数",
"LoginOverview": "セッション統計",
"LoginTo": "ログインしました",
"LoginUsers": "アクティブなアカウント",
"ActiveUsers": "アクティブユーザー",
"Monthly": "月ごと",
"CurrentConnections": "現在の接続数",
"TodayFailedConnections": "今日の接続に失敗しました",
"CurrentConnectionUsers": "現在のセッションユーザーの数",
"TodayFailedConnections": "今日の失敗したセッションの数",
"OnlineSessions": "オンラインセッション",
"RealTimeData": "リアルタイムデータ",
"UserAssetActivity": "アカウント/資産のアクティブ化",
"UserData": "アカウントデータ",
"LoginUserToday": "今日のログインアカウント数",
"UserAssetActivity": "ユーザー/アセットのアクティビティステータス",
"UserData": "ユーザーデータ",
"LoginUserToday": "今日のログインユーザー数",
"AssetData": "資産データ",
"LoginAssetToday": "今日のアクティブ資産数",
"WeekAdd": "今週の追加",
"ProportionOfAssetTypes": "資産タイプの割合",
"Proportion": "占有率",
"LoginUserRanking": "ログインアカウントランキング",
"ActiveAssetRanking": "ログイン資産ランキング",
"LoginUserRanking": "セッションユーザーランキング",
"ActiveAssetRanking": "セッションアセットランキング",
"AssetName": "資産名",
"NumberOfVisits": "アクセス回数",
"ranking": "ランキング",
@@ -1009,13 +1027,14 @@
"BatchCommandNotExecuted": "未実行コマンド",
"ExecuteFailedCommand": "失敗コマンドの実行",
"SessionTrend": "セッショントレンド",
"SessionConnectTrend": "セッション接続の傾向",
"UserLoginTrend": "アカウントログイントレンド",
"TimesWeekUnit": "回/週",
"TopAssetsOfWeek": "週間資産TOP10",
"TopUsersOfWeek": "週ユーザーTOP10",
"User": "ユーザー",
"UserRatio": "ユーザー比率統計",
"UsersTotal": "アカウント総数",
"UsersTotal": "総ユーザー数",
"Weekly": "週ごと"
},
"ops": {
@@ -1191,7 +1210,9 @@
"FileSizeExceedsLimit": "ファイルサイズが制限を超えています",
"runSucceed": "タスクが成功しました",
"EnterUploadPath": "アップロードパスを入力してください",
"FileNameTooLong": "ファイル名が長すぎます"
"FileNameTooLong": "ファイル名が長すぎます",
"StopJob": "ジョブを停止",
"StopLogOutput": "Task Canceled現在のタスクcurrentTaskIdは手動で停止されました。各タスクの進行状況が異なるため、以下はタスクの最終実行結果です。実行が失敗した場合は、タスクが正常に停止されました。"
},
"perms": {
"": "",
@@ -1592,7 +1613,8 @@
"remoteAddr": "リモートアドレス",
"replay": "リプレイ",
"replaySession": "再生セッション",
"replayStorage": "オブジェクトストレージ",
"replayStorage": "ビデオ保存",
"objectStorage": "オブジェクトストレージ",
"riskLevel": "リスクレベル",
"session": "会話",
"sshPort": "SSHポート",
@@ -1763,6 +1785,7 @@
"LDAPServerInfo": "LDAPサーバー",
"LDAPUser": "LDAPユーザー",
"ChatAI": "チャットAI",
"Example": "例: {example}",
"helpText": {
"TempPassword": "一時パスワードの有効期間は300秒で、使用後すぐに失効します",
"ApiKeyList": "Api keyを使用してリクエストヘッダに署名します。リクエストのヘッダごとに異なります。使用ドキュメントを参照してください",
@@ -1904,6 +1927,7 @@
"Assignee": "処理者",
"Assignees": "処理待ち",
"Close": "閉じる",
"CancelTicket": "作業指示をキャンセルする",
"OpenStatus": "オン",
"CloseStatus": "閉じる",
"Comment": "備考",
@@ -1969,6 +1993,7 @@
"UpdateNodeAssetHardwareInfo": "ノード資産ハードウェア情報の更新"
},
"users": {
"OrgsAndRoles": "そしきとやくわり",
"LunaSettingUpdate": "Luna 設定更新",
"KokoSettingUpdate": "Koko 設定更新",
"UserSetting": "個人設定",
@@ -2016,6 +2041,7 @@
"setWeCom": "企業のwechat認証を設定する",
"setDingTalk": "ホッチキス認証の設定",
"setFeiShu": "飛書認証を設定する",
"setLark": "Lark認証を設定する",
"setSlack": "Slack認証を設定します",
"HelpText": {
"MFAOfUserFirstLoginPersonalInformationImprovementPage": "アカウントをより安全にするために、マルチファクタ认证を有効にします。 <Br/> 有効にすると、次回のログイン時に多因子認証バインディングプロセスに入ります。また、 (個人情報-> クイック修正-> 多因子設定の変更) もできます。で直接紐付けます",
@@ -2099,7 +2125,9 @@
"passwordExpired": "パスワードが期限切れです",
"passwordWillExpiredPrefixMsg": "パスワードはまもなく",
"passwordWillExpiredSuffixMsg": "期限が切れた後、できるだけ早くパスワードを変更してください。",
"AddAllMembersWarningMsg": "すべてのメンバーを追加してもよろしいですか"
"AddAllMembersWarningMsg": "すべてのメンバーを追加してもよろしいですか",
"disallowSelfUpdateFields": "現在のフィールドを自分で変更することは許可されていません",
"GlobalDisableMfaMsg": "グローバルでの強制が有効になっています"
},
"notifications": {
"MessageType": "メッセージタイプ",
@@ -2226,6 +2254,7 @@
"CTYunPrivate": "天翼プライベート・クラウド",
"GCP": "Googleクラウド",
"UCloud": "UCloudユケデです",
"Volcengine": "Volcengine",
"FC": "Fusion Compute",
"LAN": "ローカルエリアネットワーク",
"AWS_China": "AWS(中国)",
@@ -2241,6 +2270,8 @@
"HostnameStrategy": "資産を生成するためにホスト名。例: 1. インスタンス名 (instanceDemo) 2.インスタンス名と一部IP (下位2桁) (instanceDemo-250.1)",
"IsAlwaysUpdate": "資産は常に最新です",
"FullySynchronous": "資産完全にシンクロします",
"ReleaseAssets": "資産の同期解放",
"ReleaseAssetsHelpTips": "タスクの終了時に、このタスクを介して同期され、クラウド上で解放された資産を自動的に削除するかどうか",
"AccountCreate": "アカウントの作成",
"AccountList": "アカウントリスト",
"AccountUpdate": "アカウントの更新",
@@ -2376,7 +2407,7 @@
"IntervalOfCreateUpdatePage": "単位: 時",
"UsernameOfCreateUpdatePage": "ターゲットホスト上のユーザーのユーザー名存在する場合は、ユーザーパスワードを変更します存在しない場合は、ユーザーを追加してパスワードを設定します"
},
"Beian": "ファイリング"
"Footer": "フッター"
},
"applets": {
"PublishStatus": "投稿ステータス",

View File

@@ -5,7 +5,8 @@ i18n_report_path = '/tmp/abc.json'
lang_paths = {
'cn': 'cn.json',
'en': 'en.json',
'ja': 'ja.json'
'ja': 'ja.json',
'zh_hant': 'zh_Hant.json'
}

View File

@@ -1,12 +1,17 @@
{
"accounts": {
"AccountName": "账号名称",
"AccountBatchUpdate": "批量更新(同类型)",
"BulkVerify": "批量测试可连接性",
"Test": "测试",
"QuickTest": "测试",
"AutoPush": "自动推送",
"GeneralAccounts": "普通账号",
"VirtualAccounts": "虚拟账号",
"Accounts": "账号",
"SuFrom": "切换自",
"SelectAccount": "选择账号",
"AccountTemplateUpdateSecretHelpText": "账号列表展示通过模创建的账号。更新密文时,会更新通过模所创建账号的密文。",
"AccountTemplateUpdateSecretHelpText": "账号列表展示通过模创建的账号。更新密文时,会更新通过模所创建账号的密文。",
"GenerateSuccessMsg": "账号生成成功",
"GenerateAccounts": "重新生成账号",
"UpdateSecret": "更新密文",
@@ -17,7 +22,7 @@
"AddAccountResult": "账号批量添加结果",
"AccountPolicy": "账号策略",
"BulkCreateStrategy": "创建时对于不符合要求的账号,如:密钥类型不合规,唯一键约束,可选择以上策略。",
"AccountTemplate": "账号模",
"AccountTemplate": "账号模",
"HistoryDate": "日期",
"SameTypeAccountTip": "相同用户名、密钥类型的账号已存在",
"TaskID": "任务 ID",
@@ -30,6 +35,7 @@
"PleaseClickLeftAssetToViewGatheredUser": "收集用户列表,点击左侧资产进行查看",
"AutoCreate": "自动创建",
"AccountExportTips": "导出信息中包含账号密文涉及敏感信息导出的格式为一个加密的zip文件若没有设置加密密码请前往个人信息中设置文件加密密码。",
"AccountDeleteConfirmMsg": "删除账号,是否继续?",
"AccountPush": {
"WindowsPushHelpText": "windows 资产暂不支持推送密钥",
"AccountPushList": "账号推送",
@@ -49,10 +55,12 @@
"MailRecipient": "邮件收件人",
"IsSuccess": "是否成功",
"Reason": "原因",
"RecipientHelpText": "若收件人 A B 都设置,账号的密钥将被拆分成前后两部分",
"RecipientHelpText": "若收件人 A B 都设置,账号的密钥将被拆分成前后两部分。如用户未设置加密密码 - 请前往个人信息 -> 偏好设置中设置加密密码",
"RecipientServer": "接收服务器"
},
"AccountChangeSecret": {
"OldSecret": "原密钥",
"NewSecret": "新密钥",
"PasswordRule": "密码生成规则",
"ParamsHelpText": "改密参数设置,目前仅对平台种类为主机的资产生效。",
"ContainAttachment": "含附件",
@@ -105,6 +113,7 @@
"RegularlyPerform": "定期执行",
"Result": "结果",
"Retry": "重试",
"BatchRetry": "批量重试",
"Success": "成功",
"TaskList": "任务记录",
"TimeDelta": "运行时间",
@@ -271,11 +280,12 @@
"Category": "类别",
"AccountDetail": "账号详情",
"Accounts": "账号列表",
"SelectTemplate": "选择模",
"SelectTemplate": "选择模",
"InAssetDetail": "在资产详情中更新账号信息",
"SecretType": "密文类型",
"PrivilegedTemplate": "特权的",
"InitialDeploy": "初始化部署",
"InitialDeploy": "初始化安装部署",
"OnlyInitialDeploy": "仅初始化配置",
"Address": "地址",
"PrivateKey": "密钥",
"Secret": "密码",
@@ -464,7 +474,11 @@
"NoSQLProtocol": "非关系数据库",
"OtherProtocol": "其它协议",
"PasswordOrToken": "密码 / 令牌",
"AppletHostDomainHelpText": "这里的网域属于 System 组织"
"AppletHostDomainHelpText": "这里的网域属于 System 组织",
"OracleDBNameHelpText": "提示:填写 Oracle 数据库的SID或服务名称Service Name",
"Remove": "移除",
"AddGatewayInDomain": "添加网关",
"AddAssetInDomain": "添加资产"
},
"audits": {
"ChangeField": "变更字段",
@@ -588,6 +602,7 @@
"Automations": "自动化",
"Sync": "同步",
"Deploy": "部署",
"Uninstall": "卸载",
"Detail": "详情",
"Selector": "选择器",
"NoContent": "暂无内容",
@@ -645,8 +660,8 @@
"actionsTips": "各个权限作用协议不尽相同,点击权限后面的图标查看",
"TableColSettingInfo": "请选择您想显示的列表详细信息。",
"Add": "添加",
"TemplateAdd": "模添加",
"TemplateHelpText": "选择模添加时,会自动创建资产下不存在的账号并推送",
"TemplateAdd": "模添加",
"TemplateHelpText": "选择模添加时,会自动创建资产下不存在的账号并推送",
"Task": "任务",
"UpdateAssetDetail": "配置更多信息",
"AddSuccessMsg": "添加成功",
@@ -756,6 +771,7 @@
"BatchActivate": "批量激活",
"SyncSelected": "同步所选",
"bulkDeploy": "批量部署",
"BulkVerify": "批量测试",
"bulkDeleteErrorMsg": "批量删除失败: ",
"bulkDeleteSuccessMsg": "批量删除成功",
"bulkRemoveErrorMsg": "批量移除失败: ",
@@ -801,6 +817,7 @@
"InputMessage": "输入消息...",
"EnterMessage": "请输入问题, Enter 发送",
"CollapseSidebar": "收起侧边栏",
"crontabDiffError": "请确保定期执行的时间间隔不少于十分钟!",
"introduction": {
"ConceptTitle": "\uD83E\uDD14 Python 解释器 ",
"ConceptContent": "我想让你像一个 Python 解释器一样行事。我将给你 Python 代码,你将执行它。不要提供任何解释。除了代码的输出,不要用任何东西来回应。",
@@ -959,22 +976,23 @@
"LoginCount": "登录次数",
"LoginOverview": "会话统计",
"LoginTo": "登录了",
"LoginUsers": "活跃账号",
"ActiveUsers": "活跃用户",
"Monthly": "按月",
"CurrentConnections": "当前连接数",
"TodayFailedConnections": "今日连接失败数",
"CurrentConnectionUsers": "当前会话用户数",
"TodayFailedConnections": "今日会话失败数",
"OnlineSessions": "在线会话数",
"RealTimeData": "实时数据",
"UserAssetActivity": "账号/资产活跃情况",
"UserData": "账号数据",
"LoginUserToday": "今日登录账号数",
"UserAssetActivity": "用户/资产活跃情况",
"UserData": "用户数据",
"LoginUserToday": "今日登录用户数",
"AssetData": "资产数据",
"LoginAssetToday": "今日活跃资产数",
"WeekAdd": "本周新增",
"ProportionOfAssetTypes": "资产类型占比",
"Proportion": "占比",
"LoginUserRanking": "登录账号排名",
"ActiveAssetRanking": "登录资产排名",
"LoginUserRanking": "会话用户排名",
"ActiveAssetRanking": "会话资产排名",
"AssetName": "资产名称",
"NumberOfVisits": "访问次数",
"ranking": "排名",
@@ -998,13 +1016,13 @@
"BatchCommandNotExecuted": "未执行批量命令",
"ExecuteFailedCommand": "执行失败命令",
"SessionTrend": "会话趋势",
"UserLoginTrend": "账号登录趋势",
"SessionConnectTrend": "会话连接趋势",
"TimesWeekUnit": "次/周",
"TopAssetsOfWeek": "周资产 TOP10",
"TopUsersOfWeek": "周用户 TOP10",
"User": "用户",
"UserRatio": "用户占比统计",
"UsersTotal": "账号总数",
"UsersTotal": "用户总数",
"Weekly": "按周"
},
"ops": {
@@ -1180,7 +1198,9 @@
"FileSizeExceedsLimit": "文件大小超出限制",
"runSucceed": "任务执行成功",
"EnterUploadPath": "输入上传路径",
"FileNameTooLong": "文件名太长"
"FileNameTooLong": "文件名太长",
"StopJob": "停止作业",
"StopLogOutput": "Task Canceled当前任务(currentTaskId)已手动停止,由于每个任务的执行进度不一样,下面是任务最终的执行结果,执行失败表示已成功停止任务执行。"
},
"perms": {
"": "",
@@ -1311,9 +1331,9 @@
"Accounts": "账号管理",
"AssetAccount": "账号列表",
"AssetAccountDetail": "账号详情",
"AccountTemplate": "账号模",
"CreateAccountTemplate": "创建账号模",
"UpdateAccountTemplate": "更新账号模",
"AccountTemplate": "账号模",
"CreateAccountTemplate": "创建账号模",
"UpdateAccountTemplate": "更新账号模",
"AssetHistoryAccount": "资产历史账号",
"ApplicationAccount": "应用账号",
"Ticket": "工单",
@@ -1346,7 +1366,7 @@
"BatchCommand": "批量命令",
"BatchScript": "批量脚本",
"Execution": "执行历史",
"Template": "模管理",
"Template": "模管理",
"TicketsTodo": "待办工单",
"TicketsDone": "已办工单",
"TicketsNew": "提交工单",
@@ -1462,10 +1482,10 @@
"TicketDetail": "工单详情",
"TicketCreate": "创建工单",
"Tickets": "工单列表",
"Templates": "模管理",
"TemplateDetail": "模详情",
"TemplateCreate": "创建模",
"TemplateUpdate": "更新模",
"Templates": "模管理",
"TemplateDetail": "模详情",
"TemplateCreate": "创建模",
"TemplateUpdate": "更新模",
"UserCreate": "创建用户",
"UserDetail": "用户详情",
"UserFirstLogin": "首次登录",
@@ -1575,7 +1595,8 @@
"remoteAddr": "远端地址",
"replay": "回放",
"replaySession": "回放会话",
"replayStorage": "对象存储",
"replayStorage": "录像存储",
"objectStorage": "对象存储",
"riskLevel": "风险等级",
"session": "会话",
"sshPort": "SSH端口",
@@ -1752,6 +1773,7 @@
"LDAPServerInfo": "LDAP 服务器",
"LDAPUser": "LDAP 用户",
"ChatAI": "智能问答",
"Example": "例: {example}",
"helpText": {
"TempPassword": "临时密码有效期为 300 秒,使用后立刻失效",
"ApiKeyList": "使用 Api key 签名请求头进行认证,每个请求的头部是不一样的, 相对于 Token 方式,更加安全,请查阅文档使用;<br>为降低泄露风险Secret 仅在生成时可以查看, 每个用户最多支持创建 10 个",
@@ -1891,6 +1913,7 @@
"Assignee": "处理人",
"Assignees": "待处理人",
"Close": "关闭",
"CancelTicket": "取消工单",
"OpenStatus": "审批中",
"CloseStatus": "已完成",
"Comment": "备注",
@@ -1961,11 +1984,12 @@
"PasskeyAddDisableInfo": "你的认证来源是 {source}, 不支持添加 Passkey"
},
"users": {
"OrgsAndRoles": "组织和角色",
"LunaSettingUpdate": "Luna 配置设置",
"KokoSettingUpdate": "Koko 配置设置",
"UserSetting": "偏好设置",
"AllMembers": "全部成员",
"AddAllMembersWarningMsg": "你确定要添加全部成员",
"AddAllMembersWarningMsg": "你确定要添加全部成员",
"UnbindHelpText": "本地用户为此认证来源用户,无法解绑",
"SetStatus": "设置状态",
"Set": "已设置",
@@ -2009,7 +2033,8 @@
"setWeCom": "设置企业微信认证",
"setDingTalk": "设置钉钉认证",
"setFeiShu": "设置飞书认证",
"setSlack": "设置Slack认证",
"setLark": "设置 Lark 认证",
"setSlack": "设置 Slack 认证",
"HelpText": {
"MFAOfUserFirstLoginPersonalInformationImprovementPage": "启用多因子认证,使账号更加安全。<br/> 启用之后您将会在下次登录时进入多因子认证绑定流程;您也可以在(个人信息->快速修改->更改多因子设置)中直接绑定!",
"MFAOfUserFirstLoginUserGuidePage": "为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:设置复杂密码,并启用多因子认证)<br/> 邮箱、手机号、微信等个人信息,仅作为用户认证和平台内部消息通知使用。",
@@ -2091,7 +2116,9 @@
"SetPublicKey": "设置SSH公钥",
"passwordExpired": "密码过期了",
"passwordWillExpiredPrefixMsg": "密码即将在 ",
"passwordWillExpiredSuffixMsg": "天 后过期,请尽快修改您的密码。"
"passwordWillExpiredSuffixMsg": "天 后过期,请尽快修改您的密码。",
"disallowSelfUpdateFields": "不允许自己修改当前字段",
"GlobalDisableMfaMsg": "全局已强制开启"
},
"notifications": {
"MessageType": "消息类型",
@@ -2147,6 +2174,7 @@
"ZStack": "ZStack",
"GCP": "谷歌云",
"UCloud": "UCloud优刻得",
"Volcengine": "火山引擎",
"FC": "Fusion Compute",
"AWS_China": "AWS(中国)",
"AWS_Int": "AWS(国际)",
@@ -2161,6 +2189,8 @@
"HostnameStrategy": "用于生成资产主机名。例如1. 实例名称 (instanceDemo)2. 实例名称和部分IP(后两位) (instanceDemo-250.1)",
"IsAlwaysUpdate": "资产保持最新",
"FullySynchronous": "资产完全同步",
"ReleaseAssets": "同步释放资产",
"ReleaseAssetsHelpTips": "是否在任务结束时,自动删除通过此任务同步下来且已经在云上释放的资产",
"AccountCreate": "创建账户",
"AccountList": "云账号",
"AccountUpdate": "更新账户",
@@ -2208,7 +2238,7 @@
"DeleteReleasedAssets": "删除已释放资产"
},
"Template": {
"Template": "模管理"
"Template": "模管理"
},
"Corporation": "公司",
"Edition": "版本",
@@ -2286,6 +2316,6 @@
"IntervalOfCreateUpdatePage": "单位:时",
"UsernameOfCreateUpdatePage": "目标主机上用户的用户名;如果已️存在,修改用户密码;如果不存在,添加用户并设置密码;"
},
"Beian": "备案"
"Footer": "页脚"
}
}

2318
src/i18n/langs/zh_Hant.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1 @@
<svg t="1687315588323" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3119" width="11" height="11"><path d="M302.545455 116.363636h409.6a27.927273 27.927273 0 0 0 0-55.854545h-409.6a27.927273 27.927273 0 1 0 0 55.854545z m-93.09091 102.4h595.781819a27.927273 27.927273 0 0 0 0-55.854545h-595.781819a27.927273 27.927273 0 1 0 0 55.854545z m670.254546 55.854546h-735.418182c-51.414109 0-93.090909 41.686109-93.090909 93.090909v512c0 51.414109 41.6768 93.090909 93.090909 93.090909h735.418182c51.414109 0 93.090909-41.6768 93.090909-93.090909v-512c0-51.4048-41.6768-93.090909-93.090909-93.090909z m37.236364 594.3296a46.545455 46.545455 0 0 1-46.545455 46.545454h-716.8a46.545455 46.545455 0 0 1-46.545455-46.545454V386.327273a46.545455 46.545455 0 0 1 46.545455-46.545455h716.8a46.545455 46.545455 0 0 1 46.545455 46.545455v482.620509zM602.177164 522.621673l-68.9152-64.474764a4.747636 4.747636 0 0 0-1.405673-1.349818 9.234618 9.234618 0 0 0-1.396364-1.163636 11.776 11.776 0 0 0-3.006836-1.7408c-0.996073-0.577164-1.796655-0.772655-2.802036-1.163637-0.605091-0.195491-1.200873-0.390982-2.010764-0.577163-0.390982 0-0.586473-0.195491-0.996073-0.195491-2.196945-0.577164-4.012218-0.577164-5.008291-0.577164h-0.400291c-0.996073 0-3.006836 0-5.408581 0.577164-0.400291 0-0.595782 0.195491-0.996073 0.195491-0.400291 0.195491-1.005382 0.195491-1.405673 0.390981a15.555491 15.555491 0 0 0-3.006836 1.163637c-0.195491 0-0.195491 0.195491-0.400291 0.195491 0 0.195491-0.195491 0.195491-0.195491 0.195491-1.200873 0.577164-2.606545 1.349818-3.611927 2.122472l-0.195491 0.195491a7.000436 7.000436 0 0 0-2.401746 1.7408l-67.314036 64.660946c-4.803491 4.449745-7.614836 10.426182-7.614836 16.7936-0.195491 6.376727 2.606545 12.744145 7.214545 17.370763s10.826473 7.335564 17.435927 7.335564 13.218909-2.513455 18.031709-7.149382l24.641164-23.738182v266.267928c0 13.312 11.226764 24.129164 25.041455 24.129163s25.041455-10.817164 25.041454-24.129163V532.452073l26.046836 24.520145c4.608 4.626618 10.817164 6.953891 17.640728 6.953891 6.795636 0 13.014109-2.513455 17.836218-7.149382l0.186182-0.195491c9.616291-9.644218 9.4208-24.510836-0.400291-33.773381l-0.223418-0.186182z" fill="#2c2c2c" p-id="3120"></path></svg>
<svg t="1704873961556" viewBox="0 0 1124 1224" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8913" width="14" height="14"><path stroke-width="150" d="M302.545455 116.363636h409.6a27.927273 27.927273 0 0 0 0-55.854545h-409.6a27.927273 27.927273 0 1 0 0 55.854545z m-93.09091 102.4h595.781819a27.927273 27.927273 0 0 0 0-55.854545h-595.781819a27.927273 27.927273 0 1 0 0 55.854545z m670.254546 55.854546h-735.418182c-51.414109 0-93.090909 41.686109-93.090909 93.090909v512c0 51.414109 41.6768 93.090909 93.090909 93.090909h735.418182c51.414109 0 93.090909-41.6768 93.090909-93.090909v-512c0-51.4048-41.6768-93.090909-93.090909-93.090909z m37.236364 594.3296a46.545455 46.545455 0 0 1-46.545455 46.545454h-716.8a46.545455 46.545455 0 0 1-46.545455-46.545454V386.327273a46.545455 46.545455 0 0 1 46.545455-46.545455h716.8a46.545455 46.545455 0 0 1 46.545455 46.545455v482.620509zM602.177164 522.621673l-68.9152-64.474764a4.747636 4.747636 0 0 0-1.405673-1.349818 9.234618 9.234618 0 0 0-1.396364-1.163636 11.776 11.776 0 0 0-3.006836-1.7408c-0.996073-0.577164-1.796655-0.772655-2.802036-1.163637-0.605091-0.195491-1.200873-0.390982-2.010764-0.577163-0.390982 0-0.586473-0.195491-0.996073-0.195491-2.196945-0.577164-4.012218-0.577164-5.008291-0.577164h-0.400291c-0.996073 0-3.006836 0-5.408581 0.577164-0.400291 0-0.595782 0.195491-0.996073 0.195491-0.400291 0.195491-1.005382 0.195491-1.405673 0.390981a15.555491 15.555491 0 0 0-3.006836 1.163637c-0.195491 0-0.195491 0.195491-0.400291 0.195491 0 0.195491-0.195491 0.195491-0.195491 0.195491-1.200873 0.577164-2.606545 1.349818-3.611927 2.122472l-0.195491 0.195491a7.000436 7.000436 0 0 0-2.401746 1.7408l-67.314036 64.660946c-4.803491 4.449745-7.614836 10.426182-7.614836 16.7936-0.195491 6.376727 2.606545 12.744145 7.214545 17.370763s10.826473 7.335564 17.435927 7.335564 13.218909-2.513455 18.031709-7.149382l24.641164-23.738182v266.267928c0 13.312 11.226764 24.129164 25.041455 24.129163s25.041455-10.817164 25.041454-24.129163V532.452073l26.046836 24.520145c4.608 4.626618 10.817164 6.953891 17.640728 6.953891 6.795636 0 13.014109-2.513455 17.836218-7.149382l0.186182-0.195491c9.616291-9.644218 9.4208-24.510836-0.400291-33.773381l-0.223418-0.186182z" fill="#2c2c2c" p-id="8914"></path></svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

1
src/icons/svg/help.svg Normal file
View File

@@ -0,0 +1 @@
<svg t="1705571985818" class="icon" viewBox="0 0 1028 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="19018" width="13" height="13"><path d="M983.678752 314.585575c-25.849513-60.781287-62.777388-115.474464-109.785575-162.482651s-101.701365-83.936062-162.482651-109.785575C648.433528 15.569591 581.663938 1.996101 512.998051 1.996101c-68.665887 0-135.435478 13.573489-198.412476 40.321248-60.781287 25.849513-115.474464 62.777388-162.482651 109.785575s-83.936062 101.701365-109.785575 162.482651C15.569591 377.562573 1.996101 444.332164 1.996101 512.998051c0 68.665887 13.573489 135.435478 40.321248 198.412475 25.849513 60.781287 62.777388 115.474464 109.785575 162.482651s101.701365 83.936062 162.482651 109.785575c62.976998 26.747758 129.746589 40.321248 198.412476 40.321248 68.665887 0 135.435478-13.573489 198.412475-40.321248 60.781287-25.849513 115.474464-62.777388 162.482651-109.785575s83.936062-101.701365 109.785575-162.482651c26.747758-62.976998 40.321248-129.746589 40.321248-198.412475 0-68.665887-13.573489-135.435478-40.321248-198.412476zM673.684211 894.752437c-51.00039 21.6577-105.094737 32.636257-160.68616 32.636257s-109.68577-10.978558-160.68616-32.636257c-49.403509-21.058869-93.816764-51.100195-131.9423-89.125926-38.125536-38.125536-68.067057-82.438986-89.125926-131.9423-21.6577-51.00039-32.636257-105.094737-32.636258-160.68616s10.978558-109.68577 32.636258-160.68616c21.058869-49.403509 51.100195-93.816764 89.125926-131.9423 38.125536-38.125536 82.438986-68.067057 131.9423-89.125926 51.00039-21.6577 105.094737-32.636257 160.68616-32.636258s109.68577 10.978558 160.68616 32.636258c49.403509 21.058869 93.816764 51.100195 131.9423 89.125926 38.125536 38.125536 68.067057 82.438986 89.125926 131.9423 21.6577 51.00039 32.636257 105.094737 32.636257 160.68616s-10.978558 109.68577-32.636257 160.68616c-21.058869 49.403509-51.100195 93.816764-89.125926 131.9423-38.125536 38.025731-82.538791 68.067057-131.9423 89.125926z" p-id="19019" fill="#080606"></path><path d="M476.868616 808.820273h74.554386c8.283821 0 14.97076-6.68694 14.97076-14.97076v-68.366472c0-8.283821-6.68694-14.97076-14.97076-14.97076h-74.554386c-8.283821 0-14.97076 6.68694-14.97076 14.97076v68.366472c0 8.283821 6.68694 14.97076 14.97076 14.97076zM687.557115 373.270955c-3.293567-51.299805-19.262378-90.922417-47.307602-117.869785-28.045224-26.947368-68.266667-41.119688-119.566472-42.217544h-0.998051c-58.585575 2.195712-104.096686 19.461988-135.235867 51.100195-31.039376 31.638207-47.906433 77.847953-50.102144 137.331774-0.299415 8.483431 6.487329 15.469786 14.97076 15.469785h65.272514c8.084211 0 14.77115-6.487329 14.970761-14.57154 1.896296-72.558285 30.440546-107.490058 90.024171-109.68577h1.297466c48.904483 2.095906 73.65614 26.947368 77.548538 77.947758 0 0.499025 0.099805 1.097856 0 1.596882-0.59883 26.548148-19.961014 59.0846-57.687329 96.810916-51.100195 48.904483-75.951657 93.118129-75.951657 135.036257v55.990643c0 8.283821 6.68694 14.97076 14.97076 14.970761h68.366472c8.283821 0 14.97076-6.68694 14.97076-14.970761v-65.272514c0-11.377778 7.8846-32.736062 45.211696-70.162963 52.497466-50.301754 79.145419-101.002729 79.145419-150.605848l0.099805-0.898246z" p-id="19020" fill="#080606"></path></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -0,0 +1 @@
<svg t="1705572472151" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="27270" width="13" height="13"><path d="M896.64 63.872v132.224c0 137.216-88.64 260.48-197.76 327.296 109.12 66.752 197.76 189.056 197.76 326.336V960h28.8a32 32 0 0 1 0 64H99.136a32 32 0 1 1 0-64h28.416v-110.272c0-137.28 92.8-259.584 201.856-326.4C220.16 456.576 127.488 333.376 127.488 196.16V63.872h-31.616A31.872 31.872 0 0 1 63.936 32 32 32 0 0 1 96 0h832.128a32 32 0 0 1 0 63.872h-31.36z m-703.04 0v132.224c0 135.424 117.824 253.312 243.84 286.336a39.68 39.68 0 0 1 15.936 8.384 43.712 43.712 0 0 1-1.344 66.176 42.24 42.24 0 0 1-16.064 7.68c-125.248 33.6-242.368 150.08-242.368 285.056V960h638.272v-110.272c0-135.488-119.808-252.352-245.76-285.44a43.712 43.712 0 0 1-22.784-15.872 42.56 42.56 0 0 1-7.424-18.24c-2.368-23.04 10.24-42.56 30.08-47.68 126.08-33.088 245.888-150.976 245.888-286.4V63.872H193.6zM511.872 458.24a107.264 107.264 0 0 0-14.848-16l-2.88-2.496-2.304-2.048-2.56-1.728a104.448 104.448 0 0 0-35.2-16.768c-65.28-17.088-113.664-67.392-135.744-135.936h387.264C683.52 351.744 634.88 402.048 569.408 419.2c-23.552 6.144-43.52 20.288-57.6 38.976z m-17.472 146.24l2.944-2.368c2.112-1.92 3.776-3.456 5.376-5.12l2.112-2.24 2.944-3.456 2.944-3.712s1.024-1.472 1.024-1.6l6.016 9.152 2.56 1.408 0.832 0.768a82.752 82.752 0 0 0 29.248 19.392l3.136 1.088c1.408 0.512 2.816 0.96 4.288 1.344 72.96 20.48 117.568 80.96 117.568 177.024v18.56H348.608v-18.56c0-95.36 43.776-155.52 115.648-176.64 8.704-2.176 16.96-5.888 24.576-11.008l1.664-0.896 3.84-3.136z" fill="#000000" p-id="27271"></path></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1 @@
<svg t="1705571586044" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="17569" width="13" height="13"><path d="M925.248 356.928l-258.176-258.176a64 64 0 0 0-45.248-18.752H144a64 64 0 0 0-64 64v736a64 64 0 0 0 64 64h736a64 64 0 0 0 64-64V402.176a64 64 0 0 0-18.752-45.248zM288 144h192V256H288V144z m448 736H288V736h448v144z m144 0H800V704a32 32 0 0 0-32-32H256a32 32 0 0 0-32 32v176H144v-736H224V288a32 32 0 0 0 32 32h256a32 32 0 0 0 32-32V144h77.824l258.176 258.176V880z" p-id="17570"></path></svg>

After

Width:  |  Height:  |  Size: 541 B

View File

@@ -89,7 +89,7 @@ export default {
},
mounted() {
const defaultFormSetting = this.getDefaultFormSetting()
this.iFormSetting = Object.assign({}, this.formSetting, defaultFormSetting)
this.iFormSetting = Object.assign({}, defaultFormSetting, this.formSetting)
},
methods: {
handleCheckedFieldsChange(values) {
@@ -107,6 +107,7 @@ export default {
return {
needGetObjectDetail: false,
submitMethod: () => 'patch',
cleanOtherFormValue: (formValue) => formValue,
cleanFormValue: (value) => {
const filterValue = {}
Object.keys(value)
@@ -114,12 +115,15 @@ export default {
.forEach((key) => {
filterValue[key] = value[key]
})
const formValue = []
let formValue = []
let object = {}
for (const row of vm.selectedRows) {
object = Object.assign({}, filterValue, { id: row.id })
formValue.push(object)
}
if (typeof this.iFormSetting.cleanOtherFormValue === 'function') {
formValue = this.iFormSetting.cleanOtherFormValue(formValue)
}
return formValue
},
onSubmit: function(validValues) {

View File

@@ -61,7 +61,6 @@ export default {
break
case 'logout':
this.logout()
window.location.href = `${process.env.VUE_APP_LOGOUT_PATH}?next=${this.$route.fullPath}`
break
case 'apiKey':
this.$router.push('/profile/api-keys')
@@ -73,7 +72,12 @@ export default {
this.$router.push('/profile/user/setting')
}
},
logout() {
async logout() {
const currentOrg = this.$store.getters.currentOrg
if (currentOrg.autoEnter) {
await this.$store.dispatch('users/setCurrentOrg', this.$store.getters.preOrg)
}
window.location.href = `${process.env.VUE_APP_LOGOUT_PATH}?next=${this.$route.fullPath}`
}
}
}

View File

@@ -21,6 +21,11 @@ export default {
code: 'cn',
cookieCode: 'zh-hans' // cookie code是为了让后端知道当前语言
},
{
title: '中文(繁體)',
code: 'zh_hant',
cookieCode: 'zh-hant' // cookie code是为了让后端知道当前语言
},
{
title: 'English',
code: 'en',
@@ -64,6 +69,8 @@ export default {
this.$moment.locale('en')
} else if (this.currentLang.code.indexOf('ja') > -1) {
this.$moment.locale('ja')
} else if (this.currentLang.code.indexOf('zh_hant') > -1) {
this.$moment.locale('zh-tw')
} else {
this.$moment.locale('zh-cn')
}
@@ -75,15 +82,19 @@ export default {
window.location.reload()
},
getLangCode() {
let langCode = localStorage.lang
let langCode = this.$cookie.get(this.LANG_COOKIE_NAME)
if (!langCode) {
langCode = this.$cookie.get(this.LANG_COOKIE_NAME)
langCode = localStorage.lang
}
if (!langCode) {
langCode = navigator.language || navigator.userLanguage
}
langCode = langCode.substr(0, 2)
langCode = langCode.replace('zh', 'cn')
if (langCode === 'zh-hant') {
langCode = 'zh_hant'
} else {
langCode = langCode.slice(0, 2)
langCode = langCode.replace('zh', 'cn')
}
return langCode
}
}

View File

@@ -11,7 +11,8 @@ export default {
name: 'WebTerminal',
computed: {
webTerminalUrl() {
let url = `${BASE_URL}/luna/?_=${Date.now()}`
const oid = this.$store.getters.currentOrg ? this.$store.getters.currentOrg.id : ''
let url = `${BASE_URL}/luna/?_=${Date.now()}${oid ? `&oid=${oid}` : ''}`
if (process.env.NODE_ENV !== 'production') {
url = url.replace('9528', '4200')
}

View File

@@ -1,12 +1,12 @@
<template>
<div class="sidebar-logo-container" :class="{'collapse':collapse}">
<transition name="sidebarLogoFade">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<a v-if="collapse" key="collapse" class="sidebar-logo-link" @click="handleClick">
<img :src="logoSrc" class="sidebar-logo" alt="logo">
</router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
</a>
<a v-else key="expand" class="sidebar-logo-link" @click="handleClick">
<img :src="logoTextSrc" class="sidebar-logo-text" alt="logo">
</router-link>
</a>
</transition>
</div>
</template>
@@ -27,6 +27,7 @@ export default {
},
computed: {
...mapGetters([
'viewRoutes',
'publicSettings'
]),
// eslint-disable-next-line vue/return-in-computed-property
@@ -38,6 +39,18 @@ export default {
}
},
created() {
},
methods: {
handleClick() {
const currentPath = this.$route.path
const matchingRoute = this.viewRoutes.find(route => currentPath.startsWith(route.path))
if (matchingRoute) {
this.$router.push(matchingRoute.redirect)
} else {
this.$router.push('/')
}
}
}
}
</script>

View File

@@ -66,6 +66,9 @@ export default {
font-weight: 500;
line-height: 32px;
color: #1F2329;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.page-heading-right {

View File

@@ -71,6 +71,11 @@ export default {
height: calc(100vh - 50px);
overflow-y: auto;
overflow-x: hidden;
.el-alert {
margin-top: -10px;
margin-bottom: 10px;
}
}
.go-back {

View File

@@ -22,6 +22,7 @@ import ECharts from 'vue-echarts'
import service from '@/utils/request'
import { message } from '@/utils/message'
import xss from '@/utils/xss'
import request from '@/utils/request'
import ElTableTooltipPatch from '@/utils/elTableTooltipPatch.js'
/**
@@ -70,6 +71,7 @@ Vue.prototype.$xss = xss
// 注册全局事件总线
Vue.prototype.$eventBus = eventBus
new Vue({
el: '#app',
i18n,
@@ -77,3 +79,21 @@ new Vue({
store,
render: h => h(App)
})
;(function() {
request({
url: '/api/v1/authentication/user-session/',
method: 'get'
})
})()
let IdBeforeunload = false
window.addEventListener('beforeunload', (event) => {
if (IdBeforeunload) return
IdBeforeunload = true
request({
url: '/api/v1/authentication/user-session/',
method: 'delete'
})
})

View File

@@ -30,6 +30,16 @@ export default [
component: () => import('@/views/settings/Task/CeleryTaskLog'),
name: 'TaskLog',
hidden: true,
meta: {
title: i18n.t('setting.ChatAI'),
permissions: []
}
},
{
path: '/chat/chat-ai/',
component: () => import('@/views/chat/ChatAi'),
name: 'ChatAi',
hidden: true,
meta: {
title: i18n.t('route.CeleryTaskLog'),
permissions: []

View File

@@ -38,10 +38,14 @@ const mutations = {
}
},
updateChaMessageContentById(state, { id, content }) {
updateChaMessageContentById(state, { id, data }) {
const chats = state.activeChat.chats || []
const filterChat = chats.filter((chat) => chat.message.id === id)?.[0] || {}
filterChat.message.content = content
if (Object.keys(filterChat).length > 0) {
filterChat.message.content = data.message.content
} else {
chats?.push(data)
}
}
}

View File

@@ -1,5 +1,7 @@
import { getProfile as apiGetProfile, logout } from '@/api/users'
import { getCurrentOrgLocal, getPreOrgLocal, getTokenFromCookie, saveCurrentOrgLocal, setPreOrgLocal } from '@/utils/auth'
import {
getCurrentOrgLocal, getPreOrgLocal, getTokenFromCookie, saveCurrentOrgLocal, setPreOrgLocal
} from '@/utils/auth'
import orgUtil from '@/utils/org'
import { resetRouter } from '@/router'
import Vue from 'vue'
@@ -70,7 +72,7 @@ const mutations = {
},
SET_CURRENT_ORG(state, org) {
// 系统组织和全局组织不设置成 Pre org
if (!state.currentOrg?.autoEnter) {
if (!state.currentOrg?.autoEnter && !state.currentOrg?.is_root) {
state.preOrg = state.currentOrg
setPreOrgLocal(state.username, state.currentOrg)
}
@@ -142,7 +144,7 @@ const actions = {
const systemOrg = {
id: orgUtil.SYSTEM_ORG_ID,
name: 'SystemSetting',
autoEnter: true
autoEnter: new Date().getTime()
}
commit('SET_CURRENT_ORG', systemOrg)
},
@@ -157,7 +159,8 @@ const actions = {
const globalOrg = {
id: orgUtil.GLOBAL_ORG_ID,
name: 'Global',
autoEnter: true
is_root: true,
autoEnter: new Date().getTime()
}
commit('SET_CURRENT_ORG', globalOrg)
},

BIN
src/styles/icons/dameng.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -304,6 +304,10 @@ input[type=file] {
font-size: 16px;
}
.el-dialog {
max-width: 100vw;
}
.el-input--mini .el-input__inner::placeholder {
font-size: 13px;
font-weight: 400;

View File

@@ -92,6 +92,10 @@
background: url('./icons/db2.png') no-repeat center left transparent;
}
&.dameng_ico_docu {
background: url('./icons/dameng.png') no-repeat center left transparent;
}
&.private_ico_docu {
background: url('./icons/private.png') no-repeat center left transparent;
}

View File

@@ -363,13 +363,19 @@ export function downloadText(content, filename) {
}
export function download(downloadUrl, filename) {
const iframe = document.createElement('iframe')
iframe.style.display = 'none'
document.body.appendChild(iframe)
const a = document.createElement('a')
a.href = downloadUrl
if (filename) {
a.download = filename
}
iframe.contentWindow.document.body.appendChild(a)
a.click()
window.URL.revokeObjectURL(downloadUrl)
setTimeout(() => {
document.body.removeChild(iframe)
}, 1000 * 60 * 30) // If you can't download it in half an hour, don't download it.
}
export function diffObject(object, base) {

View File

@@ -8,6 +8,11 @@ export const GLOBAL_ORG_ID = '00000000-0000-0000-0000-000000000000'
function getPropOrg() {
const orgs = store.getters.usingOrgs
const preOrg = store.getters.preOrg || {}
const preFound = orgs.find((item) => item.id === preOrg.id)
if (preFound) {
return preFound
}
const defaultOrg = orgs.find((item) => item.is_default)
if (defaultOrg) {
return defaultOrg
@@ -62,7 +67,7 @@ async function changeOrg(org, reload = true, vm = null) {
}
}
location.hash = '#' + path
setTimeout(() => location.reload(), 400)
setTimeout(() => location.reload(), 500)
}
function hasCurrentOrgPermission() {

View File

@@ -9,7 +9,6 @@ import { message } from '@/utils/message'
import store from '@/store'
import axiosRetry from 'axios-retry'
import router from '@/router'
import { DEFAULT_ORG_ID, SYSTEM_ORG_ID } from '@/utils/org'
// create an axios instance
const service = axios.create({
@@ -25,10 +24,7 @@ function beforeRequestAddToken(config) {
}
const queryOrgId = router.currentRoute.query?.oid
const storeOrgId = store.getters.currentOrg?.id
let orgId = queryOrgId || storeOrgId
if (!store.getters.publicSettings?.XPACK_ENABLED && orgId !== SYSTEM_ORG_ID) {
orgId = DEFAULT_ORG_ID
}
const orgId = queryOrgId || storeOrgId
if (orgId) {
config.headers['X-JMS-ORG'] = orgId
}

View File

@@ -2,14 +2,13 @@
import store from '@/store'
import router, { resetRouter } from '@/router'
import Vue from 'vue'
import VueCookie from 'vue-cookie'
import { message } from '@/utils/message'
import orgUtil from '@/utils/org'
import orgs from '@/api/orgs'
import { getPropView, isViewHasOrgs } from '@/utils/jms'
import request from '@/utils/request'
const whiteList = ['/login', process.env.VUE_APP_LOGIN_PATH] // no redirect whitelist
const autoEnterOrgs = ['00000000-0000-0000-0000-000000000001', '00000000-0000-0000-0000-000000000000']
function reject(msg) {
return new Promise((resolve, reject) => reject(msg))
@@ -19,30 +18,15 @@ async function checkLogin({ to, from, next }) {
if (whiteList.indexOf(to.path) !== -1) {
next()
}
// Determine whether the user has logged in
const sessionExpire = VueCookie.get('jms_session_expire')
if (!sessionExpire) {
request.get(process.env['VUE_APP_LOGOUT_PATH']).finally(() => {
window.location = process.env.VUE_APP_LOGIN_PATH
})
return reject('No session mark found in cookie')
} else if (sessionExpire === 'close') {
let startTime = new Date().getTime()
setInterval(() => {
const endTime = new Date().getTime()
const delta = (endTime - startTime)
startTime = endTime
Vue.$log.debug('Set session expire: ', delta)
VueCookie.set('jms_session_expire', 'close', { expires: '2m' })
}, 10 * 1000)
} else if (sessionExpire === 'age') {
Vue.$log.debug('Session expire on age')
}
try {
return await store.dispatch('users/getProfile')
} catch (e) {
Vue.$log.error(e)
const status = e.response.status
if (store.getters.currentOrg.autoEnter) {
await store.dispatch('users/setCurrentOrg', store.getters.preOrg)
}
if (status === 401 || status === 403) {
setTimeout(() => {
window.location = process.env.VUE_APP_LOGIN_PATH
@@ -61,7 +45,11 @@ async function getPublicSetting({ to, from, next }, isOpen) {
}
async function refreshCurrentOrg() {
orgs.getCurrentOrg().then(org => {
return orgs.getCurrentOrg().then(org => {
// Root 就不刷新本地的了, 会影响 autoEnter
if (autoEnterOrgs.indexOf(org.id) !== -1) {
return
}
store.dispatch('users/setCurrentOrg', org)
})
}
@@ -80,9 +68,16 @@ async function changeCurrentOrgIfNeed({ to, from, next }) {
Vue.$log.error('Current org is null or not a object: ', currentOrg)
await orgUtil.change2PropOrg({ to, from, next })
}
if (currentOrg.name === 'SystemSetting') {
const preOrg = store.getters.preOrg
await orgUtil.changeOrg(preOrg)
const globalOrgPath = [
'/console/perms/login-acls/', '/console/users/roles/',
'/console/perms/connect-method-acls/', '/settings/'
]
if (autoEnterOrgs.indexOf(currentOrg.id) !== -1 && currentOrg.autoEnter) {
const delta = new Date().getTime() - currentOrg.autoEnter
const notNeedChange = globalOrgPath.find(path => to.path.indexOf(path) === 0)
if (!notNeedChange && delta > 3000) {
await orgUtil.change2PropOrg({ to, from, next })
}
return
}
if (!orgUtil.hasCurrentOrgPermission()) {
@@ -156,7 +151,9 @@ export async function changeCurrentViewIfNeed({ to, from, next }) {
export async function startup({ to, from, next }) {
// if (store.getters.inited) { return true }
if (store.getters.inited) { return true }
if (store.getters.inited) {
return true
}
await store.dispatch('app/init')
// set page title

View File

@@ -1,23 +1,24 @@
const xss = require('xss')
const excludeTags = ['iframe', 'script']
const options = {
css: false,
stripIgnoreTagBody: ['script'],
onTag(tag, html, options) {
if (tag === 'iframe') {
return html.replace(/javascript:?/, '')
if (excludeTags.indexOf(tag) !== -1) {
return html.replace(/</g, '&lt;').replace(/>/g, '&gt;')
}
},
// 避免把页面样式过滤掉
onIgnoreTagAttr(tag, name, value, isWhiteAttr) {
onTagAttr(tag, name, value, isWhiteAttr) {
// 过滤掉标签上的事件
if (/^[^on]/.test(name)) {
return name + '="' + xss.escapeAttrValue(value) + '"'
} else {
if (tag === 'a') {
return name + '="' + xss.escapeAttrValue(value) + '"'
}
if (/^on/.test(name)) {
return name + '=' + '.'
}
if (['src', 'href'].indexOf(name) !== -1) {
return name + '=' + value.replace('javascript:', 'java:').replace('data:', 'dt:')
}
return name + '="' + xss.escapeAttrValue(value) + '"'
}
}
const filter = new xss.FilterXSS(options)

View File

@@ -91,7 +91,7 @@ export default {
title: this.$t('assets.TestAccountConnective'),
attrs: {
type: 'primary',
label: this.$t('assets.Test'),
label: this.$t('accounts.Test'),
disabled: (
!vm.$hasPerm('accounts.verify_account') ||
!vm.object.asset.auto_config?.ansible_enabled ||
@@ -104,7 +104,7 @@ export default {
this.$axios.post(
`/api/v1/accounts/accounts/tasks/`,
{
action: 'test',
action: 'verify',
accounts: [this.object.id]
}
).then(res => {

View File

@@ -99,6 +99,12 @@ export default {
hasBulkUpdate: false
}
}
},
mounted() {
const plan_id = this.$route.query.plan_id
if (plan_id !== undefined) {
this.tableConfig.url = `${this.tableConfig.url}?plan_id=${plan_id}`
}
}
}
</script>

View File

@@ -45,7 +45,7 @@ export default {
fieldsMeta: {
...getChangeSecretFields(),
assets: {
label: this.$t('xpack.Asset'),
label: this.$t('assets.Asset'),
type: 'assetSelect',
component: AssetSelect,
rules: [
@@ -61,7 +61,7 @@ export default {
}
},
nodes: {
label: this.$t('xpack.Node'),
label: this.$t('assets.Node'),
el: {
value: [],
ajax: {

View File

@@ -1,15 +1,24 @@
<template>
<GenericListTable :header-actions="headerActions" :table-config="tableConfig" />
<div>
<RecordViewSecret
v-if="showViewSecretDialog"
:url="secretUrl"
:visible.sync="showViewSecretDialog"
/>
<GenericListTable :header-actions="headerActions" :table-config="tableConfig" />
</div>
</template>
<script>
import GenericListTable from '@/layout/components/GenericListTable'
import { ActionsFormatter, DetailFormatter } from '@/components/Table/TableFormatters'
import { openTaskPage } from '@/utils/jms'
import RecordViewSecret from '@/components/Apps/ChangeSecret/RecordViewSecret.vue'
export default {
name: 'AccountChangeSecretExecutionTaskList',
name: 'AccountChangeSecretRecord',
components: {
RecordViewSecret,
GenericListTable
},
props: {
@@ -20,7 +29,10 @@ export default {
}
},
data() {
const vm = this
return {
secretUrl: '',
showViewSecretDialog: false,
tableConfig: {
url: `/api/v1/accounts/change-secret-records/?execution_id=${this.object.id}`,
columns: [
@@ -44,12 +56,11 @@ export default {
}
},
account: {
label: this.$t('users.Username'),
formatter: DetailFormatter,
formatterArgs: {
can: this.$hasPerm('accounts.view_account'),
getTitle({ row }) {
return row.account.name
return row.account.username
},
getRoute({ row }) {
return {
@@ -79,6 +90,19 @@ export default {
hasClone: false,
moreActionsTitle: this.$t('common.More'),
extraActions: [
{
name: 'View',
title: this.$t('common.View'),
type: 'primary',
callback: ({ row }) => {
// debugger
vm.secretUrl = `/api/v1/accounts/change-secret-records/${row.id}/secret/`
vm.showViewSecretDialog = false
setTimeout(() => {
vm.showViewSecretDialog = true
})
}
},
{
name: 'Retry',
title: this.$t('accounts.AccountChangeSecret.Retry'),
@@ -87,7 +111,7 @@ export default {
callback: ({ row }) => {
this.$axios.post(
'/api/v1/accounts/change-secret-records/execute/',
{ record_id: row.id }
{ record_ids: [row.id] }
).then(res => {
openTaskPage(res['task'])
})
@@ -107,7 +131,57 @@ export default {
hasImport: false,
hasCreate: false,
hasBulkDelete: false,
hasBulkUpdate: false
hasBulkUpdate: false,
searchConfig: {
exclude: ['id', 'status'],
options: [
{
label: this.$t('accounts.AccountChangeSecret.Asset'),
value: 'asset_name'
},
{
label: this.$t('accounts.Accounts'),
value: 'account_username'
},
{
value: 'status',
label: this.$t('common.Status'),
type: 'choice',
children: [
{
default: true,
value: 'success',
label: this.$t('common.Success')
},
{
value: 'failed',
label: this.$t('common.Failed')
}
]
}
]
},
extraMoreActions: [
{
name: 'BatchRetry',
title: this.$t('accounts.AccountChangeSecret.BatchRetry'),
type: 'primary',
fa: 'fa-retweet',
can: ({ selectedRows }) => {
return selectedRows.length > 0 && vm.$hasPerm('accounts.add_changesecretexecution')
},
callback: function({ selectedRows }) {
const ids = selectedRows.map(v => {
return v.id
})
this.$axios.post(
'/api/v1/accounts/change-secret-records/execute/',
{ record_ids: ids }).then(res => {
openTaskPage(res['task'])
})
}.bind(this)
}
]
}
}
}

View File

@@ -9,13 +9,13 @@
<script>
import { GenericDetailPage } from '@/layout/components'
import AccountChangeSecretExecutionInfo from './AccountChangeSecretExecutionInfo'
import AccountChangeSecretExecutionTaskList from './AccountChangeSecretExecutionTaskList'
import AccountChangeSecretRecord from './AccountChangeSecretRecord'
export default {
components: {
GenericDetailPage,
AccountChangeSecretExecutionInfo,
AccountChangeSecretExecutionTaskList
AccountChangeSecretRecord
},
data() {
return {
@@ -35,7 +35,7 @@ export default {
},
{
title: this.$t('accounts.AccountChangeSecret.TaskList'),
name: 'AccountChangeSecretExecutionTaskList',
name: 'AccountChangeSecretRecord',
hidden: () => !this.$hasPerm('accounts.view_changesecretrecord')
}
],

View File

@@ -1,5 +1,5 @@
<template>
<GenericListTable :table-config="tableConfig" :header-actions="headerActions" />
<GenericListTable :header-actions="headerActions" :table-config="tableConfig" />
</template>
<script>
@@ -110,7 +110,7 @@ export default {
hasSearch: true,
hasRefresh: true,
hasRightActions: true,
hasLeftActions: true,
hasLeftActions: false,
hasMoreActions: false,
hasExport: false,
hasImport: false,
@@ -119,6 +119,12 @@ export default {
hasBulkUpdate: false
}
}
},
mounted() {
const automation_id = this.$route.query.automation_id
if (automation_id !== undefined) {
this.tableConfig.url = `${this.tableConfig.url}?automation_id=${automation_id}`
}
}
}
</script>

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