Compare commits

..

140 Commits

Author SHA1 Message Date
fit2bot
7250a6be01 feat: Update v2.28.3 2022-12-12 17:12:13 +08:00
jiangweidong
5667c39bcb fix: 解决命令过滤获取不到一些应用的系统用户问题 2022-11-24 09:37:57 +08:00
jiangweidong
982ce90a9a fix: 去掉clickhouse改密页面入口 2022-11-22 18:39:40 +08:00
吴小白
86ce758adb feat: 更新 action node 版本 2022-11-17 21:56:54 +08:00
Jiangjie.Bai
582a84178d Merge pull request #2187 from jumpserver/dev
v2.28.0
2022-11-17 17:44:19 +08:00
“huailei000”
73de782756 fix: 修复绑定企业微信弹窗时input样式遮挡问题 2022-11-17 16:55:11 +08:00
Jiangjie.Bai
9b9f7c936c Merge pull request #2184 from jumpserver/dev
v2.28.0-rc5
2022-11-17 14:18:15 +08:00
jiangweidong
55100e64a1 perf: 翻译用户的[邮件]改为[邮箱] 2022-11-17 14:16:00 +08:00
jiangweidong
6d74959b64 perf: 翻译用户的[邮件]改为[邮箱] 2022-11-17 14:16:00 +08:00
Jiangjie.Bai
2a6100957f Merge pull request #2182 from jumpserver/dev
v2.28.0-rc4
2022-11-16 21:08:55 +08:00
feng626
6a3f6f8914 Merge pull request #2180 from jumpserver/pr@dev@ticket_expire_time
pef: ticket expire time
2022-11-15 17:34:08 +08:00
“huailei000”
704ae0b294 fix: 修复改密计划详情中列表和资产树添加资产到节点时table会获取url中参数问题 2022-11-15 17:11:40 +08:00
feng
9bf4597d8d pef: ticket expire time 2022-11-15 16:54:45 +08:00
Jiangjie.Bai
16606d6a27 Merge pull request #2176 from jumpserver/dev
v2.28.0-rc2
2022-11-14 10:01:05 +08:00
jiangweidong
bb1d19610e perf: 优化操作日志界面显示随主题变化 2022-11-14 09:47:12 +08:00
老广
ca871b16c4 Revert "perf: 优化刷新页面方法,避免刷新时页面出现空白"
This reverts commit a514e88b78.
2022-11-11 19:39:14 +08:00
Jiangjie.Bai
0a612f50e6 Merge pull request #2164 from jumpserver/dev
v2.28.0-rc1
2022-11-10 17:45:47 +08:00
“huailei000”
d566adb644 fix: 修复账号信息更新消息订阅从新进入页面状态没有改变问题 2022-11-10 17:43:28 +08:00
jiangweidong
242f958428 feat: 云资产同步支持选择IP类型 2022-11-10 17:42:14 +08:00
“huailei000”
f97d46814d perf: update yarn.lock 2022-11-08 15:28:11 +08:00
Jiangjie.Bai
5cc501f5af perf: 优化优先使用系统设置中的 rdp_resolution 配置 2022-11-07 18:47:29 +08:00
jiangweidong
31fd0ecde0 feat: 显示部分资源的批量删除操作按钮 2022-11-04 14:34:14 +08:00
jiangweidong
9d6ac3907a feat: 重构操作日志 (#2088)
* feat: 操作日志重构-支持查看变更资源信息

* feat: 修改界面

* feat: 优化操作日志详情界面

* feat: 修改显示样式
2022-11-04 14:33:02 +08:00
jiangweidong
784bd16e87 feat: 支持纳管 clickhouse[Web Terminal] (#2112) 2022-11-04 14:31:54 +08:00
jiangweidong
4a11ea2c57 feat: 云同步支持金山云 (#2125) 2022-11-04 14:31:20 +08:00
Jiangjie.Bai
4a2ffc754f fix: 修复创建第三方用户时 need_update_password 默认为 true 的问题 2022-11-01 16:38:06 +08:00
吴小白
d31929eb33 perf: 优化构建 2022-10-24 09:51:15 +08:00
Jiangjie.Bai
fe36fa9390 Merge pull request #2117 from jumpserver/dev
v2.27.0-rc4
2022-10-18 21:02:10 +08:00
“huailei000”
a97de8afb4 perf: 重置element-ui的message组件,防止重复点击重复弹出提示 2022-10-18 20:59:39 +08:00
Jiangjie.Bai
ba109900ec Merge pull request #2113 from jumpserver/dev
v2.27.0-rc3
2022-10-18 11:20:57 +08:00
“huailei000”
f8649457d6 perf: 优化页面布局 2022-10-18 10:34:18 +08:00
“huailei000”
8863693541 fix: 修复平台列表修复克隆名称带空格不能创建问题 2022-10-18 10:29:15 +08:00
“huailei000”
c2cbd3f5b0 fix: 修复切换组织下拉框icon图标颜色不明显 2022-10-17 16:32:07 +08:00
“huailei000”
0b84b96afb fix: 修复终端设置-创建录像存储设置默认存储字段不生效问题 2022-10-17 14:50:43 +08:00
Jiangjie.Bai
ec7768267f Merge pull request #2105 from jumpserver/dev
v2.27.0-rc2
2022-10-14 11:01:32 +08:00
吴小白
4aefb6779f fix: 修正错误的构建参数 2022-10-14 10:52:14 +08:00
吴小白
06fd007c62 Revert "revert: 还原 dockerfile"
This reverts commit 6f4e029537.
2022-10-14 10:52:14 +08:00
ibuler
6f4e029537 revert: 还原 dockerfile 2022-10-14 10:29:27 +08:00
Jiangjie.Bai
cc58b374ab Merge pull request #2101 from jumpserver/dev
v2.27.0-rc1
2022-10-13 17:44:53 +08:00
“huailei000”
07f35dbbb2 fix: jQuery undefined 2022-10-13 17:41:08 +08:00
Jiangjie.Bai
04ffbb8fd6 Merge pull request #2097 from jumpserver/dev
v2.27.0-rc1
2022-10-13 15:14:40 +08:00
“huailei000”
12a501d559 perf: update jquery 2022-10-13 11:20:29 +08:00
feng626
7dcd4490ba Merge pull request #2093 from jumpserver/pr@dev@task_log
fix: 修复任务列表无法查看日志信息bug
2022-10-13 10:54:48 +08:00
feng626
dd1ac7c7f8 fix: 修复任务列表无法查看日志信息bug 2022-10-13 10:50:53 +08:00
“huailei000”
471910e0b8 pref: 作业中心任务详情添加查看输出快捷键 2022-10-12 19:13:08 +08:00
“huailei000”
a514e88b78 perf: 优化刷新页面方法,避免刷新时页面出现空白 2022-10-11 16:21:02 +08:00
Jiangjie.Bai
b79fefdee8 feat: 命令过滤器支持关联节点; 2022-10-09 19:02:17 +08:00
Jiangjie.Bai
d2b3025709 perf: 优化用户创建默认勾选登录后需要修改密码 2022-10-09 16:57:02 +08:00
吴小白
386e2417e3 Merge pull request #2075 from YonezawaYukar/dev
fix: 修正 Dockerfile
2022-10-08 09:38:21 +08:00
米泽由香里
fd57b37cea Update Dockerfile
修改一处错误
RUN重复两次 导致无法正常build
2022-10-04 03:28:10 +08:00
吴小白
a6222f87d2 fix: 编译时修改版本 2022-09-30 10:08:14 +08:00
吴小白
5cd4e5b40b perf: 使用 yarn 构建 2022-09-30 10:08:14 +08:00
吴小白
eb3d9089e0 perf: 构建时使用缓存 2022-09-30 10:08:14 +08:00
jiangweidong
56c22cffe6 perf: 优化短信配置错误提示不友好问题 2022-09-26 14:53:03 +08:00
“huailei000”
fe1e26957a fix: 修复全局组织下更新、删除:用户、端点、端点规则、组织权限 2022-09-26 14:52:16 +08:00
“huailei000”
bc366947f0 fix: 实例同步列表不显示多选框;设置云同步详情页面的菜单高亮显示;云同步进入详情不主动激活detail页卡 2022-09-26 14:51:06 +08:00
“huailei000”
86150cc571 fix: 修复账号信息内容更新不及时问题 2022-09-26 14:49:56 +08:00
Jiangjie.Bai
f11fc947af feat: 修改 Endpoint 的创建页面; 修改 Oracle 数据库创建不包含版本号字段 2022-09-22 19:24:15 +08:00
Jiangjie.Bai
49880f6739 Merge pull request #2059 from jumpserver/dev
v2.26.0
2022-09-15 17:49:44 +08:00
“huailei000”
82faf0f99e perf: 资产详情-授权用户没有数据时显示暂无数据 2022-09-15 17:48:41 +08:00
Jiangjie.Bai
e6f98d58c4 Merge pull request #2057 from jumpserver/dev
v2.26.0-rc4
2022-09-15 16:18:03 +08:00
“huailei000”
3536a94976 fix: 修复全局组织下命令过滤不能创建 2022-09-15 15:59:53 +08:00
“huailei000”
45276010e0 fix: 创建数据库端口设置为必填项 2022-09-15 15:34:00 +08:00
“huailei000”
2e47f42366 fix: 修复批量命令列表可点击链接颜色 2022-09-15 15:33:41 +08:00
“huailei000”
dd6e9a1512 fix: 修复批量命令列表可点击链接颜色 2022-09-15 15:33:41 +08:00
Jiangjie.Bai
fd1f16d43c Merge pull request #2050 from jumpserver/dev
v2.26.0-rc2
2022-09-13 17:41:39 +08:00
feng626
24931a9f5a perf: 工单新增相关过滤 2022-09-13 15:33:22 +08:00
ibuler
f51924bf1d perf: 优化密码加密,如果没有key就不加密了 2022-09-13 15:32:53 +08:00
“huailei000”
f82257edb8 fix: 修复创建、更新用户后点击邀请用户接口404问题 2022-09-13 15:31:53 +08:00
jiangweidong
286e9894c0 perf: 屏蔽--secure参数,目前redis-cli版本为6.0,暂时用不到 2022-09-13 11:50:06 +08:00
Jiangjie.Bai
968b2415b1 Merge pull request #2043 from jumpserver/dev
v2.26.0-rc1
2022-09-08 15:46:44 +08:00
“huailei000”
e0f6fb305d perf: cas用户属性映射不能为空,至少设置username 2022-09-08 15:46:08 +08:00
jiangweidong
e0fd33f376 fix: 修复数据库创建页面有多个mongdob的问题 (#2041) 2022-09-08 15:16:54 +08:00
jiangweidong
7ad86062b4 perf: 支持连接开启ssl且自签证书的Redis/MongoDB (#2039)
* perf: 支持连接开启ssl且自签证书的Redis/MongoDB

* 修改字段文案

* 修改字段名称

* 修改变量名
2022-09-07 16:09:07 +08:00
“huailei000”
1fce7561db fix: 修复用户首次登录页面翻译 2022-09-06 19:38:49 +08:00
“huailei000”
3b17235b6e fix: 批量更新不请求接口 2022-09-06 19:06:14 +08:00
jiangweidong
52c9b9503b feat: 支持MFA可配置华为云平台短信对接 2022-09-06 17:33:42 +08:00
Jiangjie.Bai
48a2b20320 Merge pull request #2025 from jumpserver/pr@dev@feat_cloud_support_ctyun_private
feat: 云同步支持同步天翼私有云平台资产
2022-09-06 17:31:01 +08:00
Jiangjie.Bai
e6cc8cd2e8 Merge branch 'dev' into pr@dev@feat_cloud_support_ctyun_private 2022-09-06 17:30:47 +08:00
jiangweidong
3a183ddf53 feat: 支持OAuth2协议自定义注销功能 2022-09-06 17:27:11 +08:00
jiangweidong
7eb77487d1 feat: feat: 支持连接开启了ssl的Redis数据库 2022-09-06 16:55:37 +08:00
jiangweidong
a5da581317 feat: 云同步支持资产同步腾讯云(轻量应用服务器) 2022-09-06 16:19:58 +08:00
“huailei000”
6bda9a372e fix: moment.js 插件升级修复官方漏洞 2022-09-05 13:55:52 +08:00
jiangweidong
a8de087137 feat: 云同步支持同步天翼私有云平台资产 2022-09-02 17:37:27 +08:00
jiangweidong
34d9790a04 feat: MongoDB支持连接SSL类型 2022-08-24 15:00:56 +08:00
“huailei000”
5bfe2497fd fix: 修复新建用户个人信息确认后跳转路由还会返回个人信息确认页问题 2022-08-24 14:48:08 +08:00
jiangweidong
e16a775037 feat: 改密计划支持MongoDB改密 2022-08-24 14:47:46 +08:00
“huailei000”
42fab92237 fix: 调整按钮大小 2022-08-19 16:24:29 +08:00
Jiangjie.Bai
e2eac83615 fix: 修复上传json文件上传问题 2022-08-19 13:36:22 +08:00
“huailei000”
04465a1da3 fix: 修复LDAP用户导入失败弹出提示 2022-08-19 11:05:31 +08:00
“huailei000”
2adb1ee980 fix: 升级lodash 2022-08-19 11:04:43 +08:00
Jiangjie.Bai
776090d6ba Merge pull request #2001 from jumpserver/dev
v2.25.0
2022-08-18 16:12:45 +08:00
feng626
2c66bec7c9 Merge pull request #2000 from jumpserver/pr@dev@ticket_translate
fix: 修复工单翻译
2022-08-18 15:10:45 +08:00
feng626
b10f7faf25 fix: 修复工单翻译 2022-08-18 15:09:26 +08:00
feng626
450014ab16 Merge pull request #1999 from jumpserver/pr@dev@batch_command_filter
perf: 批量命令搜索优化
2022-08-18 11:48:22 +08:00
feng626
8ad0d2ac58 perf: 批量命令搜索优化 2022-08-18 11:44:45 +08:00
Jiangjie.Bai
bd41f96df3 fix: 修复属性映射字段值不显示的问题 2022-08-18 11:01:13 +08:00
Jiangjie.Bai
3a37952288 Merge pull request #1996 from jumpserver/dev
v2.25.0-rc4
2022-08-17 16:53:23 +08:00
Jiangjie.Bai
5346eb1ef1 fix: 修复云同步 lan attrs ip_group 必填项问题,gcp 上传文件不显示问题 2022-08-17 16:46:32 +08:00
Jiangjie.Bai
62b8fc0e3b Merge pull request #1994 from jumpserver/dev
v2.25.0-rc3
2022-08-16 19:08:23 +08:00
“huailei000”
f532d624e0 fix: 字段设置为必填项 2022-08-16 16:31:13 +08:00
“huailei000”
4ce6f49b30 fix: 修复应用授权远程应用以外的类型不显示字段说明 2022-08-16 15:55:19 +08:00
Jiangjie.Bai
2fb160b5f7 fix: 修改表单默认值的设置,gcp 的 attrs 字段中是 child 不是 children 2022-08-16 15:53:25 +08:00
“huailei000”
4ed9fb2acc fix: 取消树搜索input自动提示最近输入 2022-08-16 14:25:54 +08:00
“huailei000”
722fc083cb perf: 替换资产树title 2022-08-16 12:24:35 +08:00
Jiangjie.Bai
4356b79ecc perf: 优化 attrs 字段 default 值的返回 2022-08-15 16:23:13 +08:00
feng626
5eff817b05 Merge pull request #1987 from jumpserver/pr@dev@update_TICKET_AUTHORIZE_DEFAULT_TIME
fix: 配置工单授权时间后更新vuex
2022-08-15 11:28:12 +08:00
feng626
4f08fef25a fix: 配置工单授权时间后更新vuex 2022-08-15 11:27:13 +08:00
Jiangjie.Bai
b2028869cb Merge pull request #1986 from jumpserver/dev
v2.25.0-rc2
2022-08-12 18:06:56 +08:00
feng626
325f21f8b5 Merge pull request #1985 from jumpserver/fix_tree_search_url
fix: 资产树默认关闭搜索;调整搜索url携带的参数
2022-08-12 16:53:33 +08:00
“huailei000”
802d6f6cd3 fix: 资产树默认关闭搜索;调整搜索url携带的参数 2022-08-12 16:49:47 +08:00
feng626
f2120521d6 Merge pull request #1984 from jumpserver/pr@dev@ticket_flow
fix: 还原工单流逻辑
2022-08-12 14:27:02 +08:00
feng626
cbdeea9be7 fix: 还原工单流逻辑 2022-08-12 14:21:53 +08:00
“huailei000”
8d2d38327a fix: 添加正则过滤表情符号方法;修复创建云同步名称能输入表情符号问题 2022-08-12 11:33:22 +08:00
“huailei000”
5c564c42dc fix: 修复资产树右键不显示菜单问题 2022-08-12 11:33:00 +08:00
feng626
0c47f8f962 Merge pull request #1981 from jumpserver/pr@dev@ticket_flow
fix:  全局组织下不能修改工单流
2022-08-12 10:44:12 +08:00
feng626
65d625ad3c fix: 全局组织下不能修改工单流 2022-08-12 10:43:20 +08:00
feng626
0e65b7f1be Merge pull request #1979 from jumpserver/pr@dev@actions
fix: 修复授权动作不展示
2022-08-11 18:05:27 +08:00
feng626
22cfaf8665 fix: 修复授权动作不展示 2022-08-11 18:03:30 +08:00
feng626
b3670fe528 fix: 修复系统工具切换时 清空终端信息 2022-08-11 16:26:19 +08:00
Jiangjie.Bai
2f80b66b07 perf: 云同步 LAN 添加测试超时时间选项 2022-08-11 16:25:54 +08:00
Jiangjie.Bai
5277a725f8 Merge pull request #1973 from jumpserver/dev
v2.25.0-rc1
2022-08-11 14:11:59 +08:00
“huailei000”
b400c13965 perf: 优化资产树搜索等待时间 2022-08-11 14:11:29 +08:00
fit2bot
c71f0c17fc feat: 添加树的title显示模块 (#1975)
* feat: 树组件增加搜索功能

* feat: 添加插入节点

* feat: zTree组件增加搜索功能配置、创建根节点配置

* feat: 添加树的title显示模块

Co-authored-by: “huailei000” <2280131253@qq.com>
2022-08-11 13:43:08 +08:00
fit2bot
a952477c3b fix: 修复创建应用工单action (#1971)
* fix: 修复创建应用工单action

* 调整结构

Co-authored-by: feng626 <1304903146@qq.com>
2022-08-10 19:36:22 +08:00
feng626
1153a3b38b fix: 修复翻译 2022-08-10 11:01:56 +08:00
fit2bot
2e69cd86aa feat: 系统工具Ping和Telnet (#1961)
Co-authored-by: halo <wuyihuangw@gmail.com>
Co-authored-by: Jiangjie.Bai <32935519+BaiJiangJie@users.noreply.github.com>
2022-08-09 17:55:14 +08:00
jiangweidong
a5ea6360bd perf: 优化测试按钮交互 2022-08-09 16:10:39 +08:00
jiangweidong
0639b139d7 feat: 支持CMPPv2.0协议短信网关 2022-08-09 16:10:39 +08:00
ibuler
a8867dee83 perf: aes key 强制最大 16 2022-08-05 14:55:00 +08:00
Jiangjie.Bai
c4d0462362 feat: Cloud 支持局域网 IP 扫描 2022-08-05 14:38:23 +08:00
“huailei000”
db97240ad3 perf: 调整导航栏对齐;替换站内信图标 2022-08-04 14:46:52 +08:00
jiangweidong
5b6f06ac0e feat: 认证方式支持OAuth2.0协议 (#1963)
* feat: 认证方式支持OAuth2.0协议

* 增加scope参数

* perf: 优化 OAuth2 认证逻辑和Logo,支持上传图标

* perf: 优化 OAuth2 认证逻辑和Logo,支持上传图标

Co-authored-by: Jiangjie.Bai <bugatti_it@163.com>
2022-08-04 14:41:20 +08:00
“huailei000”
51de827c2b fix: 修复浏览器控制台提示icons提示告警问题 2022-08-03 14:24:55 +08:00
ibuler
49566cdae7 perf: 修改头像 2022-08-03 14:24:37 +08:00
“huailei000”
b6786687b6 fix: 修复修改个人信息后点击tip链接跳转失败问题 2022-08-03 14:24:06 +08:00
feng626
c83abe32c6 Merge pull request #1960 from jumpserver/pr@dev@ticket_app_add_action
feat: 应用工单支持选择动作
2022-07-22 16:25:10 +08:00
feng626
1b19e0201d feat: 应用工单支持选择动作 2022-07-22 16:20:48 +08:00
feng626
7681250fff Merge pull request #1959 from jumpserver/pr@dev@ticket_perm_default_time
feat: 添加默认工单授权时间
2022-07-22 15:23:02 +08:00
feng626
9fe4e0b613 feat: 添加默认工单授权时间 2022-07-22 15:21:22 +08:00
111 changed files with 7126 additions and 3799 deletions

View File

@@ -39,7 +39,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Build it and upload
uses: jumpserver/action-build-upload-assets@node10
uses: jumpserver/action-build-upload-assets@node14.16
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:

View File

@@ -1,23 +1,26 @@
FROM node:10 as stage-build
FROM node:14.16 as stage-build
ARG TARGETARCH
ARG NPM_REGISTRY="https://registry.npmmirror.com"
ENV NPM_REGISTY=$NPM_REGISTRY
ARG SASS_BINARY_SITE="https://npmmirror.com/mirrors/node-sass"
ENV SASS_BINARY_SITE=$SASS_BINARY_SITE
WORKDIR /data
RUN npm config set sass_binary_site=${SASS_BINARY_SITE}
RUN npm config set registry ${NPM_REGISTRY}
RUN yarn config set registry ${NPM_REGISTRY}
COPY package.json yarn.lock /data/
RUN yarn install
RUN npm rebuild node-sass
RUN set -ex \
&& npm config set registry ${NPM_REGISTRY} \
&& yarn config set registry ${NPM_REGISTRY} \
&& yarn config set cache-folder /root/.cache/yarn/lina
ADD package.json yarn.lock /data
RUN --mount=type=cache,target=/root/.cache/yarn \
yarn install
ARG VERSION
ENV VERSION=$VERSION
ADD . /data
RUN cd utils && bash -xieu build.sh build
RUN --mount=type=cache,target=/root/.cache/yarn \
sed -i "s@Version <strong>.*</strong>@Version <strong>${VERSION}</strong>@g" src/layout/components/Footer/index.vue \
&& yarn build
FROM nginx:alpine
COPY --from=stage-build /data/release/lina /opt/lina
COPY --from=stage-build /data/lina /opt/lina
COPY nginx.conf /etc/nginx/conf.d/default.conf

1
GITSHA Normal file
View File

@@ -0,0 +1 @@
5667c39bcb45300676b63d25754fd022543dca06

View File

@@ -7,6 +7,7 @@
"scripts": {
"dev": "vue-cli-service serve",
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"build:prod": "vue-cli-service build",
"build:stage": "vue-cli-service build --mode staging",
"preview": "node build/index.js --preview",
@@ -32,13 +33,13 @@
"element-ui": "2.13.2",
"eslint-plugin-html": "^6.0.0",
"install": "^0.13.0",
"jquery": "^3.5.0",
"jquery": "^3.6.1",
"js-cookie": "2.2.0",
"jsencrypt": "^3.2.1",
"krry-transfer": "^1.7.3",
"less": "^3.10.3",
"less-loader": "^5.0.0",
"lodash": "^4.17.15",
"lodash": "^4.17.21",
"lodash.clonedeep": "^4.5.0",
"lodash.frompairs": "^4.0.1",
"lodash.get": "^4.4.2",
@@ -50,8 +51,8 @@
"lodash.set": "^4.3.2",
"lodash.topairs": "^4.3.0",
"lodash.values": "^4.3.0",
"moment": "^2.29.1",
"moment-parseformat": "^3.0.0",
"moment": "^2.29.4",
"moment-parseformat": "^4.0.0",
"normalize.css": "7.0.0",
"npm": "^7.8.0",
"nprogress": "0.2.0",

View File

@@ -247,6 +247,15 @@ td .el-button.el-button--mini {
border-top-color: #676a6c;
}
.text-link {
color: info!important;
}
.text-link:hover {
color: info!important;
filter: opacity(65%)!important;
}
.text-danger {
color: danger;
}

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -75,6 +75,8 @@ export default {
showMenu: false,
showRefresh: true,
showAssets: false,
showSearch: true,
customTreeHeader: true,
url: '/api/v1/assets/assets/?fields_size=mini',
nodeUrl: '/api/v1/assets/nodes/',
// ?assets=0不显示资产. =1显示资产

View File

@@ -78,15 +78,22 @@ export default {
},
_cleanFormValue(form, remoteMeta) {
for (const [k, v] of Object.entries(remoteMeta)) {
if (v.default === undefined) {
continue
let valueSet = form[k]
if (v.type === 'nested object' && v.children) {
// 有一些字段属性时 nested object 类型,但是没有 children没有children的不需要走递归逻辑
// 比如:认证配置中的属性映射字段
if (typeof valueSet !== 'object') {
// 处理一些前端没有设置初始值的情况
valueSet = {}
}
form[k] = valueSet
this._cleanFormValue(valueSet, v.children)
}
const valueSet = form[k]
if (valueSet !== undefined) {
continue
}
if (v.type === 'nested object' && typeof valueSet === 'object') {
this._cleanFormValue(valueSet, v.children)
if (v.default === undefined) {
continue
}
form[k] = v.default
}

View File

@@ -18,6 +18,7 @@
<script>
import DataZTree from '../DataZTree'
import $ from '@/utils/jquery-vendor'
import { mapGetters } from 'vuex'
export default {
name: 'AutoDataZTree',
@@ -37,6 +38,10 @@ export default {
showCreate: true,
showDelete: true,
showUpdate: true,
showSearch: false,
// 自定义header
customTreeHeader: false,
customTreeHeaderName: this.$t('assets.AssetTree'),
async: {
enable: true,
url: (process.env.VUE_APP_ENV === 'production') ? (`${this.setting.treeUrl}`) : (`${process.env.VUE_APP_BASE_API}${this.setting.treeUrl}`),
@@ -66,6 +71,9 @@ export default {
}
},
computed: {
...mapGetters([
'currentOrg'
]),
treeSetting() {
this.$log.debug('Settings: ', this.setting)
return _.merge(this.defaultSetting, this.setting)
@@ -172,7 +180,7 @@ export default {
const rMenuID = this.$refs.dataztree.$refs.ztree.iRMenuID
const zTreeID = this.$refs.dataztree.$refs.ztree.iZTreeID
const offset = $(`#${zTreeID}`).offset()
const scrollTop = document.querySelector('.treebox').scrollTop
const scrollTop = document.querySelector('.treebox')?.scrollTop
x -= offset.left
// Tmp
y -= (offset.top + scrollTop) / 3 - 10
@@ -192,7 +200,7 @@ export default {
return
}
// 屏蔽收藏资产
if (treeNode.id === '-12') {
if (treeNode?.id === '-12') {
return
}
if (!treeNode && event.target.tagName.toLowerCase() !== 'button' && $(event.target).parents('a').length === 0) {

View File

@@ -14,10 +14,23 @@ export const EmailCheck = {
trigger: ['blur', 'change']
}
export const specialEmojiCheck = {
validator: (rule, value, callback) => {
value = value.trim()
if (/[\uD83C[\uDF00-\uDFFF]|\uD83D[\uDC00-\uDE4F]/.test(value)) {
callback(new Error(i18n.t('common.NotSpecialEmoji')))
} else {
callback()
}
},
trigger: ['blur', 'change']
}
export default {
Required,
RequiredChange,
EmailCheck
EmailCheck,
specialEmojiCheck
}
export const JsonRequired = {
@@ -32,3 +45,20 @@ export const JsonRequired = {
}
}
}
export const JsonRequiredUserNameMapped = {
required: true,
trigger: 'change',
validator: (rule, value, callback) => {
try {
JSON.parse(value)
const hasUserName = _.map(JSON.parse(value), (value) => value)
if (!hasUserName.includes('username')) {
callback(new Error(i18n.t('common.requiredHasUserNameMapped')))
}
callback()
} catch (e) {
callback(new Error(i18n.t('common.InvalidJson')))
}
}
}

View File

@@ -1,14 +1,50 @@
<template>
<div>
<ul v-show="loading" class="ztree">
{{ this.$t('common.tree.Loading') }}...
</ul>
<div v-show="!loading" class="treebox">
<ul :id="iZTreeID" class="ztree">
<div
v-if="treeSetting.customTreeHeader"
class="tree-header treebox"
>
<div class="content">
<span class="title">
{{ treeSetting.customTreeHeaderName }}
</span>
<span class="tree-banner-icon-zone">
<a id="searchIcon" class="tree-search special">
<i
class="fa fa-search tree-banner-icon"
@click.stop="treeSearch"
/>
<input
id="searchInput"
v-model="treeSearchValue"
type="text"
autocomplete="off"
class="tree-input"
>
</a>
<i
class="fa fa-refresh tree-banner-icon"
style="margin-right: 2px;"
@click.stop="refresh"
/>
</span>
</div>
<ul v-show="loading" class="ztree">
{{ this.$t('common.tree.Loading') }}...
</ul>
<div v-if="treeSetting.treeUrl===''">
{{ this.$t('common.tree.Empty') }}<a id="tree-refresh"><i class="fa fa-refresh" /></a>
<ul v-show="!loading" :id="iZTreeID" class="ztree" />
<div v-if="treeSetting.treeUrl===''" class="tree-empty">
{{ this.$t('common.tree.Empty') }}
</div>
</div>
<div v-else class="treebox">
<ul v-show="loading" class="ztree">
{{ this.$t('common.tree.Loading') }}...
</ul>
<ul v-show="!loading" :id="iZTreeID" class="ztree" />
<div v-if="treeSetting.treeUrl===''" class="tree-empty">
{{ this.$t('common.tree.Empty') }}
<a id="tree-refresh"><i class="fa fa-refresh" /></a>
</div>
</div>
<div :id="iRMenuID" class="rMenu">
@@ -24,6 +60,7 @@
// eslint-disable-next-line no-unused-vars
import $ from '@/utils/jquery-vendor.js'
import '@ztree/ztree_v3/js/jquery.ztree.all.min.js'
import '@ztree/ztree_v3/js/jquery.ztree.exhide.min.js'
import '@/styles/ztree.css'
import axiosRetry from 'axios-retry'
@@ -45,7 +82,8 @@ export default {
zTree: '',
rMenu: '',
init: false,
loading: false
loading: false,
treeSearchValue: ''
}
},
computed: {
@@ -54,8 +92,9 @@ export default {
}
},
mounted() {
window.refresh = this.refresh
window.treeSearch = this.treeSearch
this.initTree()
// $('.treebox').css('height', window.innerHeight - 60)
},
beforeDestroy() {
$.fn.zTree.destroy(this.iZTreeID)
@@ -82,9 +121,7 @@ export default {
retryDelay: () => { return 5000 }
}
}).then(res => {
if (!res) {
res = []
}
if (!res) res = []
if (res.length === 0) {
res.push({
name: this.$t('common.tree.Empty')
@@ -94,15 +131,13 @@ export default {
if (this.init) {
vm.zTree.destroy()
}
this.zTree = $.fn.zTree.init($(`#${this.iZTreeID}`), this.treeSetting, res)
if (!this.treeSetting.customTreeHeader) {
this.rootNodeAddDom(this.zTree)
}
// 手动上报事件, Tree加载完成
this.$emit('TreeInitFinish', this.zTree)
if (this.treeSetting.showRefresh) {
this.rootNodeAddDom(
this.zTree,
this.treeSetting.callback.refresh
)
}
if (this.treeSetting.showMenu) {
this.rMenu = $(`#${this.iRMenuID}`)
@@ -115,43 +150,196 @@ export default {
vm.init = true
})
},
rootNodeAddDom: function(ztree, callback) {
const vm = this
const refreshIcon = "<a id='tree-refresh'><i class='fa fa-refresh'></i></a>"
rootNodeAddDom(ztree) {
const { showSearch, showRefresh } = this.treeSetting
const searchIcon = `<a class="tree-search" id="searchIcon">
<i class='fa fa-search tree-banner-icon' onclick="treeSearch()" /></i>
<input type="text" autocomplete="off" id="searchInput" class="tree-input" />
</a>`
const refreshIcon = "<a id='tree-refresh' onclick='refresh()'><i class='fa fa-refresh'></i></a>"
const treeActions = `${showSearch ? searchIcon : ''}${showRefresh ? refreshIcon : ''}`
const icons = `<span class="">${treeActions}</span>`
const rootNode = ztree.getNodes()[0]
let $rootNodeRef
if (rootNode) {
$rootNodeRef = $('#' + rootNode.tId + '_a')
$rootNodeRef.after(refreshIcon)
} else {
$rootNodeRef = $('#' + ztree.setting.treeId)
$rootNodeRef.html(refreshIcon)
const $rootNodeRef = $('#' + rootNode.tId + '_a')
$rootNodeRef.after(icons)
}
const refreshIconRef = $('#tree-refresh')
refreshIconRef.bind('click', function() {
const result = callback()
if (result && result.then) {
result.finally(() => {
vm.initTree()
})
} else {
vm.initTree()
}
})
},
refresh: function() {
const refreshIconRef = $('#tree-refresh')
refreshIconRef.click()
refresh() {
this.treeSearchValue = ''
const result = this.treeSetting?.callback?.refresh()
if (result && result.then) {
result.finally(() => {
this.initTree()
})
} else {
this.initTree()
}
},
treeSearch() {
const searchIcon = document.getElementById(`searchIcon`)
const searchInput = document.getElementById(`searchInput`)
searchIcon.classList.toggle('active')
searchInput.focus()
searchInput.onclick = (e) => {
e.stopPropagation()
}
searchInput.onblur = (e) => {
e.stopPropagation()
if (!(e.target.value)) {
searchIcon.classList.toggle('active')
}
}
searchInput.oninput = _.debounce((e) => {
e.stopPropagation()
const value = e.target.value || ''
if (this.treeSetting.async.enable) {
this.filterAssetsServer(value)
} else {
this.filterTree(value)
}
}, 600)
},
getCheckedNodes: function() {
return this.zTree.getCheckedNodes(true)
},
recurseParent(node) {
const parentNode = node.getParentNode()
if (parentNode && parentNode.pId) {
return [parentNode, ...this.recurseParent(parentNode)]
} else if (parentNode) {
return [parentNode]
} else {
return []
}
},
recurseChildren(node) {
if (!node.isParent) {
return []
}
const children = node.children
if (!children) {
return []
}
let allChildren = []
children.forEach((n) => {
allChildren = [...children, ...this.recurseChildren(n)]
})
return allChildren
},
groupBy(array, filter) {
const groups = {}
array.forEach(function(o) {
const group = JSON.stringify(filter(o))
groups[group] = groups[group] || []
groups[group].push(o)
})
return Object.keys(groups).map(function(group) {
return groups[group]
})
},
filterTree(keyword, tree = this.zTree) {
if (!this.zTree) return
const searchNode = tree.getNodesByFilter((node) => node.id === 'search')
if (searchNode) tree.removeNode(searchNode[0])
const nodes = tree.transformToArray(tree.getNodes())
if (!keyword) {
tree.showNodes(nodes)
return
}
if (!keyword) {
if (tree.hiddenNodes) {
tree.showNodes(tree.hiddenNodes)
tree.hiddenNodes = null
}
if (tree.expandNodes) {
tree.expandNodes.forEach((node) => {
if (node.id !== nodes[0].id) {
tree.expandNode(node, false)
}
})
tree.expandNodes = null
}
return null
}
let shouldShow = []
const matchedNodes = tree.getNodesByFilter((node) => {
return node.name.toLowerCase().indexOf(keyword.toLowerCase()) > -1
})
if (matchedNodes.length < 1) {
let name = this.$t('common.Search')
const assetsAmount = matchedNodes.length
name = `${name} (${assetsAmount})`
const newNode = { id: 'search', name: name, isParent: false, open: false }
tree.addNodes(null, newNode)
}
matchedNodes.forEach((node) => {
const parents = this.recurseParent(node)
const children = this.recurseChildren(node)
shouldShow = [...shouldShow, ...parents, ...children, node]
})
tree.hiddenNodes = nodes
tree.expandNodes = shouldShow
tree.hideNodes(nodes)
tree.showNodes(shouldShow)
for (const node of shouldShow) {
if (node.isParent) {
tree.expandNode(node, true)
}
}
},
filterAssetsServer(keyword) {
if (!this.zTree) return
let searchNode = this.zTree.getNodesByFilter((node) => node.id === 'search')
if (searchNode) {
this.zTree.removeChildNodes(searchNode[0])
this.zTree.removeNode(searchNode[0])
}
const treeNodes = this.zTree.getNodes()
if (!keyword) {
if (treeNodes.length !== 0) {
this.zTree.showNodes(treeNodes)
}
return
}
if (treeNodes.length !== 0) {
this.zTree.hideNodes(treeNodes)
}
let treeUrl = this.treeSetting.treeUrl
const filterField = treeUrl.includes('?') ? `&search=${keyword}` : `?search=${keyword}`
if (treeUrl.indexOf('assets/nodes/children/tree') > -1) {
treeUrl = treeUrl + '&all=all'
}
const searchUrl = `${treeUrl}${filterField}`
this.$axios.get(searchUrl).then(nodes => {
let name = this.$t('common.Search')
const assetsAmount = nodes.length
name = `${name} (${assetsAmount})`
const newNode = { id: 'search', name: name, isParent: true, open: true, zAsync: true }
searchNode = this.zTree.addNodes(null, newNode)[0]
searchNode.zAsync = true
const nodesGroupByOrg = this.groupBy(nodes, (node) => {
return node.meta.data.org_name
})
for (const item of nodesGroupByOrg) {
this.zTree.addNodes(searchNode, item)
}
searchNode.open = true
})
return
}
}
}
</script>
<style lang='less' scoped>
<style lang='scss' scoped>
div.rMenu {
position: absolute;
visibility: hidden;
@@ -191,7 +379,7 @@ export default {
top: 100%;
z-index: 1000;
}
.ztree ::v-deep .fa-refresh {
.ztree ::v-deep .fa {
font: normal normal normal 14px/1 FontAwesome !important;
}
.dropdown a:hover {
@@ -218,4 +406,106 @@ export default {
height: 80vh;
overflow: auto;
}
::v-deep #tree-refresh {
margin-left: 3px;
}
::v-deep .tree-banner-icon-zone {
position: absolute;
right: 7px;
height: 30px;
overflow: hidden;
.fa {
color: #838385!important;;
&:hover {
color: #606266!important;;
}
}
}
::v-deep .tree-search {
position: relative;
top: -2px;
width: 20px;
height: 20px;
display: inline-block;
border-radius: 12px;
vertical-align: sub;
transition: .25s;
overflow: hidden;
.fa {
width: 13px!important;
}
.fa-search {
padding-top: 1px;
}
}
::v-deep .tree-search .tree-banner-icon {
position: absolute;
top: 1px;
left: 6px;
width: 6px;
height: 6px;
border-radius: 12px;
padding: 10px 6px;
overflow: hidden;
background-color: transparent!important;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
::v-deep .tree-search.active {
width: 160px;
background-color: #ffffff!important;
}
::v-deep .tree-search.active:hover {
border-radius: 12px;
}
::v-deep .tree-search input {
position: relative;
left: 20px;
width: 133px;
height: 100%;
background-color: #ffffff!important;
color: #606266;
display: flex;
justify-content: center;
align-items: center;
border: none;
outline: none;
}
.tree-header {
position: relative;
.title {
font-weight: 500;
}
.content {
height: 30px;
line-height: 30px;
border-bottom: 1px solid #e0e0e0;
border-radius: 3px;
padding: 0 5px;
box-sizing: border-box;
overflow: hidden;
cursor: pointer;
background-color: #D7D8DC;
.rotate {
transition: all .1.8s;
transform: rotate(-90deg);
}
.fa-caret-down {
font-size: 16px;
}
.special {
top: 1px!important;
}
}
}
.tree-empty {
margin-left: 4px;
}
</style>

View File

@@ -50,7 +50,7 @@ export default {
const reader = new FileReader()
reader.onload = function() {
let result = this.result
if (vm.toFormat === 'object') {
if (vm.toFormat === 'object' && vm.fileName.endsWith('.json')) {
result = JSON.parse(result)
}
vm.$emit('input', result)

View File

@@ -0,0 +1,61 @@
<template>
<el-row :gutter="10">
<div v-if="isAllEmpty()" style="text-align: center">
{{ this.$t('common.NoContent') }}
</div>
<div v-else>
<el-col :span="rightEmpty() ? 24 : 12">
<div v-if="!leftEmpty()">
<el-tag type="primary" effect="dark" :closable="false" style="width: 100%;">{{ row.leftTitle }}</el-tag>
<div v-for="(value, key, index) in row.left" :key="index">
<el-tag type="primary"><strong>{{ key }}: </strong>{{ value }}</el-tag>
</div>
</div>
</el-col>
<el-col :span="leftEmpty() ? 24 : 12">
<div v-if="!rightEmpty()">
<el-tag type="primary" effect="dark" :closable="false" style="width: 100%;">{{ row.rightTitle }}</el-tag>
<div v-for="(value, key, index) in row.right" :key="index">
<el-tag type="primary"><strong>{{ key }}: </strong>{{ value }}</el-tag>
</div>
</div>
</el-col>
</div>
</el-row>
</template>
<script>
export default {
name: 'TwoTabFormatter',
props: {
row: {
type: Object,
default: () => ({})
}
},
methods: {
isEmpty(content) {
return !content || JSON.stringify(content) === '{}'
},
leftEmpty() {
return this.isEmpty(this.row.left)
},
rightEmpty() {
return this.isEmpty(this.row.right)
},
isAllEmpty() {
return this.leftEmpty() && this.rightEmpty()
}
}
}
</script>
<style scoped>
.el-tag{
width: 100%;
white-space: normal;
height:auto;
}
</style>

View File

@@ -12,6 +12,7 @@ import DialogDetailFormatter from './DialogDetailFormatter'
import EditableInputFormatter from './EditableInputFormatter'
import StatusFormatter from './StatusFormatter'
import TagsFormatter from './TagsFormatter'
import TwoTabFormatter from './TwoTabFormatter'
export default {
DetailFormatter,
@@ -27,7 +28,8 @@ export default {
ArrayFormatter,
EditableInputFormatter,
StatusFormatter,
TagsFormatter
TagsFormatter,
TwoTabFormatter
}
export {
@@ -44,5 +46,6 @@ export {
ArrayFormatter,
EditableInputFormatter,
StatusFormatter,
TagsFormatter
TagsFormatter,
TwoTabFormatter
}

View File

@@ -119,6 +119,7 @@ export default {
border-radius: 3px;
line-height: 1.428;
cursor:pointer;
height: 30px;
}
.el-tree{
background-color: inherit !important;
@@ -134,5 +135,8 @@ export default {
}
.transition-box.left {
background: #f3f3f3;
border: 1px solid #e0e0e0;
border-radius: 3px;
margin-right: 2px;
}
</style>

View File

@@ -55,19 +55,19 @@
</el-col>
</el-row>
<el-row :gutter="24" style="margin: 0 auto;">
<el-col :md="24 - smsWidth" :sm="24">
<el-input v-model="SecretKey" :show-password="showPassword" :placeholder="HelpText" style="margin-bottom: 20px;" />
</el-col>
<el-col v-if="Select === 'sms'" :md="smsWidth" :sm="24">
<el-button
size="mini"
type="primary"
style="line-height:20px; float: right;"
:disabled="smsBtndisabled"
@click="sendChallengeCode"
>
{{ smsBtnText }}
</el-button>
<el-col :md="24" :sm="24" style="display: flex; margin-bottom: 20px;">
<el-input v-model="SecretKey" :show-password="showPassword" :placeholder="HelpText" />
<span v-if="Select === 'sms'" style="margin: -1px 0 0 20px;">
<el-button
size="mini"
type="primary"
style="line-height:20px; float: right;"
:disabled="smsBtndisabled"
@click="sendChallengeCode"
>
{{ smsBtnText }}
</el-button>
</span>
</el-col>
</el-row>
<el-row :gutter="24" style="margin: 0 auto;">

View File

@@ -47,6 +47,7 @@
"sqlserver": "SQLServer",
"redis": "Redis",
"mongodb": "MongoDB",
"clickhouse": "ClickHouse",
"k8s": "kubernetes"
},
"applicationsCategory": {
@@ -98,6 +99,7 @@
"Action": "Action",
"ActiveSelected": "Active selected",
"AdminUser": "Admin user",
"AssetTree": "Asset tree",
"ReplaceNodeAssetsAdminUser":"Replace node assets admin user with this",
"AdminUserDetail": "Admin user detail",
"DynamicUsername": "Dynamic username",
@@ -243,7 +245,9 @@
"View": "View",
"LoginIP": "Login IP",
"LoginCity": "Login city",
"LoginDate": "Login date"
"LoginDate": "Login date",
"BeforeChange": "Before change",
"AfterChange": "After change"
},
"auth": {
"LoginRequiredMsg": "You account has logout, Please login again",
@@ -253,6 +257,7 @@
"ReLoginErr": "Login time has exceeded 5 minutes, please login again"
},
"common": {
"NoContent": "No content",
"NeedAddAppsOrSystemUserErrMsg": "Please add apps or system user",
"VerificationCodeSent": "The verification code has been sent",
"SendVerificationCode": "Send verification code",
@@ -266,6 +271,12 @@
"IPLoginLimit": "IP login limit",
"Setting": "Setting",
"Certificate": "Certificate",
"CACertificate": "CA Certificate",
"ClientCertificate": "Client certificate",
"CertificateKey": "Certificate key file",
"AllowInvalidCert": "Allow invalid cert",
"UseSSL": "Use SSL/TLS",
"SecretKey": "Secret key",
"Scope": "Type",
"Builtin": "Builtin",
"DateCreated": "Date created",
@@ -407,6 +418,7 @@
"disableSelected": "Disable selected",
"disableSuccessMsg": "Disable success",
"fieldRequiredError": "This field is required",
"requiredHasUserNameMapped": "The mapping of the username field must be included, such as {'uid': 'username'}",
"getErrorMsg": "Get failed",
"fileType": "File type",
"Status": "Status",
@@ -423,6 +435,7 @@
"downloadImportTemplateMsg": "Download import template",
"downloadUpdateTemplateMsg": "Download update template",
"onlyCSVFilesTips": "Only csv supported",
"ImportFail": "Import fail",
"updateSuccessMsg": "Update success, total: {count}",
"dragUploadFileInfo": "Drag file here or click here to upload",
"uploadCsvLth10MHelpText": "csv/xlsx files with a size less than 10M",
@@ -515,6 +528,7 @@
},
"Cycle": "Cycle",
"FormatError": "Format error",
"NotSpecialEmoji": "Special emoticons are not allowed",
"WeekCronSelect": {
"Monday": "Monday",
"Tuesday": "Tuesday",
@@ -669,6 +683,7 @@
},
"route": {
"": "",
"AssignedTicketList": "Assigned tickets",
"CreateEndpoint": "Create endpoint",
"UpdateEndpoint": "Update endpoint",
"CreateEndpointRule": "Create endpoint rule",
@@ -768,7 +783,7 @@
"OperateLog": "Operation Logs",
"PasswordChangeLog": "Password Update Logs",
"Perms": "Permissions",
"PersonalInformationImprovement": "PersonalInformationImprovement",
"PersonalInformationImprovement": "Personal information improvement",
"PlatformCreate": "Platform create",
"PlatformDetail": "Platform detail",
"PlatformList": "Platforms",
@@ -912,15 +927,20 @@
}
},
"setting": {
"OAuth2LogoTip": "Tip: Authentication Service Provider (recommended image size: 64px*64px)",
"EndpointListHelpMessage": "The service endpoint is the address (port) for the user to access the service. When the user connects to the asset, the service endpoint will be selected according to the endpoint rules and asset tags, and the connection will be established as the access entry to realize the distributed connection of assets.",
"EndpointRuleListHelpMessage": "For the service endpoint selection strategy, two types are currently supported: <br>1. Specify the endpoint according to the endpoint rule (current page); <br>2. Select the endpoint through the asset tag. The tag name is fixed to endpoint, and the value is the name of the `endpoint`. <br>Two methods preferentially use label matching, because the IP segment may conflict, and the label method exists as a supplement to the rules.",
"EnableKoKoSSHHelpText": "Enabled, connect assets to display SSH Client pull-up method",
"SettingInEndpointHelpText": "Configure the service address and port in System Settings / Terminal Settings / Service Endpoints",
"Feature": "Feature",
"SMSProvider": "SMS provider",
"SMSProvider": "SMS provider / Protocol",
"SMS": "SMS",
"AlibabaCloud": "Alibaba cloud",
"TencentCloud": "Tencent cloud",
"HuaweiCloud": "Huawei cloud",
"SignChannelNum": "Signature Channel Number",
"AppEndpoint": "App access address",
"CMPP2": "CMPP v2.0",
"VerifySignTmpl": "Verification code template",
"Radius": "Radius",
"Enable": "Enable",
@@ -974,9 +994,12 @@
"authLdapServerUri": "LDAP server",
"authLdapUserAttrMap": "User attr map",
"authUserAttrMap": "User attr map",
"authUserAttrMapHelpText": "Mapping relationship { idp_key: sp_key}",
"authUserAttrMapHelpText": "The key on the left is the JumpServer user property, and the value on the right is the authenticated platform user property",
"SAML2": "SAML2",
"OAuth2": "OAuth2",
"enableSAML2Auth": "Enable SAML2 Auth",
"enableOAuth2Auth": "Enable OAuth2 Auth",
"tokenHTTPMethod": "Token Obtaining Method",
"SAML2Auth": "SAML2 Auth",
"authSAML2Xml": "IDP metadata XML",
"authSAML2MetadataUrl": "IDP metadata URL",
@@ -1096,7 +1119,13 @@
"weComTest": "Test",
"FeiShu": "FeiShu",
"feiShuTest": "Test",
"setting": "Setting"
"setting": "Setting",
"SystemTools": "System Tools",
"basicTools": "Basic Tools",
"destinationIP": "Destination IP",
"testPort": "Test Port",
"testTools": "Test",
"testHelpText": "Please enter the destination address for testing"
},
"tickets": {
"PermissionName": "Permission name",
@@ -1125,6 +1154,10 @@
"reply": "Reply",
"status": "Status",
"title": "Title",
"RelevantApp": "App",
"RelevantAsset": "Asset",
"RelevantCommand": "Command",
"RelevantSystemUser": "System user",
"type": "Type",
"user": "User",
"Status": "Status",
@@ -1393,16 +1426,20 @@
"IPNetworkSegment": "Ip Network Segment",
"Aliyun": "Ali Cloud",
"Qcloud": "Tencent Cloud",
"QcloudLighthouse": "Tencent Cloud(Lighthouse)",
"QingyunPrivatecloud": "Qingyun Private Cloud",
"HuaweiPrivatecloud": "Huawei Private Cloud",
"OpenStack": "OpenStack",
"CTYunPrivate": "CTYun Private Cloud",
"GCP": "Google Cloud Platform",
"FC": "Fusion Compute",
"LAN": "LAN",
"AWS_China": "AWS(China)",
"AWS_Int": "AWS(International)",
"HuaweiCloud": "Huawei Cloud",
"BaiduCloud": "Baidu Cloud",
"JDCloud": "JD Cloud",
"KingSoftCloud": "KingSoft Cloud",
"Azure":"Azure(China)",
"Azure_Int": "Azure(International)",
"HostnameStrategy": "Used to produce the asset hostname. For example, 1. Instance name (instanceDemo)2. Instance name and Partial IP (instanceDemo-250.1)",

View File

@@ -52,6 +52,7 @@
"sqlserver": "SQLServer",
"redis": "Redis",
"mongodb": "MongoDB",
"clickhouse": "ClickHouse",
"k8s": "Kubernetes"
},
"applicationsCategory": {
@@ -109,6 +110,7 @@
"HardwareInfo": "ハードウェア情報",
"AssetDetail": "アセットの詳細",
"AssetList": "アセットリスト",
"AssetTree": "アセットツリー",
"ReplaceNodeAssetsAdminUser": "ノード資産を置換する管理者",
"AssetListHelpMessage": "左側は資産ツリーで、右クリックはツリーノードを新規作成、削除、変更することができ、授権資産もノード方式で組織され、右側はそのノードの下に属する資産である\n",
"TestGatewayTestConnection": "テスト接続ゲートウェイ",
@@ -248,7 +250,9 @@
"SystemUserName": "システムユーザー名",
"LoginIP": "ログインIP",
"LoginCity": "ログイン都市",
"LoginDate": "ログイン日"
"LoginDate": "ログイン日",
"BeforeChange": "変更前",
"AfterChange": "変更後"
},
"auth": {
"LoginRequiredMsg": "アカウントが終了しました。ログインし直してください",
@@ -258,6 +262,7 @@
"ReLoginErr": "ログイン時間が 5 分を超えました。もう一度ログインしてください"
},
"common": {
"NoContent": "まだ内容がない",
"NeedAddAppsOrSystemUserErrMsg": "アプリケーションまたはシステムユーザーを追加してください",
"VerificationCodeSent": "検証コードが送信されました",
"SendVerificationCode": "認証コードの送信",
@@ -271,6 +276,12 @@
"IPLoginLimit": "IPログイン制限",
"Setting": "設定",
"Certificate": "証明書",
"CACertificate": "CA 証明書",
"ClientCertificate": "クライアント証明書",
"CertificateKey": "証明書秘密鍵ファイル",
"AllowInvalidCert": "証明書チェックを無視する",
"UseSSL": "使う SSL/TLS",
"SecretKey": "鍵",
"Scope": "カテゴリ",
"Builtin": "内蔵",
"DateCreated": "作成日",
@@ -416,6 +427,7 @@
"disableSelected": "選択した無効",
"disableSuccessMsg": "成功を無効にする",
"fieldRequiredError": "このフィールドは必須項目です",
"requiredHasUserNameMapped": "usernameフィールドのマッピングを含める必要があります, {'uid':'username'}など",
"getErrorMsg": "の取得に失敗しました",
"MFAErrorMsg": "MFAエラーです。チェックしてください",
"Total": "合計",
@@ -434,6 +446,7 @@
"downloadImportTemplateMsg": "作成テンプレートのダウンロード",
"downloadUpdateTemplateMsg": "更新テンプレートのダウンロード",
"onlyCSVFilesTips": "Csvファイルのインポートのみサポート",
"ImportFail": "インポートに失敗しました",
"updateSuccessMsg": "更新のインポートに成功しました。合計:{count}",
"uploadCsvLth10MHelpText": "Csv/xlsxのみアップロードでき、10m以下です",
"dragUploadFileInfo": "ここにファイルをドラッグするか、ここをクリックしてアップロードしてください",
@@ -491,6 +504,7 @@
"InvalidJson": "JSONの合法的ではありません",
"time_period": "時間帯",
"FormatError": "フォーマットエラー",
"NotSpecialEmoji": "特殊な表情記号の入力は許可されていません",
"WeekCronSelect": {
"Monday": "月曜日",
"Tuesday": "火曜日",
@@ -679,6 +693,7 @@
},
"route": {
"": "",
"AssignedTicketList": "割り当て済みワークオーダー",
"CreateEndpoint": "エンドポイントを作成する",
"UpdateEndpoint": "エンドポイントを更新",
"CreateEndpointRule": "エンドポイントルールを作成する",
@@ -933,6 +948,7 @@
}
},
"setting": {
"OAuth2LogoTip": "ヒント: 認証サービス プロバイダー (推奨画像サイズ: 64px*64px)",
"EndpointListHelpMessage": "サービスエンドポイントは、ユーザーがサービスにアクセスするためのアドレス(ポート)です。ユーザーがアセットに接続すると、エンドポイントルールとアセットタグに従ってサービスエンドポイントが選択され、接続がアクセスエントリとして確立されます。資産の分散接続を実現します。",
"EndpointRuleListHelpMessage": "サービスエンドポイント選択戦略では、現在2つのタイプがサポートされています<br> 1. エンドポイントルールに従ってエンドポイントを指定します(現在のページ);<br>2. アセットタグを介してエンドポイントを選択します。タグ名はに固定されていますエンドポイントであり、値はエンドポイントの名前です。 <br> IPセグメントが競合する可能性があるため、2つの方法が優先的にラベル照合を使用し、ラベル方法はルールの補足として存在します。",
"EnableKoKoSSHHelpText": "有効にすると、アセットを接続してSSHクライアントのプルアップ方式を表示します",
@@ -940,6 +956,10 @@
"Feature": "機能",
"AlibabaCloud": "Alibaba cloud",
"TencentCloud": "テンセント雲",
"HuaweiCloud": "ファーウェイ雲",
"SignChannelNum": "サインパス番号",
"AppEndpoint": "アクセスアドレスを適用する",
"CMPP2": "CMPP v2.0",
"Radius": "Radius",
"VerifySignTmpl": "認証コードメールテンプレート",
"Enable": "有効化",
@@ -997,9 +1017,11 @@
"authLdapServerUri": "LDAPアドレス",
"authLdapUserAttrMap": "ユーザー属性マッピング",
"authUserAttrMap": "ユーザー属性マッピング",
"authUserAttrMapHelpText": "マッピング関係 {idp:sp}",
"SAML2": "SAML2",
"authUserAttrMapHelpText": "左側のキーがJumpServerユーザ属性、右側の値が認証プラットフォームユーザ属性",
"OAuth2": "OAuth2",
"enableSAML2Auth": "SAML2認証をオンにする",
"enableOAuth2Auth": "OAuth2認証をオンにする",
"tokenHTTPMethod": "Token 取得方法",
"SAML2Auth": "SAML2認定",
"authSAML2Xml": "IDPメタデータXML",
"authSAML2MetadataUrl": "IDPメタデータURL",
@@ -1126,7 +1148,13 @@
"SMS": "SMS設定",
"feiShuTest": "テスト",
"setting": "設定",
"SMSProvider": "メールサービス業者"
"SystemTools": "システムツール",
"basicTools": "基本的なツール",
"destinationIP": "宛先アドレス",
"testPort": "テストポート",
"testTools": "テスト",
"testHelpText": "テストの宛先アドレスを入力してください",
"SMSProvider": "メールサービス業者 / プロトコル"
},
"tickets": {
"OneAssigneeType": "一次受付者タイプ",
@@ -1158,6 +1186,10 @@
"reply": "返信",
"status": "ステータス",
"title": "タイトル",
"RelevantApp": "するアプリケーション",
"RelevantAsset": "する資産",
"RelevantCommand": "するコマンド",
"RelevantSystemUser": "するシステムユーザー",
"action": "アクション",
"type": "タイプ",
"user": "ユーザー",
@@ -1227,7 +1259,7 @@
"DatePasswordLastUpdated": "パスワード最終更新日",
"DatePasswordUpdated": "パスワード更新日",
"DescribeOfGuide": "詳細については、をクリックしてください。",
"Email": "メール",
"Email": "ポスト",
"Phone": "携帯番号",
"WeCom": "企業wechat",
"DingTalk": "ホッチキス",
@@ -1437,16 +1469,20 @@
"IPNetworkSegment": "IPネットワークセグメント",
"Aliyun": "Alibaba cloud",
"Qcloud": "テンセント雲",
"QcloudLighthouse": "テンセント雲(軽量アプリケーションサーバー)",
"QingyunPrivatecloud": "青雲プライベートクラウド",
"HuaweiPrivatecloud": "ファーウェイプライベートクラウド",
"OpenStack": "OpenStack",
"CTYunPrivate": "天翼プライベート・クラウド",
"GCP": "Googleクラウド",
"FC": "Fusion Compute",
"LAN": "ローカルエリアネットワーク",
"AWS_China": "AWS(中国)",
"AWS_Int": "AWS (国際)",
"HuaweiCloud": "ファーウェイ雲",
"BaiduCloud": "百度雲",
"JDCloud": "京東雲",
"KingSoftCloud": "金山雲",
"Azure": "Azure(中国)",
"Azure_Int": "Azure (国際)",
"HostnameStrategy": "資産を生成するためにホスト名。例: 1. インスタンス名 (instanceDemo) 2.インスタンス名と一部IP (下位2桁) (instanceDemo-250.1)",

View File

@@ -52,6 +52,7 @@
"sqlserver": "SQLServer",
"redis": "Redis",
"mongodb": "MongoDB",
"clickhouse": "ClickHouse",
"k8s": "Kubernetes"
},
"applicationsCategory": {
@@ -110,6 +111,7 @@
"AssetDetail": "资产详情",
"AssetList": "资产列表",
"ReplaceNodeAssetsAdminUser":"替换节点资产的管理员",
"AssetTree": "资产树",
"AssetListHelpMessage": "左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的,右侧是属于该节点下的资产\n",
"TestGatewayTestConnection":"测试连接网关",
"TestGatewayHelpMessage": "如果使用了nat端口映射请设置为ssh真实监听的端口",
@@ -248,7 +250,9 @@
"SystemUserName": "系统用户名",
"LoginIP": "登录IP",
"LoginCity": "登录城市",
"LoginDate": "登录日期"
"LoginDate": "登录日期",
"BeforeChange": "变更前",
"AfterChange": "变更后"
},
"auth": {
"LoginRequiredMsg": "账号已退出,请重新登录",
@@ -258,6 +262,7 @@
"ReLoginErr": "登录时长已超过 5 分钟,请重新登录"
},
"common": {
"NoContent": "暂无内容",
"NeedAddAppsOrSystemUserErrMsg": "需要添加应用或系统用户",
"VerificationCodeSent": "验证码已发送",
"SendVerificationCode": "发送验证码",
@@ -271,6 +276,12 @@
"IPLoginLimit": "IP 登录限制",
"Setting": "设置",
"Certificate": "证书",
"CACertificate": "CA 证书",
"ClientCertificate": "客户端证书",
"CertificateKey": "证书秘钥文件",
"AllowInvalidCert": "忽略证书检查",
"UseSSL": "使用 SSL/TLS",
"SecretKey": "密钥",
"Scope": "类别",
"Builtin": "内置",
"DateCreated": "创建日期",
@@ -416,6 +427,7 @@
"disableSelected": "禁用所选",
"disableSuccessMsg": "禁用成功",
"fieldRequiredError": "这个字段是必填项",
"requiredHasUserNameMapped": "必须包含 username 字段的映射,如 { 'uid': 'username' }",
"getErrorMsg": "获取失败",
"MFAErrorMsg": "MFA错误请检查",
"Total": "总共",
@@ -434,6 +446,7 @@
"downloadImportTemplateMsg": "下载创建模板",
"downloadUpdateTemplateMsg": "下载更新模板",
"onlyCSVFilesTips": "仅支持csv文件导入",
"ImportFail": "导入失败",
"updateSuccessMsg": "导入更新成功,总共:{count}",
"uploadCsvLth10MHelpText": "只能上传 csv/xlsx, 且不超过 10M",
"dragUploadFileInfo": "将文件拖到此处,或点击此处上传",
@@ -491,6 +504,7 @@
"InvalidJson": "不是合法 JSON",
"time_period": "时段",
"FormatError": "格式错误",
"NotSpecialEmoji": "不允许输入特殊表情符号",
"WeekCronSelect": {
"Monday": "星期一",
"Tuesday": "星期二",
@@ -935,6 +949,7 @@
}
},
"setting": {
"OAuth2LogoTip": "提示:认证服务提供商(建议图片大小为: 64px*64px",
"EndpointListHelpMessage": "服务端点是用户访问服务的地址(端口),当用户在连接资产时,会根据端点规则和资产标签选择服务端点,作为访问入口建立连接,实现分布式连接资产",
"EndpointRuleListHelpMessage": "对于服务端点选择策略,目前支持两种:<br>1、根据端点规则指定端点(当前页面)<br>2、通过资产标签选择端点标签名固定是 endpoint值是端点的名称。<br>两种方式优先使用标签匹配,因为 IP 段可能冲突,标签方式是作为规则的补充存在的。",
"EnableKoKoSSHHelpText": "开启时连接资产会显示 SSH Client 拉起方式",
@@ -942,6 +957,10 @@
"Feature": "功能",
"AlibabaCloud": "阿里云",
"TencentCloud": "腾讯云",
"HuaweiCloud": "华为云",
"SignChannelNum": "签名通道号",
"AppEndpoint": "应用接入地址",
"CMPP2": "CMPP v2.0",
"Radius": "Radius",
"VerifySignTmpl": "验证码短信模板",
"Enable": "启用",
@@ -999,9 +1018,10 @@
"authLdapServerUri": "LDAP地址",
"authLdapUserAttrMap": "用户属性映射",
"authUserAttrMap": "用户属性映射",
"authUserAttrMapHelpText": "映射关系 {idpsp}",
"SAML2": "SAML2",
"enableSAML2Auth": "开启 SAML2 认证",
"authUserAttrMapHelpText": "左侧的键为 JumpServer 用户属性,右侧的值为认证平台用户属性",
"OAuth2": "OAuth2",
"enableOAuth2Auth": "开启 OAuth2 认证",
"tokenHTTPMethod": "Token 获取方法",
"SAML2Auth": "SAML2 认证",
"authSAML2Xml": "IDP metadata XML",
"authSAML2MetadataUrl": "IDP metadata URL",
@@ -1128,7 +1148,14 @@
"SMS": "短信设置",
"feiShuTest": "测试",
"setting": "设置",
"SMSProvider": "短信服务商"
"SMSProvider": "短信服务商",
"SystemTools": "系统工具",
"basicTools": "基本工具",
"destinationIP": "目的地址",
"testPort": "端口",
"testTools": "测试",
"testHelpText": "请输入目的地址进行测试",
"SMSProvider": "短信服务商 / 协议"
},
"tickets": {
"OneAssigneeType": "一级受理人类型",
@@ -1160,6 +1187,10 @@
"reply": "回复",
"status": "状态",
"title": "标题",
"RelevantApp": "应用",
"RelevantAsset": "资产",
"RelevantCommand": "命令",
"RelevantSystemUser": "系统用户",
"action": "动作",
"type": "类型",
"user": "用户",
@@ -1229,7 +1260,7 @@
"DatePasswordLastUpdated": "最后更新密码日期",
"DatePasswordUpdated": "密码更新日期",
"DescribeOfGuide": "欢迎使用JumpServer堡垒机系统获取更多信息请点击",
"Email": "邮",
"Email": "邮",
"Phone": "手机号",
"WeCom": "企业微信",
"DingTalk": "钉钉",
@@ -1439,16 +1470,20 @@
"IPNetworkSegment": "IP网段",
"Aliyun": "阿里云",
"Qcloud": "腾讯云",
"QcloudLighthouse": "腾讯云(轻量应用服务器)",
"QingyunPrivatecloud": "青云私有云",
"HuaweiPrivatecloud": "华为私有云",
"CTYunPrivate": "天翼私有云",
"OpenStack": "OpenStack",
"GCP": "谷歌云",
"FC": "Fusion Compute",
"LAN": "局域网",
"AWS_China": "AWS(中国)",
"AWS_Int": "AWS(国际)",
"HuaweiCloud": "华为云",
"BaiduCloud": "百度云",
"JDCloud": "京东云",
"KingSoftCloud": "金山云",
"Azure":"Azure(中国)",
"Azure_Int": "Azure(国际)",
"HostnameStrategy": "用于生成资产主机名。例如1. 实例名称 (instanceDemo)2. 实例名称和部分IP(后两位) (instanceDemo-250.1)",

View File

@@ -1 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1645509697274" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2361" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M867.046319 156.952658 156.952658 156.952658c-49.040926 0-88.317465 39.720653-88.317465 88.76158l-0.444115 532.570501c0 49.040926 39.720653 88.76158 88.76158 88.76158l710.09366 0c49.040926 0 88.76158-39.720653 88.76158-88.76158L955.807898 245.714238C955.808922 196.673311 916.087245 156.952658 867.046319 156.952658zM867.046319 334.476841l-355.047342 221.903949-355.047342-221.903949 0-88.76158 355.047342 221.903949 355.047342-221.903949L867.046319 334.476841z" p-id="2362"></path></svg>
<svg t="1659513943772" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5163" width="200" height="200"><path d="M1024 146.285714v731.428572H0V146.285714h1024z m-149.942857 94.573715L512 602.88l-362.057143-362.057143L98.267429 292.571429 512 706.340571 925.769143 292.571429l-51.748572-51.712z" fill="#7c7e7f" p-id="5164"></path></svg>

Before

Width:  |  Height:  |  Size: 865 B

After

Width:  |  Height:  |  Size: 377 B

View File

@@ -233,6 +233,10 @@ export default {
encryptedFields: {
type: Array,
default: () => ['password', 'token', 'private_key']
},
needGetObjectDetail: {
type: Boolean,
default: true
}
},
data() {
@@ -312,7 +316,7 @@ export default {
},
async getFormValue() {
const cloneFrom = this.$route.query['clone_from']
if (!this.isUpdateMethod() && !cloneFrom) {
if ((!this.isUpdateMethod() && !cloneFrom) || !this.needGetObjectDetail) {
return Object.assign(this.form, this.initial)
}
let object = this.object
@@ -322,9 +326,9 @@ export default {
const url = `${curUrl}${cloneFrom}/${query ? ('?' + query) : ''}`
object = await this.getObjectDetail(url)
if (object['name']) {
object.name = this.$t('common.cloneFrom') + ' ' + object.name
object.name = this.$t('common.cloneFrom') + object.name
} else if (object['hostname']) {
object.hostname = this.$t('common.cloneFrom') + ' ' + object.hostname
object.hostname = this.$t('common.cloneFrom') + object.hostname
}
} else {
object = await this.getObjectDetail(this.iUrl)

View File

@@ -97,6 +97,7 @@ export default {
getDefaultFormSetting() {
const vm = this
return {
needGetObjectDetail: false,
submitMethod: () => 'patch',
cleanFormValue: (value) => {
const filterValue = {}

View File

@@ -177,7 +177,7 @@ export default {
}
&>>> .el-input__icon {
color: #606266;
color: #606266!important;
}
}

View File

@@ -117,8 +117,10 @@ export default {
.navbar-right {
float: right;
margin-right: 10px;
height: 55px;
line-height: 55px;
.header-hover {
line-height: 56px!important;
&:hover {
background-color: #e6e6e6;
}

View File

@@ -12,7 +12,7 @@ export default {
<style scoped>
.wrapper-content {
padding: 20px 25px 40px;
padding: 20px 25px 10px;
}
.wrapper-content >>> .el-alert {

View File

@@ -43,6 +43,10 @@ export default {
</script>
<style scoped>
.page {
height: calc(100vh - 55px - 41px);
overflow: auto;
}
@media print {
.disabled-when-print{
display: none;

View File

@@ -46,7 +46,6 @@ Vue.use(require('vue-moment'), {
moment
})
// logger
import VueLogger from 'vuejs-logger'
import loggerOptions from './utils/logger'
Vue.use(VueLogger, loggerOptions)
@@ -55,14 +54,14 @@ import ECharts from 'vue-echarts'
Vue.component('echarts', ECharts)
import service from '@/utils/request'
Vue.prototype.$axios = service
// lodash
// import _ from 'lodash'
window._ = require('lodash')
// Vue.set(Vue.prototype, '_', _)
// if the table component cannot access `this.$axios`, it cannot send request
Vue.prototype.$axios = service
import { Message } from '@/utils/Message'
Vue.prototype.$message = Message
// 注册全局事件总线
Vue.prototype.$eventBus = new Vue()
new Vue({

View File

@@ -15,6 +15,16 @@ export default [
permissions: []
}
},
{
path: '/ops/ansible/task/:id/log/',
component: () => import('@/views/ops/CeleryTaskLog'),
name: 'AnsibleTaskLog',
hidden: true,
meta: {
title: i18n.t('route.CeleryTaskLog'),
permissions: []
}
},
{
path: '/ops/task/task/:id/log/',
component: () => import('@/views/ops/CeleryTaskLog'),

View File

@@ -1,6 +1,8 @@
import empty from '@/layout/empty'
import i18n from '@/i18n/i18n'
const activateMenu = '/console/assets/assets'
export default [
{
path: 'cloud',
@@ -20,7 +22,7 @@ export default [
hidden: true,
meta: {
title: i18n.t('xpack.Cloud.CloudSync'),
activeMenu: '/console/assets/assets'
activeMenu: activateMenu
}
},
{
@@ -71,6 +73,7 @@ export default [
hidden: true,
meta: {
title: i18n.t('xpack.Cloud.AccountDetail'),
activeMenu: activateMenu,
permissions: ['xpack.view_account']
}
}
@@ -121,7 +124,8 @@ export default [
name: 'SyncInstanceTaskDetail',
hidden: true,
meta: {
title: i18n.t('xpack.Cloud.SyncInstanceTaskDetail')
title: i18n.t('xpack.Cloud.SyncInstanceTaskDetail'),
activeMenu: activateMenu
}
}
]

View File

@@ -295,6 +295,16 @@ export default {
permissions: ['settings.change_other']
}
},
{
path: '/settings/tools',
name: 'Tools',
component: () => import('@/views/settings/Tools'),
meta: {
title: i18n.t('setting.SystemTools'),
icon: 'wrench',
permissions: ['settings.view_setting']
}
},
{
path: '/settings/license',
name: 'License',

View File

@@ -22,7 +22,7 @@ export default {
name: 'MyTicketList',
component: () => import('@/views/tickets/MyTicketList'),
meta: {
title: i18n.t('route.MyTickets'),
title: i18n.t('tickets.MyTickets'),
icon: 'file-text-o',
showOrganization: false,
permissions: []

View File

@@ -72,6 +72,9 @@ const mutations = {
},
ADD_WORKBENCH_ORGS(state, org) {
state.workbenchOrgs.push(org)
},
SET_IS_FIRST_LOGIN(state, flag) {
state.profile.is_first_login = flag
}
}
@@ -140,6 +143,9 @@ const actions = {
const usingOrgs = mapper[viewName] || state.consoleOrgs
Vue.$log.debug('Set using orgs: ', viewName, usingOrgs)
commit('SET_USING_ORGS', usingOrgs)
},
ifFirstLogin({ commit }, flag) {
commit('SET_IS_FIRST_LOGIN', flag)
}
}

View File

@@ -182,6 +182,9 @@ input[type=file] {
.el-col.el-col-sm-24 .ibox {
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
}
.el-pagination {

20
src/utils/Message.js Normal file
View File

@@ -0,0 +1,20 @@
// 重置message防止重复点击重复弹出message弹框
import { Message as elMessage } from 'element-ui'
let messageDom = null
const Message = (options) => {
// 判断弹窗是否已存在, 若存在则关闭
if (messageDom) messageDom.close()
messageDom = elMessage(options)
}
const typeArray = ['success', 'error', 'warning', 'info']
typeArray.forEach(type => {
Message[type] = options => {
if (typeof options === 'string') options = { message: options }
options.type = type
return Message(options)
}
})
export { Message }

View File

@@ -302,3 +302,8 @@ export function groupedDropdownToCascader(group) {
export { BASE_URL }
export function openWindow(url, name = '', iWidth = 900, iHeight = 600) {
var iTop = (window.screen.height - 30 - iHeight) / 2
var iLeft = (window.screen.width - 10 - iWidth) / 2
window.open(url, name, 'height=' + iHeight + ',width=' + iWidth + ',top=' + iTop + ',left=' + iLeft)
}

View File

@@ -3,17 +3,11 @@ import CryptoJS from 'crypto-js'
import VueCookie from 'vue-cookie'
export function fillKey(key) {
let keySize = 128
// 如果超过 key 16 位, 最大取 32 位,需要更改填充
if (key.length > 16) {
key = key.slice(0, 32)
keySize = keySize * 2
const KeyLength = 16
if (key.length > KeyLength) {
key = key.slice(0, KeyLength)
}
const filledKeyLength = keySize / 8
if (key.length >= filledKeyLength) {
return key.slice(0, filledKeyLength)
}
const filledKey = Buffer.alloc(keySize / 8)
const filledKey = Buffer.alloc(KeyLength)
const keys = Buffer.from(key)
for (let i = 0; i < keys.length; i++) {
filledKey[i] = keys[i]
@@ -43,10 +37,13 @@ export function encryptPassword(password) {
if (!password) {
return ''
}
let rsaPublicKeyText = getCookie('jms_public_key')
if (!rsaPublicKeyText) {
return password
}
const aesKey = (Math.random() + 1).toString(36).substring(2)
// public key 是 base64 存储的
const rsaPublicKeyText = getCookie('jms_public_key')
.replaceAll('"', '')
rsaPublicKeyText = rsaPublicKeyText.replaceAll('"', '')
const rsaPublicKey = atob(rsaPublicKeyText)
const keyCipher = rsaEncrypt(aesKey, rsaPublicKey)
const passwordCipher = aesEncrypt(password, aesKey)
@@ -54,4 +51,5 @@ export function encryptPassword(password) {
}
window.aesEncrypt = aesEncrypt
window.fillKey = fillKey

View File

@@ -1,8 +1,10 @@
import store from '@/store'
import { constantRoutes } from '@/router'
import { openWindow } from './common'
export function openTaskPage(taskId) {
window.open(`/#/ops/celery/task/${taskId}/log/`, '', 'width=900,height=600')
export function openTaskPage(taskId, taskType) {
taskType = taskType || 'celery'
openWindow(`/#/ops/${taskType}/task/${taskId}/log/?type=${taskType}`)
}
export function checkPermission(permsRequired, permsAll) {

View File

@@ -1,4 +1,4 @@
import $ from 'jquery'
import $ from 'jquery/dist/jquery.min.js'
window.$ = $
window.jQuery = $
export default $

View File

@@ -3,7 +3,8 @@ import i18n from '@/i18n/i18n'
import { getTokenFromCookie } from '@/utils/auth'
import { getErrorResponseMsg } from '@/utils/common'
import { refreshSessionIdAge } from '@/api/users'
import { Message, MessageBox } from 'element-ui'
import { MessageBox } from 'element-ui'
import { Message } from '@/utils/Message'
import store from '@/store'
import axiosRetry from 'axios-retry'
import router from '@/router'

View File

@@ -3,7 +3,7 @@ import store from '@/store'
import router, { resetRouter } from '@/router'
import Vue from 'vue'
import VueCookie from 'vue-cookie'
import { Message } from 'element-ui'
import { Message } from '@/utils/Message'
import orgUtil from '@/utils/org'
import orgs from '@/api/orgs'
import { getPropView, isViewHasOrgs } from '@/utils/jms'

View File

@@ -39,6 +39,9 @@ export function changeElementColor(themeColors) {
.el-link.el-link--${key}:after {
border-color: ${value}!important;
}
.el-tag--dark.el-tag--${key} {
background-color: ${value} !important;
}
`
}
}

View File

@@ -78,7 +78,6 @@ export default {
hasRefresh: true,
hasExport: false,
hasImport: false,
hasMoreActions: false,
createRoute: () => {
return {
name: 'AccountBackupPlanCreate'

View File

@@ -123,7 +123,6 @@ export default {
hasRefresh: true,
hasExport: false,
hasImport: false,
hasMoreActions: false,
searchConfig: {
getUrlQuery: false
},

View File

@@ -74,6 +74,9 @@ export default {
hasExport: false,
hasImport: false,
hasCreate: false,
searchConfig: {
getUrlQuery: false
},
hasMoreActions: false
},
assetRelationConfig: {

View File

@@ -107,7 +107,6 @@ export default {
hasRefresh: true,
hasExport: false,
hasImport: false,
hasMoreActions: false,
createRoute: () => {
return {
name: 'AssetChangeAuthPlanCreate'

View File

@@ -84,8 +84,7 @@ export default {
createRoute: 'AssetAclCreate',
hasRefresh: true,
hasExport: false,
hasImport: false,
hasMoreActions: false
hasImport: false
}
}
}

View File

@@ -5,6 +5,9 @@
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
import { getDatabaseTypeFieldsMap } from '@/views/applications/DatabaseApp/const'
import { UploadKey } from '@/components'
import { Required } from '@/components/DataForm/rules'
export default {
components: {
GenericCreateUpdatePage
@@ -36,6 +39,32 @@ export default {
fieldsMeta: {
host: {
type: 'input'
},
port: {
rules: [Required]
},
use_ssl: {
label: this.$t('common.UseSSL'),
component: 'el-switch'
},
allow_invalid_cert: {
label: this.$t('common.AllowInvalidCert'),
hidden: (form) => { return !form.use_ssl }
},
ca_cert: {
label: this.$t('common.CACertificate'),
hidden: (form) => { return !form.use_ssl },
component: UploadKey
},
client_cert: {
label: this.$t('common.ClientCertificate'),
hidden: (form) => { return !form.use_ssl },
component: UploadKey
},
cert_key: {
label: this.$t('common.CertificateKey'),
hidden: (form) => { return !form.use_ssl },
component: UploadKey
}
}
}

View File

@@ -1,10 +1,14 @@
import { ORACLE } from '../const'
import { MONGODB, REDIS } from '../const'
export function getDatabaseTypeFieldsMap(type) {
const baseParams = ['host', 'port', 'database']
const tlsParams = ['use_ssl', 'ca_cert']
switch (type) {
case ORACLE:
return ['host', 'port', 'database', 'version']
case REDIS:
return baseParams.concat(tlsParams.concat(['client_cert', 'cert_key']))
case MONGODB:
return baseParams.concat(tlsParams.concat(['cert_key', 'allow_invalid_cert']))
default:
return ['host', 'port', 'database']
return baseParams
}
}

View File

@@ -52,64 +52,82 @@ export const DATABASE_CATEGORY = 'db'
export const SQLSERVER = 'sqlserver'
export const REDIS = 'redis'
export const MONGODB = 'mongodb'
export const CLICKHOUSE = 'clickhouse'
const MYSQL_ITEM = {
name: MYSQL,
title: i18n.t(`applications.applicationsType.${MYSQL}`),
type: 'primary',
category: DATABASE_CATEGORY,
has: true,
group: i18n.t('applications.RDBProtocol')
}
const MARIADB_ITEM = {
name: MARIADB,
title: i18n.t(`applications.applicationsType.${MARIADB}`),
type: 'primary',
category: DATABASE_CATEGORY,
has: true
}
const ORACLE_ITEM = {
name: ORACLE,
title: i18n.t(`applications.applicationsType.${ORACLE}`),
type: 'primary',
category: DATABASE_CATEGORY,
has: hasLicence
}
const POSTGRESQL_ITEM = {
name: POSTGRESQL,
title: i18n.t(`applications.applicationsType.${POSTGRESQL}`),
type: 'primary',
category: DATABASE_CATEGORY,
has: hasLicence
}
const SQLSERVER_ITEM = {
name: SQLSERVER,
title: i18n.t(`applications.applicationsType.${SQLSERVER}`),
type: 'primary',
category: DATABASE_CATEGORY,
has: hasLicence
}
const CLICKHOUSE_ITEM = {
name: CLICKHOUSE,
title: i18n.t(`applications.applicationsType.${CLICKHOUSE}`),
type: 'primary',
category: DATABASE_CATEGORY,
has: hasLicence
}
const MONGODB_ITEM = {
name: MONGODB,
title: i18n.t(`applications.applicationsType.${MONGODB}`),
type: 'primary',
category: DATABASE_CATEGORY,
group: i18n.t('applications.NoSQLProtocol')
}
const REDIS_ITEM = {
name: REDIS,
title: i18n.t(`applications.applicationsType.${REDIS}`),
type: 'primary',
category: DATABASE_CATEGORY,
has: true
}
export const DATABASE = [
{
name: MYSQL,
title: i18n.t(`applications.applicationsType.${MYSQL}`),
type: 'primary',
category: DATABASE_CATEGORY,
has: true,
group: i18n.t('applications.RDBProtocol')
},
{
name: MARIADB,
title: i18n.t(`applications.applicationsType.${MARIADB}`),
type: 'primary',
category: DATABASE_CATEGORY,
has: true
},
{
name: ORACLE,
title: i18n.t(`applications.applicationsType.${ORACLE}`),
type: 'primary',
category: DATABASE_CATEGORY,
has: hasLicence
},
{
name: POSTGRESQL,
title: i18n.t(`applications.applicationsType.${POSTGRESQL}`),
type: 'primary',
category: DATABASE_CATEGORY,
has: hasLicence
},
{
name: SQLSERVER,
title: i18n.t(`applications.applicationsType.${SQLSERVER}`),
type: 'primary',
category: DATABASE_CATEGORY,
has: hasLicence
}
MYSQL_ITEM, MARIADB_ITEM, ORACLE_ITEM, POSTGRESQL_ITEM, SQLSERVER_ITEM, CLICKHOUSE_ITEM
]
export const KV_DATABASE = [
{
name: REDIS,
title: i18n.t(`applications.applicationsType.${REDIS}`),
type: 'primary',
category: DATABASE_CATEGORY,
has: true,
group: i18n.t('applications.NoSQLProtocol')
},
{
name: MONGODB,
title: i18n.t(`applications.applicationsType.${MONGODB}`),
type: 'primary',
category: DATABASE_CATEGORY
}
MONGODB_ITEM, REDIS_ITEM
]
export const AppPlanDatabase = DATABASE
export const AppPlanDatabase = [MYSQL_ITEM, MARIADB_ITEM, ORACLE_ITEM, POSTGRESQL_ITEM, SQLSERVER_ITEM, MONGODB_ITEM]
export const KUBERNETES = 'k8s'
export const CLOUD_CATEGORY = 'cloud'

View File

@@ -1,18 +1,40 @@
<template>
<IBox :fa="icon" :type="type" :title="title" v-bind="$attrs">
<table style="width: 100%;table-layout:fixed;" class="CardTable">
<tr v-for="obj of iObjects" :key="obj.value" class="item">
<td style="overflow: hidden;text-overflow: ellipsis;white-space: nowrap;">
<el-tooltip style="margin: 4px;" effect="dark" :content="obj.label" placement="left">
<el-link class="detail" @click="goDetail(obj)">{{ obj.label }}</el-link>
</el-tooltip>
</td>
<td>
<el-button size="mini" type="primary" style="float: right" @click="buttonClickCallback(obj)">
{{ buttonTitle }}
</el-button>
</td>
</tr>
<IBox
:fa="icon"
:type="type"
:title="title"
v-bind="$attrs"
>
<table class="card-table">
<div v-if="iObjects.length > 0" v-cloak>
<tr v-for="obj of iObjects" :key="obj.value" class="item">
<td>
<el-tooltip
style="margin: 4px;"
effect="dark"
:content="obj.label"
placement="left"
>
<el-link class="detail" @click="goDetail(obj)">
{{ obj.label }}
</el-link>
</el-tooltip>
</td>
<td>
<el-button
size="mini"
type="primary"
style="float: right"
@click="buttonClickCallback(obj)"
>
{{ buttonTitle }}
</el-button>
</td>
</tr>
</div>
<div v-else v-cloak style="text-align: center;">
{{ $t('common.NoData') }}
</div>
</table>
</IBox>
</template>
@@ -71,9 +93,9 @@ export default {
methods: {
async loadObjects() {
const data = await this.$axios.get(this.url)
data.forEach((v) => {
for (const v of data) {
v['label'] = v['name']
})
}
this.objects = data
},
goDetail(obj) {
@@ -84,18 +106,26 @@ export default {
</script>
<style lang="scss" scoped>
.card-table {
width: 100%;
table-layout:fixed;
}
[v-cloak]{
display: none!important;
}
b, strong {
font-weight: 700;
font-size: 13px;
}
tr td {
line-height: 1.42857;
padding: 8px;
vertical-align: top;
display: inline;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
tr.item {
border-bottom: 1px solid #e7eaec;
padding: 8px;

View File

@@ -48,6 +48,8 @@ export default {
showUpdate: true,
showDelete: true,
hasRightMenu: true,
showSearch: true,
customTreeHeader: true,
url: '/api/v1/assets/assets/',
nodeUrl: '/api/v1/assets/nodes/',
// ?assets=0不显示资产. =1显示资产

View File

@@ -88,7 +88,10 @@ export default {
},
headerActions: {
hasLeftActions: false,
hasRightActions: false
hasRightActions: false,
searchConfig: {
getUrlQuery: false
}
}
}
},

View File

@@ -7,7 +7,7 @@
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
import { Required } from '@/components/DataForm/rules'
import { Required, specialEmojiCheck } from '@/components/DataForm/rules'
import { ACCOUNT_PROVIDER_ATTRS_MAP, aliyun } from '../const'
import { UploadKey } from '@/components'
import { encryptPassword } from '@/utils/crypto'
@@ -17,10 +17,24 @@ export default {
GenericCreateUpdatePage
},
data() {
const vm = this
const accountProvider = this.$route.query.provider || aliyun
const accountProviderAttrs = ACCOUNT_PROVIDER_ATTRS_MAP[accountProvider]
function setFieldAttrs() {
const fieldsObject = {}
const updateNotRequiredFields = ['access_key_secret', 'client_secret', 'password', 'sc_password', 'oc_password', 'cert_file', 'key_file']
for (const item of accountProviderAttrs?.attrs) {
fieldsObject[item] = {
rules: updateNotRequiredFields.includes(item) && vm.$route.params.id ? [] : [Required]
}
}
return fieldsObject
}
return {
initial: {
attrs: {
ip_group: []
},
provider: this.$route.query.provider,
port: 443
},
@@ -31,10 +45,15 @@ export default {
[this.$t('common.Other'), ['comment']]
],
fieldsMeta: {
name: {
rules: [Required, specialEmojiCheck]
},
attrs: {
encryptedFields: ['access_key_secret'],
fields: accountProviderAttrs.attrs,
fieldsMeta: {
// 必须放在最上面,下面特殊制定的字段才会覆盖默认
...setFieldAttrs(),
service_account_key: {
label: this.$t('xpack.Cloud.ServerAccountKey'),
component: UploadKey,
@@ -42,6 +61,20 @@ export default {
toFormat: 'object'
}
},
cert_file: {
label: this.$t('common.Certificate'),
component: UploadKey,
el: {
toFormat: 'object'
}
},
key_file: {
label: this.$t('common.SecretKey'),
component: UploadKey,
el: {
toFormat: 'object'
}
},
password: {
rules: this.$route.params.id ? [] : [Required]
}
@@ -72,16 +105,34 @@ export default {
const attrs = values.attrs
for (const item of encryptedFields) {
const value = attrs[item]
if (value) {
attrs[item] = encryptPassword(value)
if (!value) {
continue
}
attrs[item] = encryptPassword(value)
}
const toListFields = ['ip_group']
for (const item of toListFields) {
let value = attrs[item]
if (!value) {
continue
}
value = value?.split(',') || []
value = value.filter((value, index) => { if (value) return true })
attrs[item] = value
}
return values
},
afterGetFormValue(formValue) {
if (!formValue.attrs) {
return formValue
}
if (Array.isArray(formValue.attrs.ip_group)) {
formValue.attrs.ip_group = formValue.attrs.ip_group.toString()
}
return formValue
}
}
},
computed: {
},
methods: {
}
}

View File

@@ -4,7 +4,7 @@
<script type="text/jsx">
import GenericListTable from '@/layout/components/GenericListTable'
import { ACCOUNT_PROVIDER_ATTRS_MAP, aliyun, aws_china, aws_international, huaweicloud, qcloud, azure, azure_international, vmware, nutanix, qingcloud_private, huaweicloud_private, openstack, gcp, baiducloud, jdcloud, fc } from '../const'
import { ACCOUNT_PROVIDER_ATTRS_MAP, aliyun, aws_china, aws_international, huaweicloud, qcloud, qcloud_lighthouse, azure, azure_international, vmware, nutanix, qingcloud_private, huaweicloud_private, ctyun_private, openstack, gcp, baiducloud, jdcloud, kingsoftcloud, fc, lan } from '../const'
export default {
name: 'AccountList',
@@ -82,6 +82,10 @@ export default {
type: 'primary',
can: true
},
{
name: qcloud_lighthouse,
title: ACCOUNT_PROVIDER_ATTRS_MAP[qcloud_lighthouse].title
},
{
name: huaweicloud,
title: ACCOUNT_PROVIDER_ATTRS_MAP[huaweicloud].title
@@ -94,6 +98,10 @@ export default {
name: jdcloud,
title: ACCOUNT_PROVIDER_ATTRS_MAP[jdcloud].title
},
{
name: kingsoftcloud,
title: ACCOUNT_PROVIDER_ATTRS_MAP[kingsoftcloud].title
},
{
name: aws_china,
title: ACCOUNT_PROVIDER_ATTRS_MAP[aws_china].title
@@ -127,6 +135,10 @@ export default {
name: huaweicloud_private,
title: ACCOUNT_PROVIDER_ATTRS_MAP[huaweicloud_private].title
},
{
name: ctyun_private,
title: ACCOUNT_PROVIDER_ATTRS_MAP[ctyun_private].title
},
{
name: openstack,
title: ACCOUNT_PROVIDER_ATTRS_MAP[openstack].title
@@ -138,6 +150,10 @@ export default {
{
name: fc,
title: ACCOUNT_PROVIDER_ATTRS_MAP[fc].title
},
{
name: lan,
title: ACCOUNT_PROVIDER_ATTRS_MAP[lan].title
}
]
}

View File

@@ -24,7 +24,7 @@ export default {
fields: [
[this.$t('common.Basic'), ['name']],
[this.$t('xpack.Cloud.CloudSource'), ['account', 'regions']],
[this.$t('xpack.Cloud.SaveSetting'), ['hostname_strategy', 'node', 'unix_admin_user', 'windows_admin_user', 'protocols', 'ip_network_segment_group', 'is_always_update']],
[this.$t('xpack.Cloud.SaveSetting'), ['hostname_strategy', 'node', 'unix_admin_user', 'windows_admin_user', 'protocols', 'ip_network_segment_group', 'sync_ip_type', 'is_always_update']],
[this.$t('xpack.Timer'), ['is_periodic', 'crontab', 'interval']],
[this.$t('common.Other'), ['comment']]
],
@@ -92,6 +92,7 @@ export default {
component: Select2,
el: {
multiple: true,
allowCreate: true,
value: [],
ajax: {
url: '/api/v1/xpack/cloud/regions/',

View File

@@ -38,6 +38,7 @@ export default {
},
tableConfig: {
url: `/api/v1/xpack/cloud/sync-instance-tasks/${this.object.id}/instances/`,
hasSelection: false,
columns: [
'instance_id',
{

View File

@@ -58,10 +58,7 @@ export default {
formatter: DetailFormatter,
formatterArgs: {
permissions: 'xpack.view_syncinstancedetail',
route: 'SyncInstanceTaskDetail',
routeQuery: {
activeTab: 'detail'
}
route: 'SyncInstanceTaskDetail'
}
},
history_count: {

View File

@@ -1,21 +1,26 @@
import i18n from '@/i18n/i18n'
export const gcp = 'gcp'
export const aliyun = 'aliyun'
export const baiducloud = 'baiducloud'
export const jdcloud = 'jdcloud'
export const kingsoftcloud = 'kingsoftcloud'
export const aws_international = 'aws_international'
export const aws_china = 'aws_china'
export const huaweicloud = 'huaweicloud'
export const qcloud = 'qcloud'
export const qcloud_lighthouse = 'qcloud_lighthouse'
export const azure = 'azure'
export const azure_international = 'azure_international'
export const vmware = 'vmware'
export const nutanix = 'nutanix'
export const qingcloud_private = 'qingcloud_private'
export const huaweicloud_private = 'huaweicloud_private'
export const ctyun_private = 'ctyun_private'
export const openstack = 'openstack'
export const gcp = 'gcp'
export const nutanix = 'nutanix'
export const vmware = 'vmware'
export const fc = 'fc'
export const baiducloud = 'baiducloud'
export const jdcloud = 'jdcloud'
export const lan = 'lan'
export const ACCOUNT_PROVIDER_ATTRS_MAP = {
[aliyun]: {
@@ -48,11 +53,21 @@ export const ACCOUNT_PROVIDER_ATTRS_MAP = {
title: i18n.t('xpack.Cloud.JDCloud'),
attrs: ['access_key_id', 'access_key_secret']
},
[kingsoftcloud]: {
name: kingsoftcloud,
title: i18n.t('xpack.Cloud.KingSoftCloud'),
attrs: ['access_key_id', 'access_key_secret']
},
[qcloud]: {
name: qcloud,
title: i18n.t('xpack.Cloud.Qcloud'),
attrs: ['access_key_id', 'access_key_secret']
},
[qcloud_lighthouse]: {
name: qcloud_lighthouse,
title: i18n.t('xpack.Cloud.QcloudLighthouse'),
attrs: ['access_key_id', 'access_key_secret']
},
[azure]: {
name: azure,
title: i18n.t('xpack.Cloud.Azure'),
@@ -63,6 +78,11 @@ export const ACCOUNT_PROVIDER_ATTRS_MAP = {
title: i18n.t('xpack.Cloud.Azure_Int'),
attrs: ['client_id', 'client_secret', 'tenant_id', 'subscription_id']
},
[gcp]: {
name: gcp,
title: i18n.t('xpack.Cloud.GCP'),
attrs: ['service_account_key']
},
[vmware]: {
name: vmware,
title: 'VMware',
@@ -88,14 +108,19 @@ export const ACCOUNT_PROVIDER_ATTRS_MAP = {
title: i18n.t('xpack.Cloud.OpenStack'),
attrs: ['auth_url', 'user_domain_name', 'username', 'password']
},
[gcp]: {
name: gcp,
title: i18n.t('xpack.Cloud.GCP'),
attrs: ['service_account_key']
},
[fc]: {
name: fc,
title: i18n.t('xpack.Cloud.FC'),
attrs: ['api_endpoint', 'username', 'password']
},
[ctyun_private]: {
name: ctyun_private,
title: i18n.t('xpack.Cloud.CTYunPrivate'),
attrs: ['access_key_id', 'access_key_secret', 'api_endpoint', 'cert_file', 'key_file']
},
[lan]: {
name: lan,
title: i18n.t('xpack.Cloud.LAN'),
attrs: ['ip_group', 'test_port', 'test_timeout', 'platform', 'hostname_prefix']
}
}

View File

@@ -15,7 +15,7 @@ export default {
},
fields: [
[this.$t('common.Basic'), ['name']],
[this.$t('common.Correlation'), ['users', 'user_groups', 'assets', 'applications', 'system_users']],
[this.$t('common.Correlation'), ['users', 'user_groups', 'nodes', 'assets', 'applications', 'system_users']],
[this.$t('common.Other'), ['is_active', 'comment']]
],
fieldsMeta: {
@@ -36,6 +36,17 @@ export default {
url: '/api/v1/users/groups/'
}
},
nodes: {
el: {
value: [],
ajax: {
url: '/api/v1/assets/nodes/',
transformOption: (item) => {
return { label: item.full_value, value: item.id }
}
}
}
},
assets: {
type: 'assetSelect',
component: AssetSelect,
@@ -63,7 +74,7 @@ export default {
el: {
value: [],
ajax: {
url: `/api/v1/assets/system-users/?protocol__in=ssh,telnet,mysql,postgresql,mariadb,oracle,sqlserver,k8s`,
url: `/api/v1/assets/system-users/?protocol__in=ssh,telnet,mysql,postgresql,mariadb,oracle,sqlserver,k8s,redis,mongodb,clickhouse`,
transformOption: (item) => {
if (this.$route.query.type === 'k8s') {
return { label: item.name, value: item.id }

View File

@@ -75,7 +75,7 @@ export default {
icon: 'fa-info-circle',
title: this.$t('assets.SystemUser'),
objectsAjax: {
url: `/api/v1/assets/system-users/?protocol__in=ssh,telnet,mysql,postgresql,mariadb,oracle,sqlserver,k8s`,
url: `/api/v1/assets/system-users/?protocol__in=ssh,telnet,mysql,postgresql,mariadb,oracle,sqlserver,k8s,redis,mongodb,clickhouse`,
transformOption: (item) => defaultTransformOption(item, 'username')
},
hasObjectsId: this.object.system_users,

View File

@@ -80,11 +80,7 @@ export default {
hasImport: false,
hasRefresh: true,
hasSearch: true,
hasMoreActions: false,
createRoute: 'CommandFilterCreate',
canCreate: () => {
return this.$hasPerm('assets.add_commandfilter')
}
createRoute: 'CommandFilterCreate'
}
}
},

View File

@@ -42,7 +42,6 @@ export default {
}
},
headerActions: {
hasMoreActions: false,
createRoute: 'DomainCreate'
},
notice: this.$t('assets.DomainHelpMessage')

View File

@@ -27,7 +27,6 @@ export default {
}
},
headerActions: {
hasMoreActions: false,
createRoute: 'LabelCreate'
}
}

View File

@@ -49,6 +49,7 @@ export default {
case 'sqlserver':
case 'redis':
case 'mongodb':
case 'clickhouse':
return Database
case 'k8s':
return K8S

View File

@@ -93,7 +93,6 @@ export default {
methods: {
onGetObjectDone(obj) {
this.fieldsMeta.private_key.el.fingerprint = obj.ssh_key_fingerprint
console.log(obj.fingerprint)
}
}
}

View File

@@ -42,7 +42,7 @@ export default {
vm.relationDialog.tableConfig.url = setUrlParam(vm.relationDialog.tableConfig.url, 'commandexecution', row.id)
vm.relationDialog.show = true
}
return <el-link onClick={onClick}>{ cellValue.length }</el-link>
return <el-link class='text-link' onClick={onClick}>{ cellValue.length }</el-link>
}
},
command: {
@@ -67,7 +67,7 @@ export default {
formatter: (row) => {
const label = this.$t('audits.View')
const route = { to: { name: 'CeleryTaskLog', params: { id: row.id }}}
return <router-link {...{ attrs: route }} target='_blank'>{ label }</router-link>
return <router-link class='text-link' {...{ attrs: route }} target='_blank'>{ label }</router-link>
}
},
date_start: {
@@ -100,7 +100,7 @@ export default {
options: [
{
label: this.$t('audits.Hosts'),
value: 'asset__hostname'
value: 'hostname_ip'
}
]
},
@@ -138,5 +138,4 @@ export default {
</script>
<style>
</style>

View File

@@ -1,23 +1,48 @@
<template>
<GenericListPage :table-config="tableConfig" :header-actions="headerActions" />
<div>
<GenericListPage
v-loading="loading"
:table-config="tableConfig"
:header-actions="headerActions"
/>
<el-dialog
:title="this.$t('route.OperateLog')"
:visible.sync="logDetailVisible"
width="70%"
>
<TwoTabFormatter :row="rowObj" />
</el-dialog>
</div>
</template>
<script>
import GenericListPage from '@/layout/components/GenericListPage'
import { getDaysAgo, getDaysFuture } from '@/utils/common'
import TwoTabFormatter from '@/components/TableFormatters/TwoTabFormatter'
import { ActionsFormatter } from '@/components/TableFormatters'
export default {
components: {
GenericListPage
GenericListPage,
TwoTabFormatter
},
data() {
const vm = this
const now = new Date()
const dateFrom = getDaysAgo(7, now).toISOString()
const dateTo = getDaysFuture(1, now).toISOString()
return {
rowObj: {
left: '',
right: '',
leftTitle: vm.$t('audits.BeforeChange'),
rightTitle: vm.$t('audits.AfterChange')
},
logDetailVisible: false,
loading: false,
tableConfig: {
url: '/api/v1/audits/operate-logs/',
columns: ['user', 'action_display', 'resource_type_display', 'resource', 'remote_addr', 'datetime'],
columns: ['user', 'action_display', 'resource_type_display', 'resource', 'remote_addr', 'datetime', 'actions'],
columnsMeta: {
user: {
showOverflowTooltip: true
@@ -36,7 +61,38 @@ export default {
width: '140px'
},
action_display: {
width: '90px'
width: '70px'
},
actions: {
width: '70px',
formatter: ActionsFormatter,
formatterArgs: {
hasUpdate: false,
canUpdate: false,
hasDelete: false,
canDelete: false,
hasClone: false,
canClone: false,
extraActions: [
{
name: 'View',
title: this.$t('common.View'),
type: 'primary',
callback: ({ row }) => {
vm.loading = true
vm.$axios.get(
`/api/v1/audits/operate-logs/${row.id}/?type=action_detail`,
).then(res => {
vm.rowObj.left = res.before
vm.rowObj.right = res.after
vm.logDetailVisible = true
}).finally(() => {
vm.loading = false
})
}
}
]
}
}
},
extraQuery: {

View File

@@ -2,7 +2,11 @@
<Page>
<el-collapse-transition>
<div style="display: flex;justify-items: center; flex-wrap: nowrap;justify-content:space-between;">
<div v-show="iShowTree" :style="iShowTree?('width:250px;'):('width:0;')" class="transition-box">
<div
v-show="iShowTree"
:style="{width: iShowTree?'20%': 0}"
class="transition-box tree-box"
>
<AutoDataZTree
ref="AutoDataZTree"
:key="DataZTree"
@@ -10,7 +14,7 @@
class="auto-data-ztree"
/>
</div>
<div :style="iShowTree?('display: flex;width: calc(100% - 250px);'):('display: flex;width:100%;')">
<div :style="iShowTree?('display: flex;width: 80%;'):('display: flex;width:100%;')">
<div class="mini">
<div style="display:block" class="mini-button" @click="iShowTree=!iShowTree">
<i v-show="iShowTree" class="fa fa-angle-left fa-x" /><i v-show="!iShowTree" class="fa fa-angle-right fa-x" />
@@ -70,6 +74,8 @@ export default {
treeUrl: '',
showRefresh: true,
showMenu: false,
showSearch: true,
customTreeHeader: true,
check: {
enable: true
},
@@ -276,4 +282,8 @@ export default {
height: 100px;
border: 1px solid #eee;
}
.tree-box {
margin-right: 2px;
border: 1px solid #e0e0e0;
}
</style>

View File

@@ -6,6 +6,7 @@
import ListTable from '@/components/ListTable'
import { ActionsFormatter } from '@/components/TableFormatters'
import { toSafeLocalDateStr } from '@/utils/common'
import { openTaskPage } from '@/utils/jms'
export default {
name: 'AdhocExecutionHistory',
@@ -86,6 +87,14 @@ export default {
callback: function({ row, tableData }) {
return this.$router.push({ name: 'HistoryExecutionDetail', params: { id: row.id }})
}
},
{
name: 'log',
title: this.$t('ops.output'),
type: 'info',
callback: function({ row }) {
openTaskPage(row.id, 'ansible')
}
}
]
}

View File

@@ -15,7 +15,6 @@ import DetailCard from '@/components/DetailCard'
import { toSafeLocalDateStr } from '@/utils/common'
import RunInfoCard from '../../RunInfoCard'
import { toLastFailureDisplay, toLastSucessDisplay } from '../business'
import { openTaskPage } from '@/utils/jms'
export default {
name: 'HistoryExecutionDetail',
@@ -72,17 +71,6 @@ export default {
{
key: this.$t('ops.isSuccess'),
value: this.object.is_success
},
{
key: this.$t('ops.output'),
value: this.object.id,
formatter: function(row, value) {
const onClick = function() {
openTaskPage(value, 'ansible')
}
const title = this.$t('common.View')
return <a onClick={onClick} >{ title }</a>
}
}
]
}

View File

@@ -108,7 +108,7 @@ export default {
openTaskPage(value, 'ansible')
}
const title = this.$t('common.View')
return <a onClick={onClick} >{ title }</a>
return <a class='text-link' onClick={onClick} >{ title }</a>
}
}
]

View File

@@ -6,6 +6,7 @@
import ListTable from '@/components/ListTable'
import { DetailFormatter } from '@/components/TableFormatters'
import { toSafeLocalDateStr } from '@/utils/common'
import { openTaskPage } from '@/utils/jms'
export default {
name: 'TaskHistory',
@@ -95,6 +96,14 @@ export default {
callback: function({ row, tableData }) {
return this.$router.push({ name: 'HistoryExecutionDetail', params: { id: row.id }})
}
},
{
name: 'log',
title: this.$t('ops.output'),
type: 'info',
callback: function({ row }) {
openTaskPage(row.id, 'ansible')
}
}
]
}

View File

@@ -12,19 +12,23 @@
import { GenericCreateUpdatePage } from '@/layout/components'
import { getDayFuture } from '@/utils/common'
import PermissionFormActionField from '../components/PermissionFormActionField'
import { remoteApplication } from '../const.js'
export default {
components: {
GenericCreateUpdatePage
},
data() {
const queryCategory = this.$route.query.category
const queryType = this.$route.query.type
const urlSearchFields = `category=${queryCategory}&type=${queryType}`
return {
initial: {
is_active: true,
date_start: new Date().toISOString(),
date_expired: getDayFuture(36500, new Date()).toISOString(),
type: this.$route.query.type,
category: this.$route.query.category
type: queryType,
category: queryCategory
},
fields: [
[this.$t('common.Basic'), ['name']],
@@ -33,7 +37,7 @@ export default {
[this.$t('common.action'), ['actions']],
[this.$t('common.Other'), ['is_active', 'date_start', 'date_expired', 'comment']]
],
url: `/api/v1/perms/application-permissions/?category=${this.$route.query.category}&type=${this.$route.query.type}`,
url: `/api/v1/perms/application-permissions/?${urlSearchFields}`,
createSuccessNextRoute: { name: 'ApplicationPermissionDetail' },
fieldsMeta: {
users: {
@@ -66,7 +70,7 @@ export default {
el: {
value: [],
ajax: {
url: `/api/v1/applications/applications/?category=${this.$route.query.category}&type=${this.$route.query.type}`,
url: `/api/v1/applications/applications/?${urlSearchFields}`,
transformOption: (item) => {
return { label: item.name + ' (' + item.type_display + ')', value: item.id }
}
@@ -79,7 +83,6 @@ export default {
ajax: {
url: (function() {
let url = '/api/v1/assets/system-users/'
const queryType = this.$route.query.type
if (this.$route.query.category === 'remote_app') {
url += `?protocol=rdp`
} else if (queryType) {
@@ -88,7 +91,7 @@ export default {
return url
}.bind(this)()),
transformOption: (item) => {
if (this.$route.query.type === 'k8s') {
if (queryType === 'k8s') {
return { label: item.name, value: item.id }
}
const username = item.username || '*'
@@ -106,7 +109,7 @@ export default {
actions: {
label: this.$t('perms.Actions'),
component: PermissionFormActionField,
helpText: this.$t('common.actionsTips')
helpText: remoteApplication.includes(queryType) ? this.$t('common.actionsTips') : ''
},
is_active: {
type: 'checkbox'

View File

@@ -22,6 +22,10 @@ export default {
choices: {
type: Array,
default: () => []
},
actions: {
type: Array,
default: () => []
}
},
data() {
@@ -74,6 +78,9 @@ export default {
},
computed: {
choicesIDs() {
if (this.actions.length !== 0) {
return this.actions
}
if (this.choices.length === 0) {
return [
'all', 'connect', 'upload_file', 'download_file', 'updownload',
@@ -83,9 +90,6 @@ export default {
return this.choices.map((v) => v.value)
},
iData() {
console.log('this.choicesIDs', this.choicesIDs)
console.log('this.choices', this.choices)
console.log('this.value', this.value)
this.$log.debug('choices: ', this.choicesIDs)
const fullTreeNodes = _.cloneDeep(this.fullChoicesTreeNodes)
const treeNodes = this.trimChoicesTreeNodes(fullTreeNodes)

View File

@@ -0,0 +1 @@
export const remoteApplication = ['chrome', 'mysql_workbench', 'vmware_client', 'custom']

View File

@@ -78,6 +78,7 @@ export default {
},
onPerformSuccess() {
this.$message.success(this.$t('common.updateSuccessMsg'))
this.$store.dispatch('users/ifFirstLogin', false)
setTimeout(() => this.$router.push({ name: 'ProfileInfo' }), 100)
},
submitMethod() {

View File

@@ -160,7 +160,7 @@ export default {
attrs: {
disabled: true,
name: 'site_msg',
model: this.object.receive_backends.indexOf('site_msg') !== -1
model: this.object?.receive_backends.indexOf('site_msg') !== -1
},
callbacks: {
change: this.updateUserReceiveBackends
@@ -171,7 +171,7 @@ export default {
type: 'switcher',
attrs: {
name: 'email',
model: this.object.receive_backends.indexOf('email') !== -1
model: this.object?.receive_backends.indexOf('email') !== -1
},
callbacks: {
change: this.updateUserReceiveBackends
@@ -182,7 +182,7 @@ export default {
type: 'switcher',
attrs: {
name: 'wecom',
model: this.object.receive_backends.indexOf('wecom') !== -1
model: this.object?.receive_backends.indexOf('wecom') !== -1
},
has: this.$store.getters.publicSettings.AUTH_WECOM,
callbacks: {
@@ -194,7 +194,7 @@ export default {
type: 'switcher',
attrs: {
name: 'dingtalk',
model: this.object.receive_backends.indexOf('dingtalk') !== -1
model: this.object?.receive_backends.indexOf('dingtalk') !== -1
},
has: this.$store.getters.publicSettings.AUTH_DINGTALK,
callbacks: {
@@ -206,7 +206,7 @@ export default {
type: 'switcher',
attrs: {
name: 'feishu',
model: this.object.receive_backends.indexOf('feishu') !== -1
model: this.object?.receive_backends.indexOf('feishu') !== -1
},
has: this.$store.getters.publicSettings.AUTH_FEISHU,
callbacks: {
@@ -306,6 +306,7 @@ export default {
{ 'receive_backends': this.getReceiveBackendList() }
).then(res => {
this.$message.success(this.$t('common.updateSuccessMsg'))
this.$store.dispatch('users/getProfile', true)
}).catch(err => {
this.$message.error(this.$t('common.updateErrorMsg' + ' ' + err))
})

View File

@@ -1,11 +1,8 @@
<template>
<IBox>
<GenericCreateUpdateForm
:fields="fields"
:fields-meta="fieldsMeta"
:initial="object"
:url="url"
:submit-method="submitMethod"
v-bind="$data"
/>
</IBox>
</template>
@@ -29,6 +26,7 @@ export default {
data() {
return {
url: `/api/v1/users/profile/`,
hasDetailInMsg: false,
fields: [
[this.$t('users.Account'), ['username', 'name', 'email']],
[this.$t('common.Other'), ['phone', 'wechat']]
@@ -43,13 +41,11 @@ export default {
email: {
disabled: true
}
},
submitMethod() {
return 'patch'
}
}
},
methods: {
submitMethod() {
return 'patch'
}
}
}
</script>

View File

@@ -8,7 +8,7 @@
<script>
import BaseAuth from './Base'
import { JsonRequired } from '@/components/DataForm/rules'
import { JsonRequiredUserNameMapped } from '@/components/DataForm/rules'
import { JsonEditor } from '@/components/FormFields'
export default {
@@ -25,16 +25,14 @@ export default {
'AUTH_CAS', 'CAS_SERVER_URL', 'CAS_ROOT_PROXIED_AS', 'CAS_VERSION'
]],
[this.$t('common.Other'), [
'CAS_LOGOUT_COMPLETELY', 'CAS_USERNAME_ATTRIBUTE',
'CAS_APPLY_ATTRIBUTES_TO_USER', 'CAS_RENAME_ATTRIBUTES',
'CAS_CREATE_USER'
'CAS_LOGOUT_COMPLETELY', 'CAS_RENAME_ATTRIBUTES', 'CAS_CREATE_USER'
]]
],
fieldsMeta: {
CAS_RENAME_ATTRIBUTES: {
component: JsonEditor,
label: this.$t('setting.authUserAttrMap'),
rules: [JsonRequired]
rules: [JsonRequiredUserNameMapped]
}
},
submitMethod: () => 'patch',
@@ -43,9 +41,18 @@ export default {
return obj
},
cleanFormValue(data) {
if (data['CAS_RENAME_ATTRIBUTES']) {
data['CAS_RENAME_ATTRIBUTES'] = JSON.parse(data['CAS_RENAME_ATTRIBUTES'])
let userNameAttribute = ''
const renameAttributes = JSON.parse(data['CAS_RENAME_ATTRIBUTES'])
if (renameAttributes) {
data['CAS_RENAME_ATTRIBUTES'] = renameAttributes
}
for (const key in renameAttributes) {
if (renameAttributes[key] === 'username') {
userNameAttribute = key
}
}
data['CAS_USERNAME_ATTRIBUTE'] = userNameAttribute
data['CAS_APPLY_ATTRIBUTES_TO_USER'] = true
return data
}
}

View File

@@ -0,0 +1,106 @@
<template>
<BaseAuth
:config="settings"
:title="$t('setting.OAuth2')"
enable-field="AUTH_OAUTH2"
v-on="$listeners"
/>
</template>
<script>
import BaseAuth from './Base'
import { JsonEditor } from '@/components/FormFields'
import { JsonRequired } from '@/components/DataForm/rules'
import { UploadField } from '@/components'
import request from '@/utils/request'
export default {
name: 'OAuth2',
components: {
BaseAuth
},
data() {
const vm = this
return {
settings: {
url: '/api/v1/settings/setting/?category=oauth2',
fields: [
[this.$t('common.Basic'), [
'AUTH_OAUTH2',
'AUTH_OAUTH2_PROVIDER',
'AUTH_OAUTH2_LOGO_PATH',
'AUTH_OAUTH2_CLIENT_ID', 'AUTH_OAUTH2_CLIENT_SECRET',
'AUTH_OAUTH2_ACCESS_TOKEN_METHOD'
]],
[this.$t('common.Params'), [
'AUTH_OAUTH2_SCOPE',
'AUTH_OAUTH2_PROVIDER_AUTHORIZATION_ENDPOINT',
'AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT',
'AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT',
'AUTH_OAUTH2_PROVIDER_END_SESSION_ENDPOINT'
]],
[this.$t('common.Other'), [
'AUTH_OAUTH2_LOGOUT_COMPLETELY',
'AUTH_OAUTH2_ALWAYS_UPDATE_USER',
'AUTH_OAUTH2_USER_ATTR_MAP'
]]
],
fieldsMeta: {
AUTH_OAUTH2_LOGO_PATH: {
component: UploadField,
el: {
width: '5%',
height: '5%',
tip: this.$t('setting.OAuth2LogoTip')
},
on: {
fileChange: ([value], updateForm) => {
vm.updateOAuth2Logo(value)
}
}
},
AUTH_OAUTH2_USER_ATTR_MAP: {
component: JsonEditor,
label: this.$t('setting.authUserAttrMap'),
rules: [JsonRequired],
helpText: this.$t('setting.authUserAttrMapHelpText')
},
AUTH_OAUTH2_ACCESS_TOKEN_METHOD: {
label: this.$t('setting.tokenHTTPMethod')
}
},
submitMethod: () => 'patch',
afterGetFormValue(obj) {
obj.AUTH_OAUTH2_USER_ATTR_MAP = JSON.stringify(obj.AUTH_OAUTH2_USER_ATTR_MAP)
return obj
},
cleanFormValue(data) {
delete data['AUTH_OAUTH2_LOGO_PATH']
if (data['AUTH_OAUTH2_USER_ATTR_MAP']) {
data['AUTH_OAUTH2_USER_ATTR_MAP'] = JSON.parse(data['AUTH_OAUTH2_USER_ATTR_MAP'])
}
return data
}
}
}
},
methods: {
updateOAuth2Logo(file) {
const formData = new FormData()
formData.append('AUTH_OAUTH2_LOGO_PATH', file)
return request({
url: '/api/v1/settings/setting/?category=oauth2',
method: 'patch',
headers: {
'Content-Type': 'multipart/form-data'
},
data: formData
})
}
}
}
</script>
<style scoped>
</style>

View File

@@ -1,6 +1,5 @@
<template>
<BaseAuth
:value="value"
:config="settings"
:title="$t('setting.SAML2')"
enable-field="AUTH_SAML2"
@@ -19,12 +18,6 @@ export default {
components: {
BaseAuth
},
props: {
value: {
type: Boolean,
required: true
}
},
data() {
return {
settings: {
@@ -45,9 +38,6 @@ export default {
]]
],
fieldsMeta: {
AUTH_SAML2: {
label: this.$t('setting.enableSAML2Auth')
},
SAML2_IDP_METADATA_URL: {
component: 'el-input',
label: this.$t('setting.authSAML2MetadataUrl'),

View File

@@ -19,6 +19,7 @@ import FeiShu from './FeiShu'
import WeCom from './WeCom'
import SSO from './SSO'
import SAML2 from './SAML2'
import OAuth2 from './OAuth2'
export default {
components: {
@@ -33,7 +34,8 @@ export default {
FeiShu,
Radius,
SSO,
SAML2
SAML2,
OAuth2
},
data() {
return {
@@ -60,6 +62,10 @@ export default {
title: this.$t('setting.SAML2'),
name: 'SAML2'
},
{
title: this.$t('setting.OAuth2'),
name: 'OAuth2'
},
{
title: this.$t('setting.WeCom'),
name: 'WeCom'

View File

@@ -28,6 +28,7 @@
import ListTable from '@/components/ListTable'
import Dialog from '@/components/Dialog'
import { importLdapUser, refreshLdapUserCache, startLdapUserCache } from '@/api/settings'
import { getErrorResponseMsg } from '@/utils/common'
export default {
name: 'ImportDialog',
@@ -103,7 +104,12 @@ export default {
importLdapUser(data).then(res => {
this.$message.success(res.msg)
// eslint-disable-next-line no-return-assign
}).finally(() => this.dialogLdapUserImportLoginStatus = false)
}).catch(error => {
const errorMessage = getErrorResponseMsg(error) || this.$t('common.imExport.ImportFail')
this.$message.error(errorMessage)
}).finally(() => {
this.dialogLdapUserImportLoginStatus = false
})
}
},
importAllUserClick() {

View File

@@ -60,6 +60,7 @@ export default {
actions: {
prop: 'id',
formatterArgs: {
canUpdate: this.$hasPerm('orgs.change_organization'),
canDelete: function({ row }) {
return !row.is_default && vm.$hasPerm('orgs.delete_organization')
},

View File

@@ -1,7 +1,10 @@
<template>
<Page>
<IBox>
<GenericCreateUpdateForm v-bind="$data" />
<GenericCreateUpdateForm
v-bind="$data"
@submitSuccess="onSubmitSuccess"
/>
</IBox>
</Page>
</template>
@@ -43,7 +46,9 @@ export default {
[
this.$t('setting.Perm'),
[
'PERM_SINGLE_ASSET_TO_UNGROUP_NODE'
'PERM_SINGLE_ASSET_TO_UNGROUP_NODE',
'TICKET_AUTHORIZE_DEFAULT_TIME',
'TICKET_AUTHORIZE_DEFAULT_TIME_UNIT'
]
]
],
@@ -81,6 +86,9 @@ export default {
methods: {
hasValidLicense() {
return this.$store.getters.hasValidLicense
},
onSubmitSuccess(res) {
this.$store.state.settings.publicSettings.TICKET_AUTHORIZE_DEFAULT_TIME = res.TICKET_AUTHORIZE_DEFAULT_TIME
}
}
}

View File

@@ -12,7 +12,7 @@
v-on="$listeners"
@confirm="onConfirm()"
>
<GenericCreateUpdateForm v-bind="iConfig" @submitSuccess="submitSuccess" />
<GenericCreateUpdateForm ref="form" v-bind="iConfig" @submitSuccess="submitSuccess" />
</Dialog>
</div>
</template>
@@ -52,6 +52,16 @@ export default {
submitSuccess(res) {
this.$emit('input', !!res[this.enableField])
this.visible = false
},
testPerformError(error) {
const data = error.response.data
for (const key of Object.keys(data)) {
let value = data[key]
if (value instanceof Array) {
value = value.join(';')
}
this.$refs.form.$refs.form.setFieldError(key, value)
}
}
}
}

View File

@@ -0,0 +1,72 @@
<template>
<BaseSMS ref="baseSms" :title="$t('setting.CMPP2')" :config="$data" />
</template>
<script>
import BaseSMS from './Base'
import { UpdateToken } from '@/components/FormFields'
export default {
name: 'CMPP2',
components: {
BaseSMS
},
data() {
const vm = this
return {
url: `/api/v1/settings/setting/?category=cmpp2`,
hasDetailInMsg: false,
visible: false,
moreButtons: [
{
title: this.$t('common.Test'),
loading: false,
callback: function(value, form, btn) {
btn.loading = true
vm.$axios.post(
`/api/v1/settings/sms/cmpp2/testing/`,
value
).then(res => {
vm.$message.success(res['msg'])
}).catch((error) => {
vm.$log.error('err occur')
vm.$refs.baseSms.testPerformError(error)
}).finally(() => { btn.loading = false })
}
}
],
fields: [
[
this.$t('common.BasicInfo'),
[
'CMPP2_HOST', 'CMPP2_PORT', 'CMPP2_SP_ID', 'CMPP2_SP_SECRET', 'CMPP2_SRC_ID', 'CMPP2_SERVICE_ID',
'CMPP2_VERIFY_SIGN_NAME', 'CMPP2_VERIFY_TEMPLATE_CODE'
]
],
[
this.$t('common.Other'),
[
'SMS_TEST_PHONE'
]
]
],
fieldsMeta: {
CMPP2_SP_SECRET: {
component: UpdateToken
}
},
submitMethod() {
return 'patch'
}
}
},
computed: {
},
methods: {
}
}
</script>
<style scoped>
</style>

View File

@@ -1,5 +1,5 @@
<template>
<BaseSMS :title="$t('setting.AlibabaCloud')" :config="$data" />
<BaseSMS ref="baseSms" :title="$t('setting.AlibabaCloud')" :config="$data" />
</template>
<script>
@@ -24,12 +24,13 @@ export default {
callback: function(value, form, btn) {
btn.loading = true
vm.$axios.post(
`/api/v1/settings/alibaba/testing/`,
`/api/v1/settings/sms/alibaba/testing/`,
value
).then(res => {
vm.$message.success(res['msg'])
}).catch(() => {
}).catch((error) => {
vm.$log.error('err occur')
vm.$refs.baseSms.testPerformError(error)
}).finally(() => { btn.loading = false })
}
}

View File

@@ -0,0 +1,88 @@
<template>
<BaseSMS ref="baseSms" :title="$t('setting.HuaweiCloud')" :config="$data" />
</template>
<script>
import BaseSMS from './Base'
import { UpdateToken } from '@/components/FormFields'
export default {
name: 'SMSHuawei',
components: {
BaseSMS
},
data() {
const vm = this
return {
url: `/api/v1/settings/setting/?category=huawei`,
hasDetailInMsg: false,
visible: false,
moreButtons: [
{
title: this.$t('common.Test'),
loading: false,
callback: function(value, form, btn) {
btn.loading = true
vm.$axios.post(
`/api/v1/settings/sms/huawei/testing/`,
value
).then(res => {
vm.$message.success(res['msg'])
}).catch((error) => {
vm.$log.error('err occur')
vm.$refs.baseSms.testPerformError(error)
}).finally(() => { btn.loading = false })
}
}
],
fields: [
[
this.$t('common.BasicInfo'),
[
'HUAWEI_APP_KEY', 'HUAWEI_APP_SECRET', 'HUAWEI_SMS_ENDPOINT'
]
],
[
this.$t('setting.VerifySignTmpl'),
[
'HUAWEI_SIGN_CHANNEL_NUM', 'HUAWEI_VERIFY_SIGN_NAME', 'HUAWEI_VERIFY_TEMPLATE_CODE'
]
],
[
this.$t('common.Other'),
[
'SMS_TEST_PHONE'
]
]
],
fieldsMeta: {
HUAWEI_VERIFY_SIGN_TMPL: {
fields: ['SIGN_NAME', 'TEMPLATE_CODE'],
fieldsMeta: {
}
},
HUAWEI_APP_SECRET: {
component: UpdateToken
},
HUAWEI_SIGN_CHANNEL_NUM: {
label: this.$t('setting.SignChannelNum')
},
HUAWEI_SMS_ENDPOINT: {
label: this.$t('setting.AppEndpoint')
}
},
submitMethod() {
return 'put'
}
}
},
computed: {
},
methods: {
}
}
</script>
<style scoped>
</style>

View File

@@ -1,5 +1,5 @@
<template>
<BaseSMS :title="$t('setting.TencentCloud')" :config="$data" />
<BaseSMS ref="baseSms" :title="$t('setting.TencentCloud')" :config="$data" />
</template>
<script>
@@ -24,12 +24,13 @@ export default {
callback: function(value, form, btn) {
btn.loading = true
vm.$axios.post(
`/api/v1/settings/tencent/testing/`,
`/api/v1/settings/sms/tencent/testing/`,
value
).then(res => {
vm.$message.success(res['msg'])
}).catch(() => {
}).catch((error) => {
vm.$log.error('err occur')
vm.$refs.baseSms.testPerformError(error)
}).finally(() => { btn.loading = false })
}
}

View File

@@ -6,6 +6,8 @@
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import SMSAlibaba from './SMSAlibaba'
import SMSTencent from './SMSTencent'
import SMSHuawei from './SMSHuawei'
import CMPP2 from './CMPP2'
export default {
name: 'Auth',
@@ -23,7 +25,7 @@ export default {
],
[
this.$t('setting.SMSProvider'), [
'ALIYUN', 'QCLOUD'
'ALIYUN', 'QCLOUD', 'HUAWEICLOUD', 'CMPP2'
]
]
],
@@ -41,6 +43,20 @@ export default {
hidden: (form) => {
return form['SMS_BACKEND'] !== 'tencent'
}
},
HUAWEICLOUD: {
label: this.$t('setting.HuaweiCloud'),
component: SMSHuawei,
hidden: (form) => {
return form['SMS_BACKEND'] !== 'huawei'
}
},
CMPP2: {
label: this.$t('setting.CMPP2'),
component: CMPP2,
hidden: (form) => {
return form['SMS_BACKEND'] !== 'cmpp2'
}
}
},
submitMethod() {

View File

@@ -37,6 +37,12 @@ export default {
[
'TERMINAL_MAGNUS_ENABLED'
]
],
[
`Web ${comp}(Luna)`,
[
'TERMINAL_GRAPHICAL_RESOLUTION'
]
]
],
fieldsMeta: {

View File

@@ -23,13 +23,15 @@ export default {
this.$t('applications.port'),
[
'http_port', 'https_port', 'ssh_port', 'rdp_port',
'mysql_port', 'mariadb_port', 'postgresql_port', 'redis_port',
'oracle_11g_port', 'oracle_12c_port'
'magnus_listen_port_range'
]
],
[this.$t('common.Other'), ['comment']]
],
fieldsMeta: {
magnus_listen_port_range: {
disabled: true
}
},
hasDetailInMsg: false
}

View File

@@ -20,17 +20,15 @@ export default {
url: '/api/v1/terminal/endpoints/',
columns: [
'name', 'host',
'http_port', 'https_port', 'ssh_port',
'rdp_port', 'mysql_port', 'mariadb_port',
'postgresql_port', 'redis_port',
'oracle_11g_port', 'oracle_12c_port',
'http_port', 'https_port', 'ssh_port', 'rdp_port',
'magnus_listen_port_range',
'date_created', 'comment', 'actions'
],
columnsShow: {
min: ['name', 'actions'],
default: [
'name', 'host', 'actions',
'http_port', 'https_port', 'ssh_port', 'rdp_port'
'http_port', 'https_port', 'ssh_port', 'rdp_port', 'magnus_listen_port_range'
]
},
columnsMeta: {
@@ -39,9 +37,10 @@ export default {
},
actions: {
formatterArgs: {
canUpdate: this.$hasPerm('terminal.change_endpoint'),
updateRoute: 'EndpointUpdate',
cloneRoute: 'EndpointCreate',
canDelete: ({ row }) => row.id !== '00000000-0000-0000-0000-000000000001'
canDelete: ({ row }) => row.id !== '00000000-0000-0000-0000-000000000001' && this.$hasPerm('terminal.delete_endpoint')
}
}
}

View File

@@ -38,6 +38,7 @@ export default {
},
actions: {
formatterArgs: {
canUpdate: this.$hasPerm('terminal.change_endpointrule'),
updateRoute: 'EndpointRuleUpdate',
cloneRoute: 'EndpointRuleCreate'
}

View File

@@ -27,7 +27,8 @@ export default {
initial: {
type: storageType,
endpoint_suffix: 'core.chinacloudapi.cn',
protocol: 'http'
protocol: 'http',
is_default: true
},
getUrl() {
const params = this.$route.params

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