Compare commits

...

967 Commits
v0.4 ... 1.4.3

Author SHA1 Message Date
老广
0665644fd0 Merge pull request #1905 from jumpserver/dev
支持命令过滤
2018-10-12 15:33:52 +08:00
ibuler
7bafa546b5 [Update] 修改版本 2018-10-12 15:05:04 +08:00
ibuler
666815b324 [Update] 修改文案 2018-10-12 15:04:01 +08:00
ibuler
532abb86b5 [Update] 添加文案说明和翻译 2018-10-11 11:10:47 +08:00
ibuler
76d4e4ad55 [Update] 修改翻译 2018-10-10 19:31:28 +08:00
ibuler
70fa43adaa [Update] 修改一些逻辑 2018-10-10 19:29:53 +08:00
ibuler
44bf01d4ed [Update] 支持命令过滤 2018-10-10 15:37:20 +08:00
老广
1341983fd3 Merge pull request #1878 from jumpserver/dev
[Update] 更改版本号
2018-10-08 19:13:12 +08:00
ibuler
78936bf9f2 [Update] 更改版本号 2018-10-08 19:03:02 +08:00
老广
9a5d3cb475 Merge pull request #1877 from jumpserver/dev
支持web sftp
2018-10-08 18:57:40 +08:00
ibuler
9bddc29da4 [Update] 暂时不存status数据 2018-09-29 10:44:52 +08:00
ibuler
d68a4d9cae [Update] 修改背景色 2018-09-29 10:28:12 +08:00
ibuler
5457118fb6 [Update] 修改css 2018-09-21 22:49:38 +08:00
ibuler
7ee68f7eeb [Update] 修改static 2018-09-21 20:35:58 +08:00
ibuler
2063f2f257 [Update] Add file to static 2018-09-21 16:37:33 +08:00
ibuler
2637c608a6 [Update] 修改gunicorn配置 2018-09-14 11:29:26 +08:00
老广
32519ea326 Merge pull request #1819 from jumpserver/dev
Dev
2018-09-14 11:08:03 +08:00
ibuler
3ce9d01b6d [Bugfix] 修复coco无法查看资产的bug 2018-09-13 12:16:49 +08:00
ibuler
310bc6ad0b [Update] 修复获取token的bug 2018-09-13 11:41:44 +08:00
ibuler
b54afbe7bb [Bugfix] 修复组织管理员无法查看用户授权的bug 2018-09-13 11:17:55 +08:00
ibuler
ab848afdb9 [Bugfix] 修复创建节点bug 2018-09-12 15:19:40 +08:00
ibuler
5bb867d10d [Update] 添加禁用用户mfa脚本 2018-09-12 11:36:27 +08:00
ibuler
0eda8865e6 [Bugfix] 修复首页显示问题 2018-09-12 11:24:07 +08:00
老广
2a37107abc Merge pull request #1805 from jumpserver/dev
Dev
2018-09-11 16:04:18 +08:00
ibuler
c78107f62f [Bugfix] Remote datepicker bug 2018-09-11 16:01:49 +08:00
ibuler
b022bf36ba [Update] 使用gthread替代eventlet 2018-09-11 12:42:49 +08:00
ibuler
6dc2272a26 [Update] 修复无法取消授权资产的前端bug 2018-09-11 12:17:17 +08:00
老广
1d462aea1b Merge pull request #1796 from jumpserver/dev
Dev
2018-09-07 12:42:55 +08:00
ibuler
88a29c0a93 [Update] 增加手动结束非正常关闭的连接 2018-09-07 12:40:26 +08:00
ibuler
9ffae722f3 [Update] 升级gunicorn版本 2018-09-05 12:26:46 +08:00
老广
9ab2f4bc56 Merge pull request #1780 from jumpserver/dev
[Update] 修改图标
2018-09-05 10:55:26 +08:00
ibuler
41e7f45c20 [Update] 修改图标 2018-09-05 10:53:12 +08:00
老广
9945ac172b Merge pull request #1775 from jumpserver/dev
[Update] 修改web terminal访问bug
2018-09-04 17:10:13 +08:00
ibuler
67ddd42b3d [Update] 修改web terminal访问bug 2018-09-04 17:09:37 +08:00
老广
03adddefa3 Merge pull request #1771 from jumpserver/dev
Dev
2018-09-04 16:05:19 +08:00
ibuler
60b7ccddc0 [Update] 更新翻译 2018-09-04 16:03:42 +08:00
老广
1194932bc0 Merge pull request #1770 from jumpserver/dev
settings加密存储
单独推送系统用户到某个资产
支持了 改密日志 操作日志
翻译更加完善,支持切换语言
2018-09-04 15:29:11 +08:00
ibuler
5c8fd91cf9 [Update] 修复上传录像文件,无法读取的问题 2018-09-04 12:19:20 +08:00
ibuler
bb13003a10 [Bugfix] 修复资产权限 2018-09-03 19:41:44 +08:00
ibuler
9a18817dbb [Bugfix] 修复rdp系统用户密码无法成功的问题 2018-09-03 19:40:06 +08:00
ibuler
2c4966c678 [Update] 修改form 2018-09-03 15:33:35 +08:00
ibuler
d1390a1cd7 [Update] 修改版本 2018-09-03 11:28:56 +08:00
老广
fe45d839fb Dev2 (#1766)
* [Update] 初始化操作日志

* [Feature] 完成操作日志记录

* [Update] 修改mfa失败提示

* [Update] 修改增加created by内容

* [Update] 增加改密日志

* [Update] 登录日志迁移到日志审计中

* [Update] change block user logic, if login success, clean block limit

*  [Update] 更新中/英文翻译(ALL) (#1662)

* Revert "授权页面分页问题"

* 增加命令导出 (#1566)

* [Update] gunicorn不使用eventlet

* [Update] 添加eventlet

* 替换淘宝IP查询接口

* [Feature] 添加命令记录下载功能 (#1559)

* [Feature] 添加命令记录下载功能

* [Update] 文案修改,导出记录、提交,取消全部命令导出

* [Update] 命令导出,修复时间问题

* [Update] paramiko => 2.4.1

* [Update] 修改settings

* [Update] 修改权限判断

* Dev (#1646)

* [Update] 添加org

* [Update] 修改url

* [Update] 完成基本框架

* [Update] 修改一些逻辑

* [Update] 修改用户view

* [Update] 修改资产

* [Update] 修改asset api

* [Update] 修改协议小问题

* [Update] stash it

* [Update] 修改约束

* [Update] 修改外键为org_id

* [Update] 删掉Premiddleware

* [Update] 修改Node

* [Update] 修改get_current_org 为 proxy对象 current_org

* [Bugfix] 解决Node.root() 死循环,移动AdminRequired到permission中 (#1571)

* [Update] 修改permission (#1574)

* Tmp org (#1579)

* [Update] 添加org api, 升级到django 2.0

* [Update] fix some bug

* [Update] 修改一些bug

* [Update] 添加授权规则org (#1580)

* [Update] 修复创建授权规则,显示org_name不是有效UUID的bug

* [Update] 更新org之间隔离授权规则,解决QuerySet与Manager问题;修复创建用户,显示org_name不是有效UUID之bug;

* Tmp org (#1583)

* [Update] 修改一些内容

* [Update] 修改datatable 支持process

* [Bugfix] 修复asset queryset 没有valid方法的bug

* [Update] 在线/历史/命令model添加org;修复命令记录保存org失败bug (#1584)

* [Update] 修复创建授权规则,显示org_name不是有效UUID的bug

* [Update] 更新org之间隔离授权规则,解决QuerySet与Manager问题;修复创建用户,显示org_name不是有效UUID之bug;

* [Update] 在线/历史/命令model添加org

* [Bugfix] 修复命令记录,保存org不成功bug

* [Update] Org功能修改

* [Bugfix] 修复merge带来的问题

* [Update] org admin显示资产详情右侧选项卡;修复资产授权添加用户,会显示其他org用户的bug (#1594)

* [Bugfix] 修复资产授权添加用户,显示其他org的用户bug

* [Update] org admin 显示资产详情右侧选项卡

* Tmp org (#1596)

* [Update] 修改index view

* [Update] 修改nav

* [Update] 修改profile

* [Bugfix] 修复org下普通用户打开web终端看不到已被授权的资产和节点bug

* [Update] 修改get_all_assets

* [Bugfix] 修复节点前面有个空目录

* [Bugfix] 修复merge引起的bug

* [Update] Add init

* [Update] Node get_all_assets 过滤游离资产,条件nodes_key=None -> nodes=None

* [Update] 恢复原来的api地址

* [Update] 修改api

* [Bugfix] 修复org下用户查看我的资产不显示已授权节点/资产的bug

* [Bugfix] Fix perm name unique

* [Bugfix] 修复校验失败api

* [Update] Merge with org

* [Merge] 修改一下bug

* [Update] 暂时修改一些url

* [Update] 修改url 为django 2.0 path

* [Update] 优化datatable 和显示组织优化

* [Update] 升级url

* [Bugfix] 修复coco启动失败(load_config_from_server)、硬件刷新,测试连接,str 没有 decode(… (#1613)

* [Bugfix] 修复coco启动失败(load_config_from_server)、硬件刷新,测试连接,str 没有 decode() method的bug

* [Bugfix] (task任务系统)修复资产连接性测试、硬件刷新和系统用户连接性测试失败等bug

* [Bugfix] 修复一些bug

* [Bugfix] 修复一些bug

*  [Update] 更新org下普通用户的资产详情 (#1619)

* [Update] 更新org下普通用户查看资产详情,只显示数据

* [Update] 优化org下普通用户查看资产详情前端代码

* [Update] 创建/更新用户的role选项;密码强度提示信息中英文; (#1623)

* [Update] 修改 超级管理员/组织管理员 在 创建/更新 用户时role的选项 问题

* [Update] 用户密码强度提示信息支持中英文

* [Update] 修改token返回

* [Update] Asset返回org name

* [Update] 修改支持xpack

* [Update] 修改url

* [Bugfix] 修复不登录就能查看资产的bug

* [Update] 用户修改

* [Bugfix] ...

* [Bugfix] 修复跳转错误的问题

*  [Update] xpack/orgs组织添加删除功能-js; 修复Label继承Org后bug; (#1644)

* [Update] 更新xpack下orgs的翻译信息

* [Update] 更新model Label,继承OrgModelMixin;

* [Update] xpack/orgs组织添加删除功能-js; 修复Label继承Org后bug;

* [Bugfix] 修复小bug

* [Update] 优化一些api

* [Update] 优化用户资产页面

* [Update] 更新 xpack/orgs 删除功能:限制在当前org下删除当前org (#1645)

* [Update] 修改版本号

* [Update] 添加功能: 语言切换(中/英);修改 header_bar <商业支持、文档>显示方式

* [Update] 中/英切换文案修改;修改django_language key 从 settings 中获取

* [Update] 修改Dashboard页面文案,支持英文

* [Update] 更新中/英文翻译(ALL)

* [Update] 解决翻译文件冲突

* [Update] 系统用户支持单独隋松

* [Update] 重置用户MFA

* [Update] 设置session空闲时间

* [Update] 加密setting配置

* [Update] 修改单独推送和测试资产可连接性

*  [Update] 添加功能:用户个人详情页添加 更改MFA操作 (#1748)

* [Update] 添加功能:用户个人详情页添加 更改MFA操作

* [Update] 删除print

* [Bugfix] 添加部分views的权限控制;从组织移除用户,同时从授权规则和用户组中移除此用户。 (#1746)

* [Bugfix] 修复上传command log 为空

* [Update] 修复执行任务的bug

* [Bugfix] 修复将用户从组内移除,其依然具有之前的组权限的bug, perms and user_groups

* [Bugfix] 修复组管理员可以访问部分url-views的bug(如: /settings/)添加views权限控制

* [Update] 修改日志滚动

* [Bugfix] 修复组织权限控制的bug (#1763)

* [Bugfix] 修复将用户从组内移除,其依然具有之前的组权限的bug, perms and user_groups

* [Bugfix] 修复组管理员可以访问部分url-views的bug(如: /settings/)添加views权限控制
2018-09-03 11:24:25 +08:00
老广
9f96f1c537 Merge pull request #1737 from jumpserver/dev
Dev
2018-08-24 12:12:38 +08:00
老广
dc918c031c Merge pull request #1736 from jumpserver/bugfix_run_adhoc
[Update] 修复执行任务的bug
2018-08-24 12:11:27 +08:00
ibuler
6b047ca702 Merge remote-tracking branch 'github/dev' into bugfix_run_adhoc 2018-08-24 12:09:29 +08:00
ibuler
47d31005b5 [Update] 修复执行任务的bug 2018-08-24 12:08:42 +08:00
老广
57e1ca93f0 Merge pull request #1715 from jumpserver/dev
[Bugfix] 修复上传command log 为空
2018-08-20 08:41:15 -05:00
ibuler
483a7617ce [Bugfix] 修复上传command log 为空 2018-08-20 21:39:15 +08:00
老广
5470ab752e Merge pull request #1702 from jumpserver/dev
Dev
2018-08-16 06:19:27 -05:00
ibuler
2dbd6b6f6e [Bugfix] 修复获取资产失败 2018-08-16 18:29:54 +08:00
老广
504d9242c6 Merge pull request #1700 from jumpserver/dev
Dev
2018-08-16 04:47:13 -05:00
ibuler
14b1e3fa13 [Update] 添加backup脚本 2018-08-16 17:08:45 +08:00
ibuler
7eeca511f1 [Bugfix] 修复打印日志的bug 2018-08-16 16:40:00 +08:00
ibuler
670c8a6d0b [Bugfix] 修复唯一认证 2018-08-16 16:32:49 +08:00
老广
a2aa923abe Merge pull request #1698 from jumpserver/dev
Dev
2018-08-15 23:53:49 -05:00
ibuler
2ac5786ba1 [Bugfix] 修复Hostname可能不唯一引起的任务执行失败 2018-08-16 12:44:39 +08:00
ibuler
5b93a1a0a5 [Bugfix] 修复单页面bug 2018-08-15 17:44:06 +08:00
ibuler
00928dd46d [Update] 不选择节点默认导出所有 2018-08-15 15:01:27 +08:00
ibuler
7ddf7f2a79 [Update] 修改cpu的数量问题 2018-08-15 12:00:47 +08:00
ibuler
3533bf588b [Bugfix] 修复修改资产报错无提示的问题 2018-08-14 18:08:07 +08:00
老广
dea007f27b Merge pull request #1688 from jumpserver/dev
[Update] 支持full value
2018-08-14 03:01:12 -05:00
ibuler
cd2b88caee [Update] 修改节点full value 2018-08-14 15:59:53 +08:00
ibuler
1877511acf [Update] 更改组织同事更改节点名称 2018-08-14 10:16:38 +08:00
ibuler
1c5ce61ed0 [Update] 修改Org刷新cache 2018-08-13 18:26:13 +08:00
老广
b1132bfc37 Merge pull request #1683 from jumpserver/dev
Dev
2018-08-13 02:19:27 -05:00
ibuler
75e67410cf [Bugfix] 修复批量更新资产和用户的bug 2018-08-13 15:01:56 +08:00
ibuler
c9d137bc20 [Update] 修改Mixin,允许org_id不填写 2018-08-13 11:44:44 +08:00
ibuler
d97e606503 [Update] 修改django版本 2018-08-13 11:02:00 +08:00
ibuler
e59b95e97a Merge remote-tracking branch 'github/dev' into dev 2018-08-13 10:46:02 +08:00
ibuler
bb6394150d [Update] 修改批量更新资产的bug 2018-08-12 15:52:16 +08:00
ibuler
2354f0c970 [Bugfix] 修复无法批量禁用启用的bug 2018-08-10 17:11:17 +08:00
老广
ae564ed0d4 Merge pull request #1670 from jumpserver/dev
Dev
2018-08-10 03:16:35 -05:00
ibuler
05ecd7497a [Update] 不可以更改root节点名称 2018-08-10 16:11:40 +08:00
ibuler
6b86b8b485 [Bugfix] 修复资产数量不对的bug 2018-08-10 14:46:04 +08:00
老广
fa0bd85fd4 Merge pull request #1667 from jumpserver/dev
[Bugfix] 修复点击资产报错
2018-08-09 21:07:40 -05:00
ibuler
7da46354ca [Update] 去掉debug 2018-08-10 09:29:26 +08:00
ibuler
e41aad1576 [Bugfix] 修复点击资产报错 2018-08-10 09:12:24 +08:00
ibuler
3f049440b7 [Bugfix] 修复可能引起的创建外键失败 2018-08-08 13:10:58 +08:00
老广
4f532f588b Merge pull request #1651 from jumpserver/dev
Dev
2018-08-07 22:48:26 -05:00
老广
a792781b98 Merge branch 'master' into dev 2018-08-07 22:48:12 -05:00
ibuler
3a4c7846bf [Bugfix] 修改创建第一个节点的Bug 2018-08-08 11:44:06 +08:00
ibuler
ccc292d9a9 [Update] 去掉debug信息 2018-08-07 18:38:46 +08:00
pufer
337338ebf3 增加多OU支持 (#1262)
* 增加多OU支持

* Update models.py

need to import LDAPSearchUnion
2018-08-07 05:11:46 -05:00
ibuler
aa3bc7b53a [Update] 权限修改 2018-08-07 16:20:22 +08:00
老广
d5451a482a Dev (#1646)
* [Update] 添加org

* [Update] 修改url

* [Update] 完成基本框架

* [Update] 修改一些逻辑

* [Update] 修改用户view

* [Update] 修改资产

* [Update] 修改asset api

* [Update] 修改协议小问题

* [Update] stash it

* [Update] 修改约束

* [Update] 修改外键为org_id

* [Update] 删掉Premiddleware

* [Update] 修改Node

* [Update] 修改get_current_org 为 proxy对象 current_org

* [Bugfix] 解决Node.root() 死循环,移动AdminRequired到permission中 (#1571)

* [Update] 修改permission (#1574)

* Tmp org (#1579)

* [Update] 添加org api, 升级到django 2.0

* [Update] fix some bug

* [Update] 修改一些bug

* [Update] 添加授权规则org (#1580)

* [Update] 修复创建授权规则,显示org_name不是有效UUID的bug

* [Update] 更新org之间隔离授权规则,解决QuerySet与Manager问题;修复创建用户,显示org_name不是有效UUID之bug;

* Tmp org (#1583)

* [Update] 修改一些内容

* [Update] 修改datatable 支持process

* [Bugfix] 修复asset queryset 没有valid方法的bug

* [Update] 在线/历史/命令model添加org;修复命令记录保存org失败bug (#1584)

* [Update] 修复创建授权规则,显示org_name不是有效UUID的bug

* [Update] 更新org之间隔离授权规则,解决QuerySet与Manager问题;修复创建用户,显示org_name不是有效UUID之bug;

* [Update] 在线/历史/命令model添加org

* [Bugfix] 修复命令记录,保存org不成功bug

* [Update] Org功能修改

* [Bugfix] 修复merge带来的问题

* [Update] org admin显示资产详情右侧选项卡;修复资产授权添加用户,会显示其他org用户的bug (#1594)

* [Bugfix] 修复资产授权添加用户,显示其他org的用户bug

* [Update] org admin 显示资产详情右侧选项卡

* Tmp org (#1596)

* [Update] 修改index view

* [Update] 修改nav

* [Update] 修改profile

* [Bugfix] 修复org下普通用户打开web终端看不到已被授权的资产和节点bug

* [Update] 修改get_all_assets

* [Bugfix] 修复节点前面有个空目录

* [Bugfix] 修复merge引起的bug

* [Update] Add init

* [Update] Node get_all_assets 过滤游离资产,条件nodes_key=None -> nodes=None

* [Update] 恢复原来的api地址

* [Update] 修改api

* [Bugfix] 修复org下用户查看我的资产不显示已授权节点/资产的bug

* [Bugfix] Fix perm name unique

* [Bugfix] 修复校验失败api

* [Update] Merge with org

* [Merge] 修改一下bug

* [Update] 暂时修改一些url

* [Update] 修改url 为django 2.0 path

* [Update] 优化datatable 和显示组织优化

* [Update] 升级url

* [Bugfix] 修复coco启动失败(load_config_from_server)、硬件刷新,测试连接,str 没有 decode(… (#1613)

* [Bugfix] 修复coco启动失败(load_config_from_server)、硬件刷新,测试连接,str 没有 decode() method的bug

* [Bugfix] (task任务系统)修复资产连接性测试、硬件刷新和系统用户连接性测试失败等bug

* [Bugfix] 修复一些bug

* [Bugfix] 修复一些bug

*  [Update] 更新org下普通用户的资产详情 (#1619)

* [Update] 更新org下普通用户查看资产详情,只显示数据

* [Update] 优化org下普通用户查看资产详情前端代码

* [Update] 创建/更新用户的role选项;密码强度提示信息中英文; (#1623)

* [Update] 修改 超级管理员/组织管理员 在 创建/更新 用户时role的选项 问题

* [Update] 用户密码强度提示信息支持中英文

* [Update] 修改token返回

* [Update] Asset返回org name

* [Update] 修改支持xpack

* [Update] 修改url

* [Bugfix] 修复不登录就能查看资产的bug

* [Update] 用户修改

* [Bugfix] ...

* [Bugfix] 修复跳转错误的问题

*  [Update] xpack/orgs组织添加删除功能-js; 修复Label继承Org后bug; (#1644)

* [Update] 更新xpack下orgs的翻译信息

* [Update] 更新model Label,继承OrgModelMixin;

* [Update] xpack/orgs组织添加删除功能-js; 修复Label继承Org后bug;

* [Bugfix] 修复小bug

* [Update] 优化一些api

* [Update] 优化用户资产页面

* [Update] 更新 xpack/orgs 删除功能:限制在当前org下删除当前org (#1645)

* [Update] 修改版本号
2018-08-06 23:34:35 -05:00
ibuler
534734881c [Update] 修改版本号 2018-08-07 11:32:11 +08:00
ibuler
8236c7baa0 Merge remote-tracking branch 'github/dev' into dev 2018-08-06 18:17:38 +08:00
BaiJiangJie
96ec5fac99 [Update] 更新 xpack/orgs 删除功能:限制在当前org下删除当前org (#1645) 2018-08-06 05:17:17 -05:00
ibuler
b7fcf80fc5 [Update] 优化用户资产页面 2018-08-06 18:16:25 +08:00
ibuler
de3695bf97 Merge remote-tracking branch 'github/dev' into dev 2018-08-06 17:17:49 +08:00
ibuler
7c814080b2 [Update] 优化一些api 2018-08-06 17:16:52 +08:00
BaiJiangJie
f5531b6065 [Update] xpack/orgs组织添加删除功能-js; 修复Label继承Org后bug; (#1644)
* [Update] 更新xpack下orgs的翻译信息

* [Update] 更新model Label,继承OrgModelMixin;

* [Update] xpack/orgs组织添加删除功能-js; 修复Label继承Org后bug;

* [Bugfix] 修复小bug
2018-08-06 03:08:05 -05:00
ibuler
b0aa9f197a [Bugfix] 修复跳转错误的问题 2018-08-06 12:20:27 +08:00
ibuler
fc156e23f3 [Bugfix] ... 2018-08-06 10:39:22 +08:00
ibuler
1a05dab572 [Update] 修改user api权限 2018-08-06 10:28:30 +08:00
ibuler
b8ecb6f81d [Update] 用户修改 2018-08-06 10:24:51 +08:00
ibuler
c01936facc [Bugfix] 修复不登录就能查看资产的bug 2018-08-03 10:32:02 +08:00
ibuler
90c629c837 [Update] 修改url 2018-08-03 10:25:41 +08:00
ibuler
c9d192eefc Merge remote-tracking branch 'github/org' into org 2018-08-02 12:37:17 +08:00
ibuler
9c4ebf9c75 [Update] 修改支持xpack 2018-08-02 12:36:31 +08:00
ibuler
37d89b4ea2 Merge branch 'tmp_org' into org 2018-08-01 13:07:12 +08:00
ibuler
87e0e1f2c4 [Update] Asset返回org name 2018-08-01 13:06:50 +08:00
ibuler
183ff09530 [Update] 修改token返回 2018-08-01 12:35:48 +08:00
BaiJiangJie
485a178c0a [Update] 创建/更新用户的role选项;密码强度提示信息中英文; (#1623)
* [Update] 修改 超级管理员/组织管理员 在 创建/更新 用户时role的选项 问题

* [Update] 用户密码强度提示信息支持中英文
2018-07-31 21:44:43 -05:00
BaiJiangJie
227cc4e965 [Update] 更新org下普通用户的资产详情 (#1619)
* [Update] 更新org下普通用户查看资产详情,只显示数据

* [Update] 优化org下普通用户查看资产详情前端代码
2018-07-30 22:18:38 -05:00
ibuler
01bef95e6e Merge remote-tracking branch 'github/org' into org 2018-07-31 11:01:31 +08:00
ibuler
672dd66023 [Bugfix] 修复一些bug 2018-07-31 11:00:48 +08:00
ibuler
c032294b14 [Bugfix] 修复一些bug 2018-07-31 11:00:21 +08:00
BaiJiangJie
6ce813faf8 [Bugfix] 修复coco启动失败(load_config_from_server)、硬件刷新,测试连接,str 没有 decode(… (#1613)
* [Bugfix] 修复coco启动失败(load_config_from_server)、硬件刷新,测试连接,str 没有 decode() method的bug

* [Bugfix] (task任务系统)修复资产连接性测试、硬件刷新和系统用户连接性测试失败等bug
2018-07-30 21:27:55 -05:00
ibuler
aefc18d73b Merge branch 'org' into tmp_org 2018-07-27 19:02:26 +08:00
ibuler
23815f87c5 [Update] 升级url 2018-07-27 18:56:40 +08:00
ibuler
206e037cf2 [Update] 优化datatable 和显示组织优化 2018-07-27 18:55:07 +08:00
ibuler
492fd98882 [Update] 修改url 为django 2.0 path 2018-07-27 17:43:18 +08:00
ibuler
d92d462dab [Update] 暂时修改一些url 2018-07-27 16:54:40 +08:00
ibuler
8afd5ef90a [Merge] 修改一下bug 2018-07-27 16:21:55 +08:00
ibuler
d3dca5d077 [Update] Merge with org 2018-07-27 16:19:19 +08:00
ibuler
9166a26f80 Merge branch 'org' into tmp_org 2018-07-27 16:16:29 +08:00
ibuler
3039284666 [Bugfix] 修复校验失败api 2018-07-27 16:15:04 +08:00
老广
2f395794ef Merge pull request #1602 from jumpserver/bai_org
[Bugfix] 修复org下用户查看我的资产不显示已授权节点/资产的bug
2018-07-27 03:13:59 -05:00
ibuler
c6d50802db [Bugfix] Fix perm name unique 2018-07-27 15:35:12 +08:00
BaiJiangJie
a10e47f72c Merge branch 'org' into michael_org 2018-07-27 15:24:52 +08:00
BaiJiangJie
3dc214d1fa [Bugfix] 修复org下用户查看我的资产不显示已授权节点/资产的bug 2018-07-27 15:24:09 +08:00
ibuler
f7fb36a176 [Update] 修改api 2018-07-27 15:21:14 +08:00
ibuler
0d7295b60e [Update] 恢复原来的api地址 2018-07-27 15:10:48 +08:00
BaiJiangJie
8f654c37a9 [Update] Node get_all_assets 过滤游离资产,条件nodes_key=None -> nodes=None 2018-07-27 13:01:41 +08:00
ibuler
b29a541aa6 [Update] Add init 2018-07-27 12:55:59 +08:00
ibuler
9fd52f6665 [Bugfix] 修复merge引起的bug 2018-07-27 12:14:13 +08:00
ibuler
f4c86718dc [Update] Merge dev 2018-07-27 12:12:32 +08:00
老广
4ff7a1f066 Merge pull request #1600 from jumpserver/bai_org
[Bugfix] 修复org下普通用户打开web终端看不到已被授权的资产和节点bug
2018-07-26 23:08:55 -05:00
ibuler
eca245fdd5 [Bugfix] 修复节点前面有个空目录 2018-07-27 11:59:39 +08:00
ibuler
7e3cf908a1 [Update] 修改get_all_assets 2018-07-27 11:09:37 +08:00
老广
dded4e10fb Merge pull request #1599 from jumpserver/dev
Dev
2018-07-26 21:45:32 -05:00
BaiJiangJie
45a354f848 [Bugfix] 修复org下普通用户打开web终端看不到已被授权的资产和节点bug 2018-07-27 10:42:32 +08:00
ibuler
8386f107c6 Merge remote-tracking branch 'github/dev' into dev 2018-07-26 19:32:23 +08:00
ibuler
5ce3dd4079 [Update] 添加unblock user脚本 2018-07-26 19:30:37 +08:00
老广
a48fb9de8d Tmp org (#1596)
* [Update] 修改index view

* [Update] 修改nav

* [Update] 修改profile
2018-07-26 05:27:09 -05:00
BaiJiangJie
04e7f54c69 [Update] org admin显示资产详情右侧选项卡;修复资产授权添加用户,会显示其他org用户的bug (#1594)
* [Bugfix] 修复资产授权添加用户,显示其他org的用户bug

* [Update] org admin 显示资产详情右侧选项卡
2018-07-26 05:25:44 -05:00
BaiJiangJie
d649aacfd6 [Update] asset platform 取消*required (#1595) 2018-07-26 05:25:14 -05:00
ibuler
7e65e44a3c [Update] 兼容guacamole手动模式上传system user是uuid 2018-07-26 18:12:25 +08:00
ibuler
74c3f12275 [Update] 添加脚本,将windows协议改为rdp 2018-07-26 14:37:10 +08:00
ibuler
8c12c382a5 [Bugfix] 修复merge带来的问题 2018-07-25 18:21:37 +08:00
ibuler
2ecfecb06f [Update] Merge with dev 2018-07-25 18:18:18 +08:00
BaiJiangJie
ac238aa36e [Update] 修改用户登录失败限制次数,3->7 (#1586)
* [Update] 修改用户登录失败限制次数,3->7

* [Update] 修改用户登录失败限制次数,3->7 - 续
2018-07-25 04:51:09 -05:00
ibuler
2abb9efe96 [Update] Org功能修改 2018-07-25 17:21:13 +08:00
BaiJiangJie
f17727deb9 [Update] 在线/历史/命令model添加org;修复命令记录保存org失败bug (#1584)
* [Update] 修复创建授权规则,显示org_name不是有效UUID的bug

* [Update] 更新org之间隔离授权规则,解决QuerySet与Manager问题;修复创建用户,显示org_name不是有效UUID之bug;

* [Update] 在线/历史/命令model添加org

* [Bugfix] 修复命令记录,保存org不成功bug
2018-07-25 02:13:53 -05:00
老广
36f1165d1b Tmp org (#1583)
* [Update] 修改一些内容

* [Update] 修改datatable 支持process

* [Bugfix] 修复asset queryset 没有valid方法的bug
2018-07-25 02:05:28 -05:00
BaiJiangJie
e7c530d8e6 [Update] 添加授权规则org (#1580)
* [Update] 修复创建授权规则,显示org_name不是有效UUID的bug

* [Update] 更新org之间隔离授权规则,解决QuerySet与Manager问题;修复创建用户,显示org_name不是有效UUID之bug;
2018-07-24 22:28:20 -05:00
老广
b156f4ad16 Tmp org (#1579)
* [Update] 添加org api, 升级到django 2.0

* [Update] fix some bug

* [Update] 修改一些bug
2018-07-24 22:21:12 -05:00
老广
16b23a37fe 增加命令导出 (#1566)
* [Update] gunicorn不使用eventlet

* [Update] 添加eventlet

* 替换淘宝IP查询接口

* [Feature] 添加命令记录下载功能 (#1559)

* [Feature] 添加命令记录下载功能

* [Update] 文案修改,导出记录、提交,取消全部命令导出

* [Update] 命令导出,修复时间问题

* [Update] paramiko => 2.4.1

* [Update] 修改settings

* [Update] 修改权限判断
2018-07-24 22:09:58 -05:00
ibuler
e41add6126 Merge remote-tracking branch 'github/dev' into dev 2018-07-25 10:23:18 +08:00
ibuler
f4c31d8e86 [Update] 修改权限判断 2018-07-25 10:22:32 +08:00
ibuler
28e8f204ec Merge remote-tracking branch 'github/dev' into dev 2018-07-24 16:41:17 +08:00
ibuler
80f147cf13 [Update] 修改settings 2018-07-24 16:40:41 +08:00
老广
c816875f28 [Update] 修改permission (#1574) 2018-07-22 23:55:13 -05:00
ibuler
1c56ba5a11 [Update] paramiko => 2.4.1 2018-07-20 18:48:57 +08:00
老广
2208d6d51e [Bugfix] 解决Node.root() 死循环,移动AdminRequired到permission中 (#1571) 2018-07-20 05:42:01 -05:00
ibuler
e3aa18ff2d [Update] 修改get_current_org 为 proxy对象 current_org 2018-07-20 17:49:47 +08:00
ibuler
b5f6f80ae6 [Update] 修改Node 2018-07-20 13:25:50 +08:00
ibuler
bbe4080008 [Update] 删掉Premiddleware 2018-07-20 12:15:45 +08:00
ibuler
cd797b18fb Merge remote-tracking branch 'github/dev' into dev 2018-07-20 11:10:51 +08:00
ibuler
b6523da603 [Update] 修改外键为org_id 2018-07-20 10:54:16 +08:00
ibuler
c24f1a0517 [Update] 修改约束 2018-07-19 19:24:29 +08:00
BaiJiangJie
83f220d7de [Feature] 添加命令记录下载功能 (#1559)
* [Feature] 添加命令记录下载功能

* [Update] 文案修改,导出记录、提交,取消全部命令导出

* [Update] 命令导出,修复时间问题
2018-07-19 05:36:52 -05:00
老广
8e42a65736 Merge pull request #1560 from wojiushixiaobai/dev
[Update]替换淘宝IP查询接口
2018-07-19 05:03:59 -05:00
wojiushixiaobai
e1fff18ce3 替换淘宝IP查询接口 2018-07-19 14:14:58 +08:00
ibuler
3052744203 [Update] 添加eventlet 2018-07-18 19:17:20 +08:00
ibuler
7924b094f8 [Update] gunicorn不使用eventlet 2018-07-18 16:15:35 +08:00
老广
3d34b06203 Merge pull request #1551 from jumpserver/dev
[Bugfix] 修复转义引起的中文乱码问题
2018-07-18 02:33:33 -05:00
ibuler
c94d018d7e [Bugfix] 修复转义引起的中文乱码问题 2018-07-18 15:31:14 +08:00
老广
53086a8977 Merge pull request #1550 from jumpserver/dev
Dev
2018-07-18 02:28:13 -05:00
ibuler
ce1fc0f3e2 [Bugfix] 修复授权列表引起的分页bug 2018-07-18 15:26:58 +08:00
ibuler
c215278978 [Bugfix] 修复授权列表引起的分页bug 2018-07-18 15:24:58 +08:00
老广
061963a316 Merge pull request #1549 from jumpserver/revert-1515-master
Revert "授权页面分页问题"
2018-07-18 01:56:18 -05:00
老广
97b240cfdd Revert "授权页面分页问题" 2018-07-18 14:55:16 +08:00
ibuler
fd5f562cbf [Update] stash it 2018-07-18 12:57:08 +08:00
老广
722bf786f1 Merge pull request #1542 from jumpserver/dev
Dev to master
2018-07-17 04:26:37 -05:00
老广
2cb5876d1a Merge branch 'master' into dev 2018-07-17 04:25:37 -05:00
ibuler
8883e0090f [Update] 更改版本号 2018-07-17 17:12:28 +08:00
ibuler
790652ff4d [Update] 修改协议小问题 2018-07-17 17:09:36 +08:00
ibuler
ff3f74abe6 [Update] 修改api 2018-07-17 12:06:47 +08:00
老广
1a49cf4d9c Merge pull request #1532 from wojiushixiaobai/dev
[Update]修改url地址
2018-07-16 22:24:09 -05:00
ibuler
4d1da56872 [Update] 修改asset api 2018-07-16 14:47:06 +08:00
老广
ff9e109a2c Merge pull request #1533 from jumpserver/update_unblockuser
[Update] 取消系统用户-清除认证信息,取消-网关rdp协议认证信息,添加解除用户登录限制功能
2018-07-16 01:42:30 -05:00
BaiJiangJie
09e636495e [Update] 模版删除unblock的id属性 2018-07-16 12:22:32 +08:00
BaiJiangJie
14076d8fe1 Merge branch 'dev' of github.com:jumpserver/jumpserver into github_dev 2018-07-16 12:13:59 +08:00
BaiJiangJie
812078331e [Update] 添加解除用户登录限制功能 2018-07-16 12:13:13 +08:00
wojiushixiaobai
696589a3cf 修改url 2018-07-16 12:13:06 +08:00
wojiushixiaobai
7eba46b303 Merge branch 'dev' of https://github.com/jumpserver/jumpserver into dev 2018-07-16 11:01:49 +08:00
wojiushixiaobai
5d63d2369f 合并冲突解决 2018-07-16 11:01:06 +08:00
ibuler
3e17e94245 [Update] 修改资产 2018-07-15 18:39:11 +08:00
ibuler
5648dcd7e7 [Update] 修改用户view 2018-07-15 00:55:05 +08:00
ibuler
28e47f33c1 [Update] 修改一些逻辑 2018-07-14 00:47:21 +08:00
老广
91b3b7ce69 Merge pull request #1515 from wstart/master
授权页面分页问题
2018-07-13 08:22:34 -05:00
老广
8c587a1376 Merge pull request #1523 from wojiushixiaobai/dev
[Update]修改url地址
2018-07-13 08:19:40 -05:00
BaiJiangJie
1182313c1a [Update] 取消系统用户-清除认证信息,取消-网关rdp协议认证信息,添加用户unblock功能 2018-07-13 19:30:48 +08:00
ibuler
7412bdcba7 [Update] 完成基本框架 2018-07-13 15:05:46 +08:00
ibuler
d6ec92d82d [Update] 修改url 2018-07-13 00:31:50 +08:00
ibuler
ad3214641d [Update] 添加org 2018-07-13 00:00:35 +08:00
wojiushixiaobai
9004351ad1 修正字符串 2018-07-12 16:47:35 +08:00
wojiushixiaobai
d3e22a2a90 修改url地址 2018-07-12 10:53:39 +08:00
wstart
fd3df81a64 Update api.py 2018-07-10 17:03:31 +08:00
ycfuck
72517a2c72 授权页面分页
授权页面分页
2018-07-10 16:33:28 +08:00
ibuler
01185a2d07 [Update] 添加gateway api 2018-07-10 16:03:05 +08:00
ibuler
8bfd2be21f [Update] 清除系统用户认证信息时提示 2018-07-10 15:50:15 +08:00
老广
b03ac46df9 Merge pull request #1456 from wojiushixiaobai/dev
[Update]修改提示文字
2018-07-10 02:35:26 -05:00
老广
76be054fcb Merge pull request #1502 from Nevss/patch-1
update docker-guacamole version  to latest
2018-07-10 02:32:06 -05:00
老广
df95c93bb1 Merge pull request #1503 from jumpserver/update_loginlog
[Update] 记录用户登录失败日志,限制用户登录失败次数
2018-07-10 02:31:39 -05:00
ycfuck
eaefb5c669 Revert "资产授权页面增加分页"
This reverts commit 0ddb9476ba.
2018-07-09 16:00:02 +08:00
林峰任1049851
0ddb9476ba 资产授权页面增加分页
资产授权页面增加分页
2018-07-09 15:55:07 +08:00
ibuler
75a9deebdd Merge remote-tracking branch 'github/dev' into dev 2018-07-08 13:03:43 +08:00
ibuler
b6cd4a20c5 [Bugfix] Data table xss attack 2018-07-08 13:02:43 +08:00
BaiJiangJie
435acafccd [Update] 在线会话,历史会话显示登录来源 Web/SSH Terminal 2018-07-06 13:22:20 +08:00
BaiJiangJie
86e4bc5e9a Merge branch 'dev' of github.com:jumpserver/jumpserver into github_dev 2018-07-06 10:57:42 +08:00
ibuler
315609bc45 Merge remote-tracking branch 'github/dev' into dev 2018-07-06 10:35:31 +08:00
ibuler
814e6a7df8 [Update] 修改sshpass 引号引入密码,特殊字符处理 2018-07-06 10:34:16 +08:00
Nevss
7a7c6d40df update docker-guacamole version to latest
update docker-guacamole version  to latest
2018-07-05 19:08:35 +08:00
BaiJiangJie
5d800fa629 [Update] 修改coco端登录限制次数逻辑 2018-07-05 17:07:03 +08:00
BaiJiangJie
bd14266abd [Update] 修改登录日志模型类的MFA字段默认状态为- 2018-07-05 16:45:05 +08:00
BaiJiangJie
88f36c6f02 Merge branch 'dev' of github.com:jumpserver/jumpserver into github_dev 2018-07-05 16:23:48 +08:00
BaiJiangJie
512fc8f8f0 [Update] 优化登录失败次数限制的逻辑,并添加系统安全设置选项 2018-07-05 16:23:33 +08:00
ibuler
37bb344166 [Bugfix] 修复网关端口不对的问题 2018-07-05 10:58:52 +08:00
ibuler
f8c2a445f7 [Bugfix] 修改gateway port 2018-07-05 10:26:16 +08:00
BaiJiangJie
ff9b1a887f [Update] 添加用户登录失败次数限制 2018-07-04 16:55:11 +08:00
BaiJiangJie
43370c547a [Update] 登录日志逻辑修改变量名 2018-07-04 10:57:37 +08:00
BaiJiangJie
2eda58eadd Merge branch 'dev' of github.com:jumpserver/jumpserver into github_dev 2018-07-03 17:48:22 +08:00
BaiJiangJie
e1be867913 [Update] 更新用户登录日志,记录登录失败日志并添加MFA启用状态信息 2018-07-03 17:47:12 +08:00
ibuler
01afcf701c Merge remote-tracking branch 'origin/dev' into dev 2018-07-03 13:24:07 +08:00
ibuler
4002289974 [Bugfix] 修复详情中删除时的bug 2018-07-03 13:23:21 +08:00
ibuler
ef9e03c7ed Merge remote-tracking branch 'github/dev' into dev 2018-07-02 12:11:45 +08:00
老广
442d4e727a Merge pull request #1492 from jumpserver/feature_telnet
[Feature] 添加功能,支持 telnet server.
2018-07-01 22:23:20 -05:00
BaiJiangJie
3993797527 Merge branch 'dev' of github.com:jumpserver/jumpserver into github_dev 2018-06-29 15:00:15 +08:00
BaiJiangJie
f1f06491d6 [Feature] 添加功能, 支持 telnet server. 2018-06-29 14:59:43 +08:00
ibuler
eb95a0a912 [Bugfix] 修改下载录像没有目录的bug 2018-06-27 10:34:16 +08:00
ibuler
401a7f88a8 [Update] 更新依赖storage的版本 2018-06-26 22:27:16 +08:00
ibuler
0a2ff83ca1 Merge remote-tracking branch 'origin/dev' into dev 2018-06-26 22:14:21 +08:00
ibuler
7276bd0b2a [Update] 更新获取session log的方法,以后统一到media/replay中 2018-06-26 22:13:39 +08:00
wojiushixiaobai
2950613b69 修改提示文字 2018-06-20 15:58:01 +08:00
老广
6fa0562d7a Merge pull request #1441 from chnliyong/master
python-gssapi libkrb5-dev for gssapi/gssapi.h
2018-06-18 21:25:15 -05:00
老广
ef22d33afa Merge pull request #1161 from Nidhoggur1993/dev
更新make_migrations.sh的migration逻辑
2018-06-13 21:53:23 -05:00
老广
86404db6c7 Merge pull request #1414 from leostudio/dev
[fixed #1404]新增Redis DB配置
2018-06-13 21:51:53 -05:00
老广
fdf2807d9b Merge pull request #1419 from jumpserver/feature_login_mode
[Update] 添加功能,系统用户选择登录模式(自动/手动登录)
2018-06-13 21:25:00 -05:00
BaiJiangJie
2e6d238c76 [Bugfix] 修复系统用户选择手动登录,ssh命令行连接获取不到login_mode字段值 2018-06-11 19:09:24 +08:00
BaiJiangJie
f5a4370b80 [Update] 系统用户选择手动登录,取消自动推送 2018-06-11 17:56:20 +08:00
BaiJiangJie
db2273ef27 Merge branch 'dev' of github.com:jumpserver/jumpserver into github_dev 2018-06-11 16:28:10 +08:00
BaiJiangJie
dd07fa678f [Update] 添加功能,系统用户选择登录模式(自动/手动登录) 2018-06-11 16:27:40 +08:00
老广
e7a731fae9 Merge pull request #1417 from jumpserver/dev
Dev
2018-06-10 22:33:57 -05:00
ibuler
73f9f54620 [Update] 修改版本号 2018-06-11 11:23:32 +08:00
梁志艳
a2f23e9681 新增Redis DB配置 2018-06-09 00:19:57 +08:00
ibuler
dbc471c195 [Bugfix] 修复小错误 2018-06-08 18:40:31 +08:00
ibuler
4c8eb4a94b Merge remote-tracking branch 'github/dev' into dev 2018-06-08 18:34:03 +08:00
老广
9946c4612f Merge pull request #1409 from jokimina/dev_for_pr2
[Update]删除示例配置中未使用的BROKER_URL配置
2018-06-08 05:32:38 -05:00
老广
8a5e1b8223 Merge pull request #1407 from jokimina/dev_for_pr
[Bugfix] 更新用户信息时不填密码也会触发密码规则验证,导致无法更新
2018-06-08 05:31:05 -05:00
老广
06ce098e00 Merge pull request #1408 from jumpserver/bugfix_user_date_expired
[Bugfix] 修复用户失效日期截止后还可登录问题
2018-06-08 05:29:12 -05:00
ibuler
5579d3f0de [Update] 修复测试网关连接known_host问题 2018-06-08 18:28:15 +08:00
xiaodong
1ef582e9ac [Update]删除示例配置中未使用的BROKER_URL配置 2018-06-08 16:39:16 +08:00
BaiJiangJie
221fae5875 [Bugfix] 修复用户失效日期截止后还可登录问题 2018-06-08 16:34:15 +08:00
xiaodong
f4084c800a [Bugfix] 更新用户信息时不填密码也会触发密码规则验证,导致无法更新 2018-06-08 16:20:28 +08:00
老广
5464ac8167 Merge pull request #1397 from jumpserver/feature_security_setting
[Feature] 增加功能,安全设置(全局MFA设置,密码强度校验)
2018-06-06 23:57:48 -05:00
BaiJiangJie
c6a8967376 [Update] 删除多余代码 2018-06-07 12:52:40 +08:00
BaiJiangJie
5f2c31e42c [Update] 重置公钥,添加成功信息(user-pubkey-update)/自动跳转下一步(first-login) 2018-06-07 12:29:55 +08:00
BaiJiangJie
64db02c3f8 [Update] 添加用户来源 db/ldap 2018-06-06 16:59:32 +08:00
BaiJiangJie
842841128f Merge branch 'dev' of github.com:jumpserver/jumpserver into github_dev 2018-06-06 15:35:40 +08:00
BaiJiangJie
b026e86741 [Update] 修改判断MFA是否全局启用的逻辑,放到User.otp_force_enabled中 2018-06-06 15:35:26 +08:00
老广
283b1c1d64 Merge pull request #1403 from jumpserver/dev
[Update] 修改清理用户组脚本
2018-06-06 02:25:10 -05:00
ibuler
c8c0479ce5 [Update] 修改清理用户组脚本 2018-06-06 15:18:23 +08:00
老广
2a30204c4e Merge pull request #1401 from jumpserver/dev
Dev
2018-06-06 01:57:41 -05:00
ibuler
30afcecf59 Merge remote-tracking branch 'github/dev' into dev 2018-06-06 14:43:11 +08:00
ibuler
80d11bbaab [Update] 删除user group继承的抽象类 2018-06-06 14:42:06 +08:00
ibuler
58e36b5f63 [Bugfix] 暂时还不能删除Tree这个类 2018-06-06 11:13:13 +08:00
ibuler
2c413e8d51 [Bugfix] 修复权限查看引起的bug 2018-06-06 10:49:16 +08:00
老广
17de014ee9 Merge pull request #1400 from jokimina/dev_for_pr
remove useless image
2018-06-05 21:19:08 -05:00
xiaodong
6b2a38c78d remove useless image 2018-06-06 08:17:45 +08:00
BaiJiangJie
fcd17460d7 [Update] 增加密码校验规则中密码最小长度限制6位 2018-06-05 17:56:58 +08:00
BaiJiangJie
4c4430661b [Merge] Merge branch dev of github.com:jumpserver/jumpserver into github_dev 2018-06-05 17:31:55 +08:00
BaiJiangJie
ee35ca3643 [Feature] 增加功能,安全设置(全局MFA设置,密码强度校验) 2018-06-05 17:26:31 +08:00
老广
99c4875dd7 Merge pull request #1395 from jumpserver/dev
Dev
2018-06-04 22:06:58 -05:00
ibuler
482d1bb27f [Update] 修改permisstion util 2018-06-01 16:22:52 +08:00
ibuler
e7c7c3a7a8 Merge branch 'dev' into perm_tree 2018-06-01 15:41:50 +08:00
ibuler
54efc88799 Merge remote-tracking branch 'github/dev' into dev 2018-06-01 15:39:43 +08:00
ibuler
a9d1538135 [Update] 修改一些方法 2018-06-01 15:34:08 +08:00
ibuler
f8ff223f90 [Update] 更新api和model方法 2018-05-31 19:47:57 +08:00
老广
a3f1622a50 Merge pull request #1371 from jumpserver/fix_asset_count
[Update] 修改资产树节点(数量)总显示此节点以及子孙节点下的所有资产总数
2018-05-31 12:31:44 +08:00
老广
0021f2e5e1 Merge pull request #1377 from wojiushixiaobai/dev
[Update]更新自动升级脚本
2018-05-31 12:29:21 +08:00
wojiushixiaobai
dbcad47214 更新升级文档 2018-05-30 11:23:54 +08:00
wojiushixiaobai
6c384b49fe 修改脚本 2018-05-30 11:19:37 +08:00
BaiJiangJie
526943a041 Merge branch 'dev' of github.com:jumpserver/jumpserver into github_dev 2018-05-29 19:26:19 +08:00
BaiJiangJie
e9d0104a69 Merge branch 'dev' of github.com:jumpserver/jumpserver into fix_asset_count_dev 2018-05-29 19:20:03 +08:00
BaiJiangJie
7c694c6885 [Update] 合并Node的parent.setter方法,修改node_fake的parent逻辑 2018-05-29 19:19:35 +08:00
ibuler
69e5ab438a [Bugfix] 解决发送邮件重启的问题 2018-05-29 18:58:56 +08:00
ibuler
757a31a52f [Update] 优化asset api, 获取时增加prefetch 2018-05-29 13:19:58 +08:00
BaiJiangJie
5b53cfb4dd [Update] 修改资产树节点(数量)总显示此节点下的资产总数 2018-05-29 11:50:09 +08:00
ibuler
b0710c42b0 Merge remote-tracking branch 'github/dev' into dev 2018-05-29 11:27:56 +08:00
老广
d9d82cea5e Merge pull request #1370 from jumpserver/fix_asset_count
[Update] 修改资产树和资产列表数量显示(游离资产)
2018-05-29 11:26:23 +08:00
老广
4f521e5a94 Merge pull request #1369 from gengkeye/master
录像回放API BUG: SessionReplayViewSet
2018-05-29 11:20:23 +08:00
BaiJiangJie
3a2973023c [Update] 修改资产树和资产列表数量显示(游离资产) 2018-05-29 11:17:09 +08:00
oldseven
4f28f85410 #1368 2018-05-29 09:51:55 +08:00
oldseven
a1905ecfdb Merge branch 'master' of https://github.com/jumpserver/jumpserver
Conflicts:
	apps/terminal/api.py
2018-05-29 09:39:30 +08:00
ibuler
47397d2308 [Bugfix] 修复网关测试连接需要ssh信任key的问题 2018-05-28 16:44:00 +08:00
ibuler
7b57d24dc9 [Update] 优化代码 2018-05-28 15:00:06 +08:00
老广
f2216274c5 Merge pull request #1366 from jumpserver/dev
Dev
2018-05-28 13:26:04 +08:00
ibuler
ffabef0040 Merge branch 'large' into dev 2018-05-28 13:20:58 +08:00
ibuler
0b4df78393 [Update] 修改permistion utils优化返回的属性 2018-05-28 13:20:26 +08:00
BaiJiangJie
2fab69ca61 Merge branch 'dev' of github.com:jumpserver/jumpserver into github_dev 2018-05-28 11:15:48 +08:00
老广
7987056b12 Merge pull request #1365 from jumpserver/dev
Dev修复
2018-05-28 11:05:11 +08:00
BaiJiangJie
f9ab0abc37 Merge branch 'dev' of github.com:jumpserver/jumpserver into github_dev 2018-05-28 10:58:47 +08:00
ibuler
0bc86543b5 [Update] 修改用户组详情显示授权资产的bug 2018-05-26 21:53:50 +08:00
ibuler
fc2a44621b [Bugfix] 修复右击位置不对的bug 2018-05-25 19:17:57 +08:00
ibuler
9b4b9e6900 [Update] 发送邮件增加用户名显示 2018-05-25 18:13:48 +08:00
老广
62c114d9c4 Merge pull request #1362 from jumpserver/dev
修改树形结构
2018-05-25 17:53:40 +08:00
ibuler
22a84d57ca [Update] 修改树形结构 2018-05-25 17:32:10 +08:00
ibuler
3f4b5ad465 [Update] Merge 2018-05-25 17:28:53 +08:00
ibuler
a96bda8ca9 [Update] 修改树形结构 2018-05-25 17:26:57 +08:00
ibuler
bff3868b8f [Update] 统一requirements版本 2018-05-25 15:23:52 +08:00
老广
8470dce805 Merge pull request #1357 from jumpserver/dev
Dev
2018-05-24 18:58:47 +08:00
ibuler
b9d0d89f66 [Update] 更改版本好 2018-05-24 13:32:20 +08:00
ibuler
5a7192e035 [Update] 修改心跳bug 2018-05-24 13:30:15 +08:00
BaiJiangJie
de2416b173 Merge branch 'dev' of github.com:jumpserver/jumpserver into github_dev 2018-05-24 12:42:06 +08:00
BaiJiangJie
486793ddcd [Bugfix] 修复取消LDAP认证不成功bug 2018-05-24 12:41:48 +08:00
老广
c40c5ac543 Merge pull request #1355 from jumpserver/dev
调整Luna树形结构修复一些bug
2018-05-24 11:39:01 +08:00
ibuler
c529061ee0 [Bugfix] 修复授权uitls的错误 2018-05-24 11:18:20 +08:00
ibuler
fe52c57a11 [Bugfix] 修复授权uitls的错误 2018-05-24 11:13:22 +08:00
ibuler
f8384973a1 [Update] 调整Luna的树形结构 2018-05-23 15:15:27 +08:00
ibuler
dc1d228e07 [Update] 更新requirements 2018-05-22 19:44:13 +08:00
ibuler
092b33d4d1 [Update] 更新requirements 2018-05-22 19:30:15 +08:00
ibuler
d615eb80b5 [Update] 优化使用storage sdk 2018-05-22 18:22:06 +08:00
ibuler
46520287d9 [Update] 添加清理重复用户组脚本 2018-05-17 15:26:22 +08:00
ibuler
4b7af1457d [Update] 系统用户新增清除认证的操作 2018-05-17 11:39:04 +08:00
BaiJiangJie
c1db33713f [Bugfix] 修复资产授权树root节点bug 2018-05-16 16:12:27 +08:00
老广
c3101dba29 Merge pull request #1334 from jumpserver/dev
Dev
2018-05-16 12:43:54 +08:00
老广
e66cfc2e13 Merge pull request #1333 from jumpserver/update_rdp
[Update] 去掉windows相关无用配置,修复资产树节点移动等问题
2018-05-16 12:42:01 +08:00
BaiJiangJie
ac67c231fc [Update] 修改前端rdp相关配置的选择逻辑 2018-05-16 12:31:13 +08:00
BaiJiangJie
599431f402 [Update] 去掉添加windows相关配置时的一些无用功能,网域网关/会话列表 2018-05-15 17:32:20 +08:00
ibuler
ed18cb317f [Bugfix] 修复创建node api的bug 2018-05-15 15:05:49 +08:00
ibuler
cb4afabc91 [Bugfix] 修复创建node节点的问题,修复windows session的问题 2018-05-15 14:44:02 +08:00
BaiJiangJie
718715cc6d [Update] 创建系统用户选择rdp协议,去掉相关配置的一些无用功能 2018-05-15 12:52:53 +08:00
BaiJiangjie
38f8c5bb72 [Bugfix] 修复资产树节点移动,资产授权树节点下的资产显示问题 2018-05-14 14:21:32 +08:00
ibuler
ebc1b4975a [Update] 自动生成秘钥去掉密码 2018-05-11 18:38:26 +08:00
ibuler
2da87151ed Merge remote-tracking branch 'github/dev' into dev 2018-05-11 12:01:54 +08:00
老广
ab9d457ce0 Merge pull request #1312 from jumpserver/bugfix_asset_tree_node
修复:资产树节点,取消model value unique,并添加唯一性校验,serializers/api中;修改新增节点计数器规则;
2018-05-11 12:01:13 +08:00
BaiJiangjie
cbc4a0a97b [Bugfix] 资产树节点Node Value值校验修改异常抛出为400 2018-05-11 11:27:48 +08:00
ibuler
0031d025aa [Bugfix] 修复上次提交引起的bug 2018-05-11 09:28:20 +08:00
BaiJiangjie
4c53eebdbe Merge branch 'dev' of github.com:jumpserver/jumpserver into github_dev 2018-05-10 21:15:49 +08:00
BaiJiangjie
2583c0b26c [Bugfix] 资产树节点,取消model value unique,并添加唯一性校验,修改新增节点计数器规则 2018-05-10 20:48:16 +08:00
ibuler
ebef4f254a Merge remote-tracking branch 'github/dev' into dev 2018-05-10 12:29:09 +08:00
老广
84d3fa6db0 Merge pull request #1303 from jumpserver/update_asset_tree
[Update] 添加功能:资产树显示全部/只显示当前节点下的资产;修复资产树节点主机数与列表显示不一致
2018-05-10 12:28:34 +08:00
ibuler
e2fa492987 Merge remote-tracking branch 'github/dev' into dev 2018-05-10 12:25:10 +08:00
ibuler
116a04da68 Merge branch 'term_js' into dev 2018-05-10 12:24:09 +08:00
ibuler
7de6af89ad [Update] 使用xterm.js替换原来的term.js 2018-05-10 12:19:37 +08:00
ibuler
8e74a04282 [Update] 修改一些bug 2018-05-09 17:35:46 +08:00
BaiJiangjie
3af01d6a31 [Update] 添加功能:资产树显示全部/只显示当前节点下的资产;修复资产树节点主机数与列表显示不一致 2018-05-08 18:02:19 +08:00
老广
5bfc34a9ea Merge pull request #1301 from jumpserver/dev
Dev
2018-05-08 11:00:26 +08:00
老广
869a84964d Merge branch 'master' into dev 2018-05-08 10:59:59 +08:00
老广
2a8358b1aa Bugfix ldap option (#1300)
* [Update] 修改replay api

* [Bugfix] 修复ldap参数改错引起的bug
2018-05-08 10:48:52 +08:00
ibuler
0a9af98729 [Bugfix] 修复ldap参数改错引起的bug 2018-05-08 10:13:33 +08:00
老广
dfd98f8aea Dev (#1299)
* [Update] 修改版本号

* [Update] 默认关闭定时任务

* [Update] 修改授权列表创建api,返回id

* [Update] 修改用户名校验规则

* Perms return (#1293)

* [Update] 修改版本号

* [Update] 默认关闭定时任务

* [Update] 修改授权列表创建api,返回id

* [Update] 修改用户名校验规则

* [Update] 添加ldap auth timeout时间

* [Update] 修改replay api (#1298)
2018-05-08 09:06:33 +08:00
老广
e9b86ca668 [Update] 修改replay api (#1298) 2018-05-08 09:05:10 +08:00
ibuler
d2b0aba620 [Update] 修改replay api 2018-05-07 19:28:01 +08:00
ibuler
7f670ab709 Merge remote-tracking branch 'github/dev' into dev 2018-05-07 13:13:08 +08:00
ibuler
941e55bdec [Update] 添加ldap auth timeout时间 2018-05-07 13:11:02 +08:00
老广
e630321e55 Perms return (#1293) (#1294)
* [Update] 修改版本号

* [Update] 默认关闭定时任务

* [Update] 修改授权列表创建api,返回id

* [Update] 修改用户名校验规则
2018-05-04 15:04:39 +08:00
老广
01d136cf1e Perms return (#1293)
* [Update] 修改版本号

* [Update] 默认关闭定时任务

* [Update] 修改授权列表创建api,返回id

* [Update] 修改用户名校验规则
2018-05-04 15:01:49 +08:00
ibuler
4ed9e11090 [Update] 修改用户名校验规则 2018-05-04 14:58:52 +08:00
ibuler
cde2e7adb0 [Update] 修改授权列表创建api,返回id 2018-05-04 14:54:58 +08:00
zhangbohan
7a27021d3d 图片调小一些 2018-05-03 23:02:12 +08:00
zhangbohan
966123e4c6 README功能说明修改 2018-05-03 22:48:24 +08:00
ibuler
e291ca9057 [Update] 默认关闭定时任务 2018-05-02 18:15:48 +08:00
老广
84003b777c Merge pull request #1283 from jumpserver/dev
更改版本号
2018-05-02 10:54:38 +08:00
老广
1edfb1cec4 [Update] 修改版本号 (#1282) 2018-05-02 10:52:57 +08:00
ibuler
0b89ff17fd [Update] 修改版本号 2018-05-02 10:48:39 +08:00
老广
9fa6b3e387 [Update] 暂时隐藏rdp的session,等待windows录像播放 (#1276) (#1278) 2018-04-27 21:23:18 +08:00
老广
c0bbda9769 [Update] 暂时隐藏rdp的session,等待windows录像播放 (#1276) 2018-04-27 15:38:13 +08:00
oldseven
0d8a600277 #1258 bug fix: 阿里云OSS录像无法回放问题 2018-04-27 15:17:32 +08:00
老广
d7a32120ba Merge pull request #1274 from jumpserver/dev
Merge with dev
2018-04-27 12:44:00 +08:00
老广
55096f9ad5 Bugfix perm asset not active (#1273)
* [Bugfix] 修复资产禁用了还可以登录的bug
2018-04-27 11:41:47 +08:00
老广
494cd760d7 Trans change (#1272)
* [Update] 更新授权规则生效日期文案
2018-04-27 11:33:28 +08:00
老广
6f494ef09c 校验用户名长度和特殊字符 (#1271)
* [Bugfix] 修复系统用户用户名校验问题

* [Update] 增加长度限制
2018-04-27 11:27:16 +08:00
wojiushixiaobai
e476cab2a1 更新自动升级脚本 (#1250) 2018-04-27 11:01:52 +08:00
老广
cc67fcb53b Merge pull request #1269 from jumpserver/dev
Dev
2018-04-26 21:02:00 +08:00
老广
b074bd8fbd Merge pull request #1267 from jumpserver/bugfix_user
[Bugfix] 修复首次登录条款问题及引导页面MFA配置问题
2018-04-26 21:00:20 +08:00
老广
627582233b Merge pull request #1268 from jumpserver/bufix_for_perm_tree
[Bugfix] 解决上次引入的bug
2018-04-26 20:59:05 +08:00
ibuler
5103dab72e [Bugfix] 解决上次引入的bug 2018-04-26 20:48:32 +08:00
老广
43c13355f2 Merge pull request #1266 from jumpserver/dev
Dev
2018-04-26 19:54:42 +08:00
老广
59eb1f8e3e Merge pull request #1265 from jumpserver/bufix_for_perm_tree
[Bugfix] 修复授权树列表和资产树列表不同的bug
2018-04-26 19:53:39 +08:00
ibuler
16aa42a861 [Bugfix] 修复授权树列表和资产树列表不同的bug 2018-04-26 19:51:32 +08:00
BaiJiangjie
0962a16b22 [Bugfix] 修复首次登录条款问题及引导页面MFA配置问题 2018-04-26 19:49:09 +08:00
wojiushixiaobai
7c35e75586 更新自动升级脚本 2018-04-25 19:49:22 +08:00
老广
787be3ff7a Merge pull request #1248 from jumpserver/dev
Bugfix 修复搜索命令的bug
2018-04-25 17:55:39 +08:00
ibuler
d5debc375e [Bugfix] 修复命令搜索异常bug 2018-04-25 17:48:44 +08:00
ibuler
40a0c4597b Merge remote-tracking branch 'github/dev' into dev 2018-04-25 15:15:36 +08:00
老广
5c17b1a7f7 Merge pull request #1240 from jumpserver/dev
首次登陆免验证码,首次登录信息变更
2018-04-25 10:00:56 +08:00
BaiJiangjie
ea2863a51b [Update] 创建用户、资产成功的提示信息可关闭 2018-04-24 22:10:52 +08:00
BaiJiangjie
102e1ca97c [Bugfix] 创建资产,资产系统置上,RDP:3389 2018-04-24 18:29:14 +08:00
BaiJiangjie
2823d02763 [Bugfix] 修复资产列表导入,资产id问题 2018-04-24 16:15:04 +08:00
BaiJiangjie
c37414045b [Bugfix] 修改用户列表导出,默认导出全部用户 2018-04-24 15:16:05 +08:00
BaiJiangjie
784bec42ff [Update] 修改用户登录,首次登录不需要验证码,登录失败时需要验证码校验 2018-04-24 13:00:36 +08:00
ibuler
9a3d0732bc [Update] 更新授权代码 2018-04-24 11:07:09 +08:00
ibuler
20a7247b16 Merge remote-tracking branch 'github/dev' into dev 2018-04-24 10:52:31 +08:00
ibuler
df60981eb4 Merge branch 'dev' of bitbucket.org:jumpserver/core into dev 2018-04-24 10:51:30 +08:00
老广
7aa2bb06e8 Merge pull request #1228 from wojiushixiaobai/dev
添加升级脚本
2018-04-24 10:41:23 +08:00
liuzheng712
536be1175a feat: replay api update
make a v2 session replay api, get the json response
2018-04-24 08:53:30 +08:00
BaiJiangjie
b5fc76d6a5 [Update] 修改用户首次登录页 2018-04-23 21:04:46 +08:00
ibuler
941dd627e3 [Update] 更新session列表 2018-04-23 11:44:09 +08:00
ibuler
8389c85054 [Update] 更改sesion表结构 2018-04-23 11:40:30 +08:00
ibuler
02ca8c3139 [Update] 更新session api 2018-04-23 11:32:46 +08:00
wojiushixiaobai
abd20f31b8 更新升级脚本 2018-04-22 22:37:51 +08:00
wojiushixiaobai
5c7acae018 更改版本号 2018-04-22 20:18:10 +08:00
wojiushixiaobai
1c623f71e0 添加升级脚本 2018-04-22 19:22:55 +08:00
BaiJiangjie
5b52b907c0 [Update] 创建用户,更新用户,添加MFA设置选项 2018-04-20 16:15:45 +08:00
BaiJiangjie
8447c6f487 [Update] 修改coco文案OTP改为MFA 2018-04-20 11:51:32 +08:00
老广
e865484a56 Merge pull request #1224 from jumpserver/dev
Dev
2018-04-20 11:36:16 +08:00
BaiJiangjie
ad6e22cd42 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2018-04-20 11:24:21 +08:00
BaiJiangjie
7a219e1710 [Update] OTP文案修改为MFA 2018-04-20 11:23:31 +08:00
老广
a0a8419c5e Merge pull request #1221 from jumpserver/dev
[Bugfix] 修复用户错误storage的bug
2018-04-19 18:27:49 +08:00
ibuler
0c24310510 [Bugfix] 修复用户错误storage的bug 2018-04-19 18:26:59 +08:00
老广
967491fba5 Merge pull request #1220 from jumpserver/dev
[Update] 修改用户详情,自己更改自己otp,提示小写
2018-04-19 17:56:41 +08:00
ibuler
9ac7f26c74 [Update] 修改用户详情,自己更改自己otp,提示小写 2018-04-19 17:55:40 +08:00
老广
910f3cdddc Merge pull request #1219 from jumpserver/dev
[Bugfix] 修复用户登录缓存设置问题
2018-04-19 17:22:04 +08:00
BaiJiangjie
f73fe1f315 [Bugfix] 修复用户登录缓存设置问题 2018-04-19 17:20:53 +08:00
老广
28acc6cc63 Merge pull request #1217 from jumpserver/dev
[Bugfix] 修复解密None的bug
2018-04-19 16:39:27 +08:00
ibuler
763cf0d981 [Bugfix] 修复解密None的bug 2018-04-19 16:35:38 +08:00
老广
611289a5ec Merge pull request #1214 from jumpserver/dev
支持二次认证
2018-04-19 12:16:25 +08:00
ibuler
95a8bf0988 Merge remote-tracking branch 'github/dev' into dev 2018-04-19 11:46:07 +08:00
ibuler
947f7d206a [Bugfix] 修复生成system user的问题 2018-04-19 11:16:52 +08:00
BaiJiangjie
12c8cf6b76 [Update] 添加OTP认证功能 2018-04-19 11:13:11 +08:00
BaiJiangjie
33bc73aba7 [Update] 添加OTP认证功能 2018-04-19 10:49:47 +08:00
老广
53c532a6ad Merge pull request #1213 from jumpserver/dev
更新资产选择
2018-04-19 10:49:01 +08:00
ibuler
035dd16b36 [Bugfix] 修复授权详情中选择用户或资产的bug 2018-04-19 10:34:17 +08:00
BaiJiangjie
f450accbf8 [Merge] with dev 2018-04-18 12:49:17 +08:00
BaiJiangjie
0bbfc7433d [Feature] 支持otp 2018-04-18 12:48:07 +08:00
ibuler
48e8785725 [Update] 修改users otp secret key 2018-04-18 12:46:25 +08:00
ibuler
b90d3306c5 [Bugfix] 去掉gateway name连接 2018-04-17 12:01:00 +08:00
ibuler
7f7d634c38 Merge remote-tracking branch 'github/master' into dev 2018-04-16 17:08:01 +08:00
老广
45b13abed3 Merge pull request #1204 from jumpserver/bugfix_user_assets
[Bugfix] 修复用户看不到我的资产的bug
2018-04-16 17:04:29 +08:00
ibuler
72cd7a3be2 [Bugfix] 修复用户看不到我的资产的bug 2018-04-16 17:01:25 +08:00
ibuler
3ccd54680e [Bugfix] 修改授权详情快速Node更改失效的bug 2018-04-14 12:18:15 +08:00
ibuler
071d14c639 [Update] 修改资产获取select 2018-04-13 21:26:10 +08:00
ibuler
823e879432 [Update] 更新节点移动交互 2018-04-13 15:48:10 +08:00
ibuler
739932b005 [Update] 更新资产导入 2018-04-12 18:50:43 +08:00
ibuler
24f144fdc3 [Bugfix] 修复导入资产时url地址问题 2018-04-12 18:17:42 +08:00
ibuler
967800391e [Update] 更新api 2018-04-12 17:31:37 +08:00
老广
3ccb6637d7 Merge pull request #1197 from jumpserver/dev
[Update] 更新迁移脚本
2018-04-12 17:03:39 +08:00
ibuler
8dfdefd428 [Update] 更新迁移脚本 2018-04-12 17:02:04 +08:00
老广
ab2c58b626 Merge pull request #1194 from jumpserver/dev
[Bugfix] 修复资产重复的bug
2018-04-12 11:08:14 +08:00
ibuler
ee4f5a8194 [Bugfix] 修复资产重复的bug 2018-04-12 11:07:40 +08:00
老广
084a76b215 Merge pull request #1193 from jumpserver/dev
更改表结构
2018-04-12 10:17:53 +08:00
ibuler
2398e9acbd 更改表截稿 2018-04-12 10:16:38 +08:00
老广
5ad8b3cc70 Merge pull request #1191 from jumpserver/dev
[Update] 更改版本号
2018-04-12 09:51:54 +08:00
ibuler
7d14e1f248 [Update] 更改版本号 2018-04-12 09:49:44 +08:00
老广
819f8f469d Merge pull request #1187 from jumpserver/dev
[Bugfix] 修复一个脚本的bug
2018-04-11 17:02:02 +08:00
ibuler
a31b7a8800 [Update] 2018-04-11 16:59:07 +08:00
老广
24bdaecab4 Merge pull request #1185 from jumpserver/dev
授权规则优化,支持细颗粒授权
2018-04-11 15:25:02 +08:00
ibuler
8b3b517bab [Update] 修改授权规则详情列表页面 2018-04-11 15:24:12 +08:00
ibuler
7fc2ef00ee [Update] 修改资产api获取的bug 2018-04-11 12:45:04 +08:00
ibuler
cbd6c3ee69 [Update] 添加迁移脚本 2018-04-11 12:23:35 +08:00
ibuler
3835adafb8 [Update] 修改bug 2018-04-11 12:13:49 +08:00
ibuler
bbaa35c773 [Update] 修改Perms 2018-04-11 11:34:15 +08:00
ibuler
0fa8287811 Merge branch 'dev' into perms 2018-04-11 10:25:18 +08:00
ibuler
78f4e5a89a [Update] 修改用户Opt 2018-04-10 21:04:56 +08:00
ibuler
3193c5549d Merge remote-tracking branch 'github/dev' into dev 2018-04-10 21:02:36 +08:00
ibuler
ed71e7d2d9 [Update] 修改用户Opt 2018-04-10 21:02:07 +08:00
ibuler
33c299566a [Update] 修复bug 2018-04-10 20:45:01 +08:00
ibuler
84634eb8c0 [Update] 修改Permr认证 2018-04-10 20:29:06 +08:00
ibuler
a4ff2181c5 [Update] 修改用户view的api 2018-04-10 09:41:06 +08:00
ibuler
fffa0def9e [Update] 修改api和view 2018-04-08 20:02:40 +08:00
ibuler
d0ede246e7 [Update] 修改授权 2018-04-08 00:16:37 +08:00
BaiJiangJie
b8b78ffeb2 Merge pull request #1162 from BaiJiangJie/dev
创建用户发送邮件
2018-04-06 15:50:50 +08:00
老广
d2d10b59ac Merge pull request #1175 from jumpserver/dev
支持sftp
2018-04-06 13:30:05 +08:00
ibuler
cb8e59edf2 [Update] 修改文案 2018-04-06 13:23:15 +08:00
ibuler
1c0d783eec [update] 添加Migrations 2018-04-06 11:36:47 +08:00
ibuler
4fa72400be Merge remote-tracking branch 'github/dev' into dev 2018-04-06 11:35:33 +08:00
ibuler
37c0062fae [Update] 添加审计模块 2018-04-06 11:27:52 +08:00
BaiJiangjie
8504c3d2fd 修复资产列表导出问题 2018-04-04 19:38:32 +08:00
老广
2d10e13057 Merge pull request #1171 from jumpserver/dev
Dev
2018-04-04 14:05:16 +08:00
ibuler
1d7ba3e204 [Bugfix] 修复小bug 2018-04-04 13:03:36 +08:00
ibuler
d966e22cf9 [Update] 修改小bug 2018-04-04 13:01:57 +08:00
ibuler
6a88fd2d60 [Update] 修改api权限 2018-04-04 08:47:02 +08:00
BaiJiangjie
b63999f385 创建用户发送邮件 2018-04-03 16:28:58 +08:00
Nidhoggur1993
4fd83bd5be Update make_migrations.sh
增加自动合并而不是在弹出warning结束
2018-04-03 16:02:51 +08:00
老广
82d06351e7 Merge pull request #1158 from jumpserver/dev
bugfix for celery log path
2018-04-03 14:50:49 +08:00
ibuler
aa8bece724 Merge branch 'ansible' into dev 2018-04-03 14:49:58 +08:00
老广
241bdff7c8 Merge pull request #1156 from jumpserver/dev
Dev
2018-04-03 12:18:41 +08:00
ibuler
168335a381 [Update] 更新内容 2018-04-03 12:14:58 +08:00
ibuler
121726b731 Merge remote-tracking branch 'github/dev' into dev 2018-04-03 10:44:35 +08:00
ibuler
0b812a03c6 [Bugfix] 修复 ApiUpdateAttr的bug 2018-04-03 10:36:11 +08:00
ibuler
79ae6efafb [Bugfix] 修复没有Log path引起的bug 2018-04-02 19:14:42 +08:00
老广
d2f108eeec Merge pull request #1154 from BaiJiangJie/master
终端管理用户显示,资产导入IP字段空格,资产导入domain字段保存
2018-04-02 19:03:51 +08:00
BaiJiangJie
c48beb10af Merge pull request #1 from BaiJiangJie/dev
终端管理用户,资产列表IP字段,domain保存对象
2018-04-02 18:52:52 +08:00
老广
ea9264ec49 Merge pull request #1153 from BaiJiangJie/dev
修复导入资产列表IP字段前后有空格问题,导入资产列表时domain保存为对象
2018-04-02 18:43:33 +08:00
BaiJiangjie
5b65ed8a19 修复导入资产列表IP字段前后有空格问题,导入资产列表时doamin保存为对象 2018-04-02 18:32:24 +08:00
ibuler
951ac252fe [Merge] 更新ansible功能 2018-04-02 17:45:17 +08:00
ibuler
24c4e1df50 [Update] 资产,系统用户,管理用户等支持查看日志 2018-04-02 16:55:39 +08:00
ibuler
d247e49b70 [Update] 修改celery位置 2018-04-02 15:54:49 +08:00
ibuler
a4c843ff13 [Update] 迁移celery到ops 2018-04-02 13:19:31 +08:00
BaiJiangjie
ec6103448e 使终端管理的应用用户不显示在用户组里 2018-04-02 12:02:06 +08:00
老广
0ca14463cd Merge pull request #1147 from jumpserver/dev
支持网域功能
2018-04-02 10:59:55 +08:00
ibuler
df80e8047a [Update] 修改日志 2018-04-01 23:45:37 +08:00
ibuler
09fc2776df [Update] Support history view 2018-03-30 22:03:43 +08:00
ibuler
e4c2affb5f Merge branch 'rev' into dev 2018-03-29 15:07:49 +08:00
ibuler
6fae4d5dee [Bugfix] for some commit 2018-03-29 15:06:35 +08:00
ibuler
0c80e3e815 [rev] for api commit 2018-03-29 15:02:09 +08:00
ibuler
d32f070b5c [Update] 修改Inverntoy,增加更多属性 2018-03-28 19:37:29 +08:00
ibuler
b959f1f68b Merge branch 'bugfix_ip_lookup' into dev 2018-03-28 16:47:44 +08:00
ibuler
e1cab35db0 [Bugfix] 修改获取城市 2018-03-28 16:47:36 +08:00
ibuler
8014cc48b6 [Update] 修改settings和缩进 2018-03-28 15:47:20 +08:00
ibuler
829f57e2d7 [Bugfix] 修复小bug 2018-03-28 14:35:27 +08:00
ibuler
5f0b4a4b63 [Update] 修改一些翻译 2018-03-28 11:28:40 +08:00
ibuler
c5af4d47eb Merge branch 'some_auth_api' into dev 2018-03-27 18:37:04 +08:00
ibuler
c37bfb682a [Update] 添加设置认证api和创建用户时可以不选择组 2018-03-27 18:34:41 +08:00
ibuler
3aaea6cc31 Merge branch 'bugfix_useradd' into dev 2018-03-27 17:48:35 +08:00
ibuler
f85e5b6f75 [Bugfix] 修改添加用户的bug 2018-03-27 17:47:53 +08:00
ibuler
d598571dc1 [Update] 添加一个log 2018-03-27 16:51:27 +08:00
ibuler
e873be95d5 [Update] 导入到当前node 2018-03-26 16:16:18 +08:00
ibuler
dbaa4ab502 [Update] 修改资产导入的事务问题 2018-03-26 15:55:31 +08:00
ibuler
ac1e319cd9 [Merge] merge with vpc 2018-03-26 09:56:25 +08:00
ibuler
a39424ac09 [Update] 更新jms脚本 2018-03-25 21:48:41 +08:00
ibuler
75319b99ae [Update] 更新ap请求 2018-03-25 21:47:29 +08:00
ibuler
7f4f67aa8d [Update] 支持网域 2018-03-23 19:46:46 +08:00
ibuler
fe1862120f [Update] 删掉集群等等 2018-03-21 18:13:16 +08:00
ibuler
759760e7d9 [Update] 服务器可以生成用户密钥 2018-03-21 15:22:10 +08:00
老广
15b74da57c Merge pull request #1100 from jumpserver/dev
Dev
2018-03-21 11:59:05 +08:00
ibuler
6f29cf5ddd [Update] 修改quickstart 2018-03-21 09:26:14 +08:00
ibuler
0bba840e4d Merge branch 'pubkey' into dev 2018-03-19 16:24:59 +08:00
ibuler
2156e0f51a [Update] 修改jms 2018-03-19 16:24:50 +08:00
ibuler
2fc9c04228 [Bugfix] 修复更新系统用户后关联节点丢失的问题 2018-03-19 15:32:02 +08:00
ibuler
d8e614c54d [Update] 管理脚本 2018-03-19 11:26:51 +08:00
ibuler
2ab26e25cc [Update] 改为supervisor启动 2018-03-16 10:43:21 +08:00
ibuler
f195b309d4 [Bugfix] 全选后编辑checkbox问题 2018-03-16 10:42:53 +08:00
ibuler
5e41c5cadc [Update] 删掉没用的脚本 2018-03-15 18:14:43 +08:00
ibuler
d92d09bd80 [Update] 更新说明 2018-03-15 09:34:19 +08:00
liuzheng
227804b7ab Update step_by_step.rst 2018-03-15 07:33:56 +08:00
liuzheng
eeae989c06 Update step_by_step.rst 2018-03-15 07:30:32 +08:00
liuzheng
c67a9eb845 Update quickstart.rst 2018-03-15 07:27:30 +08:00
老广
35ed881139 Merge pull request #1070 from jumpserver/dev
[Update] 升级版本号
2018-03-14 20:03:02 +08:00
ibuler
a58db9826e [Update] 升级版本号 2018-03-14 19:57:35 +08:00
老广
5250a223c3 Merge pull request #1069 from jumpserver/dev
Dev to master
2018-03-14 19:56:04 +08:00
ibuler
15427fb743 [Update] 还原ldap配置 2018-03-14 19:37:36 +08:00
ibuler
7874a1539c [Update] 修改配置,默认先从数据库认证 2018-03-14 19:33:48 +08:00
老广
9f23520712 Merge pull request #1068 from jumpserver/dev
merge dev to master
2018-03-14 18:39:31 +08:00
ibuler
63f1ec839c [Update] merge with master 2018-03-14 18:38:54 +08:00
ibuler
7e087ad5c6 Merge remote-tracking branch 'github/dev' into dev 2018-03-14 18:36:22 +08:00
ibuler
93895b6b92 [Update] 修改一些文案说明和翻译 2018-03-14 18:14:37 +08:00
q4speed
4d7fbcc49b 修复文字过长bug 2018-03-14 18:01:37 +08:00
ibuler
3822d51888 Merge branch 'docs' into dev 2018-03-14 16:21:53 +08:00
ibuler
aa5187dc39 Merge remote-tracking branch 'github/dev' into dev 2018-03-14 16:21:35 +08:00
ibuler
e9be4f51e5 [Update] 删除资产详情中的windows等测试连接 2018-03-14 16:19:59 +08:00
ibuler
496403bde0 [Update] 修改docs 2018-03-14 16:08:27 +08:00
ibuler
4f522c1cd1 [Update] docs 2018-03-14 16:06:05 +08:00
ibuler
5c3846a886 [Update] 添加Faq 2018-03-14 15:54:27 +08:00
ibuler
42f297e6c4 [Update] 更新一些文案 2018-03-14 13:13:32 +08:00
fit2cloud-fengyi
5d88c7b779 add git install 2018-03-14 13:09:51 +08:00
ibuler
74047d19d0 Merge branch 'dev' of bitbucket.org:jumpserver/core into dev 2018-03-14 12:18:17 +08:00
ibuler
8b63961d4d [Update] 默认开启定时检测,运行失败默认log为warn 2018-03-14 12:17:48 +08:00
liuzheng712
9c2470b67c feat: update the sdk 2018-03-13 19:05:16 +08:00
liuzheng
240faee2b4 Update upgrade.rst 2018-03-13 14:53:23 +08:00
liuzheng
3d3d9b565a Update upgrade.rst 2018-03-13 14:51:22 +08:00
ibuler
2e8f5919a8 [Update] 默认关闭自动定时推送功能 2018-03-13 09:57:38 +08:00
ibuler
faf6a7b623 [Update] 删掉token api调用次数限制 2018-03-12 20:29:27 +08:00
ibuler
8e4ab9f3a9 [Update] 支持获取token的用户 2018-03-12 19:39:27 +08:00
ibuler
e4823a21e3 [Update] 优化部分代码 2018-03-12 18:29:06 +08:00
ibuler
634af19945 Merge branch 'dev' of bitbucket.org:jumpserver/core into dev 2018-03-12 18:24:14 +08:00
liuzheng712
b2ae0e8f44 fix: download update 2018-03-12 17:53:22 +08:00
ibuler
4a3e5e9b22 Merge branch 'dev' of bitbucket.org:jumpserver/core into dev 2018-03-12 16:44:10 +08:00
ibuler
43b4b7c55e [Feature] 资产节点增加批量测试可连接性和更新硬件信息 2018-03-12 11:41:12 +08:00
fit2cloud-fengyi
b19a0e1cdf 删除faq和久的intro 2018-03-12 10:33:55 +08:00
liuzheng
b92ccdd05e fix: update the jms-storage==0.0.11 2018-03-12 10:30:49 +08:00
fit2cloud-fengyi
34cc6abec5 Merge remote-tracking branch 'origin/docs' into docs 2018-03-12 10:28:54 +08:00
ibuler
f59f03adfd [Update] 支持使用环境变量关闭定时任务 2018-03-12 10:20:23 +08:00
老广
0f2b0b146d Update README.md 2018-03-12 09:17:34 +08:00
老广
e085fee101 Update README.md 2018-03-12 09:16:54 +08:00
ibuler
fe03011177 [Update] 不允许添加应用程序类型的用户 2018-03-11 19:46:40 +08:00
chnliyong
6035d1f130 python-gssapi libkrb5-dev for gssapi/gssapi.h 2018-03-10 10:46:28 +08:00
ibuler
9ffb079c8f [Update] 增加修改system user auth 的api 2018-03-09 12:53:08 +08:00
ibuler
050b6e6d88 [Update] 修改一些docs 2018-03-09 12:12:10 +08:00
fit2cloud-fengyi
422990a992 添加 venv 到 ignore 文件 2018-03-09 10:33:57 +08:00
老广
06ff7f75ce Merge pull request #1061 from toryzen/master
Update step_by_step.rst
2018-03-09 10:07:38 +08:00
toryzen
0b82b97c66 Update step_by_step.rst
删除多余的3.2章节
2018-03-09 10:04:59 +08:00
ibuler
fcbe94de37 [Update] 更新持久化 2018-03-08 12:28:45 +08:00
老广
ce35a8e2ac Merge pull request #1059 from jumpserver/dev
Dev
2018-03-08 12:11:50 +08:00
老广
bebd71895c Merge pull request #1058 from jumpserver/docs
Docs
2018-03-08 12:11:04 +08:00
ibuler
ea18727bdb Merge remote-tracking branch 'github/docs' into docs 2018-03-08 12:08:45 +08:00
fit2cloud-fengyi
b90c5d1e43 英文格式 2018-03-08 12:00:14 +08:00
liuzheng
0be31a8078 Merge pull request #1056 from jumpserver/dev
revert: add the old apps/users/migrations/0002_auto_20171225_1157.py
2018-03-08 09:18:25 +08:00
liuzheng712
9162f4a226 revert: add the old apps/users/migrations/0002_auto_20171225_1157.py 2018-03-08 09:13:40 +08:00
老广
3bf7d061c9 Merge pull request #1055 from jumpserver/dev
Dev
2018-03-07 23:31:31 +08:00
ibuler
51e15b583f Merge remote-tracking branch 'origin/dev' into dev 2018-03-07 23:29:17 +08:00
ibuler
60427b9908 [Update] README.md 2018-03-07 23:28:30 +08:00
老广
60bc42f630 Dev (#1054)
* [Update] 修改 success message, 添加资产组时可以添加资产

* [Update] system user form add label

* [Update] set default cluster

* [Update] 修改一些翻译

* [Bugfix] 修复重置密码bug

* [Bugfix] 默认default cluster

* [Bugfix] 用户添加报错

* 修改tab样式

* [Bugfix] 修复了一些显示上的bug

* 修复全选按钮在搜索后仍然选择全部的问题

* [Bugfix] 修复以下bug
1. 查看执行历史异常
2. 用户授权资产页显示message

* [Update] api 返回platform, 并增加web terminal nav

* [Feature] 添加setting页面

* [Feature] 添加basic settings

* [Update] 修改翻译

* [Update] 修改config

* [Update] 启动加载common setting

* [Bugfix] 修复cluster创建的bug

* [Bugfix] 修复title显示Jumpserver

* [Bugfix] setting tables not found

* [Bugfix] settings add option

* [Feature] 添加后端paging

* [Bugfix] 资产列表选择别的页会报错

* [Update] check all 只选择当前页面

* [Bugfix] user login ip

* [Bugfix] for login ip

* [Bugfix] 修复资产列表显示bug

* [Remove] labels

* [Bugfix] task运行失败,因为tasks没有设置

* [Feature] 增加标签

* [Bugfix] 读取不到prefix

* For storage

* [Change] 修改部分翻译

* [Update] 启用ldap移动位置

* [Update] 修改翻译

* [Feature] 支持es存储命令

* Update README.md

* [Feature] 添加es支持

* [update] 修改用户创建时 姓名和用户名的位置

* [Update] 修改install.md

* [Update] remote default PAGE_SIZE stting

* [Feature] terminal config load

* [Feature] es support

* [Update] 修改requirement

* [Update] 修改requirements

* [Update] 修改dictfiled

* [Fix] 修改Logger

* [Bugfix] 倒序显示

* [Update] 修改默认头像和logo

* [Update] 修改django-celery-beat的版本

* [Feature] 添加修改用户密码api

* add logo test

* [Bugfix] 修复一些bug

* [Update] 修改copyrite

* [Update] 修改copyright

* Update ISSUE_TEMPLATE.md

* [Update] 修改禁止排序的颜色

* [Feature] 标签管理功能

* [Bugfix] git status

* [Model] 修改create_by字段

* [Update] 修改位置

* [Update] 修改签名md5算法

* [Feature] 资产列表标签搜索

* [Feature] 添加资产详情标签

* [Bugfix] 修复资产搜索bug

* [Update] ansible disk bug

* [Update] ansible disk bug

* [Bugfix] 修复获取kvmcpu的bug

* [Bugfix] 修复bsd获取cpu数量bug

* [Bugfix] 修改翻译

* [Bugfix] 资产model 太长

* [Bugfix] 修改项目结构描述

修正"项目多语言目录"

* Update project_structure.md

* [Update] add debug log

* refactor: rename folder i18n

* [Feature] 添加链接token

* [Feature] Label 删除修改

* [Update] 修改部分翻译

* [Update] 修改小bug

* [Update] 修复获取资产信息异常bug

* [Bugfix] 修复系统用户上传秘钥的bug

* [Update] 修改获取资产信息产生的异常

* [Update] 删除部分资产属性

* [Bugfix] 资产批量便捷

* [Update] 修改认证

* [Feature] 支持popover

* [Feature] tree

* [Feature] 添加资产树

* [Feature] 使用ztree

* [Feature] tree增删功能

* [Bugfix] 修复组详情bug

* [Bugfix] 修复组详情bug

* [Bugfix] 修改创建label时报错的bug

* [Bugfix] 修改label api bug

* [Update] 去掉资产组添加

* [Update] 修改ztrr

* Update README.md

* [Update] 修改资产创建

* [Bugfix] 修复ldap认证bug

* [Update] 修改一处翻译

* [Update] 更改授权规则前commit

* [Abandon] ...

* Update README.md

* Update README.md

* Update README.md

* [Feature] 完成资产授权和资产添加

* [Update] 修改授权

* [Bugfix] 修改创建系统用户的bug

* feat: rdp support

* [Update] 拆分asset api module

* [Update] 资产列表选中和移除资产

* [Feature] 更改perms api

* [Update] 使用资产树,去掉集群和资产组

* [Update] 修改系统用户推送,拆分assets的部分模块

* [Update] 完成树形改造

* [Update] 完成资产书

* [Update] 修改资产model

* ubuntu16.04 deb_requirements.txt update (#1007)

* Update run server.py (#915)

Fix  for not callable error when  config.py not exists

* [Update]一些修改

* [Update] 修改初始

* feat: replay setting page and api

* 增加隐藏树功能

* [Update] 修改翻译

* 对齐菜单文字。修改英文

* feat: update app setting

* fix: app get replay storage

* [Update] 修改文案

* [Docs] 初始化doc

* [Bugfix] 用户csv导入编码问题

* [Update] 修改设置的一些require

* [Bugfix] 修复管理用户无法查看的bug

* [Update] 修改授权api, windows资产只有rdp协议,linux只有ssh协议

* [Update] terminal可以更改名称

* [Update] 统一copyright

* [Update] 修改文档

* [Bugfix] 修复资产禁用还可以登录

* [Update] 修改文案

* [Update] 支持拖拽更新

* [Bugfix] 修复bug,修改celery beat版本依赖

* [Update] 修改一些小问题

* 添加普通用户使用内容

* [Update] 修改一些文案

* Update README.md

* Update README.md

* Update README.md

* 用户列表

* [Update] 修改一些bug和文案

* [Delete] 删除build 页面

* 用户管理模块更新

* 用户管理模块更新

* [Update] 修改conf

* [Update] bugfix

* [Update] 更新文档地址

* 用户管理模块更新

* [Update] 修改部分翻译和文档

* 管理用户

* 权限管理文档

* 权限管理文档命名修改

* 作业中心文档

* 细节改动

* 添加贡献者

* 截图

* [Update] 修改一些bug

* add ignore

* [Update] 修改文档

* fix bug

* add ignore

* [Igore] force

* [Update] 修改链接

* [Update] 修改版本

* 标签管理

* 会话管理,权限管理,作业中心文档

* [Update] 增加批量终端session api

* [Update] 修改Node value唯一

* 系统设置

* 快速启动修改

* [Bugfix] 修复首页无法显示数据的bug

* feat: s3 replay file get

* feat: update

* [Update] 修改bug

* [Update] 修改Model

* [Update] 修改文档地址

* [Update] 修改gua的安装

* [Update] 修改推送任务

* [Update] 修改安装分支

* feat: update the requirements

* [Update] 更新README

* fix: remove gssapi==1.2.4

* [Update] 修改readme

* fix(config = settings.TERMINAL_REPLAY_STORAGE.items()):

* fix: AttributeError: 'SessionReplayViewSet' object has no attribute 'ACCESS_KEY'

* [Update] 修改readme

* [Update] index

* [Update] 修改Linux截图
2018-03-07 23:26:50 +08:00
ibuler
63924a2ef4 Merge branch 'master' of github.com:jumpserver/jumpserver into dev 2018-03-07 23:26:25 +08:00
ibuler
2842153e21 [Update] 修改Linux截图 2018-03-07 23:21:29 +08:00
老广
3643ee1f89 Dev (#1053)
* [Update] 修改 success message, 添加资产组时可以添加资产

* [Update] system user form add label

* [Update] set default cluster

* [Update] 修改一些翻译

* [Bugfix] 修复重置密码bug

* [Bugfix] 默认default cluster

* [Bugfix] 用户添加报错

* 修改tab样式

* [Bugfix] 修复了一些显示上的bug

* 修复全选按钮在搜索后仍然选择全部的问题

* [Bugfix] 修复以下bug
1. 查看执行历史异常
2. 用户授权资产页显示message

* [Update] api 返回platform, 并增加web terminal nav

* [Feature] 添加setting页面

* [Feature] 添加basic settings

* [Update] 修改翻译

* [Update] 修改config

* [Update] 启动加载common setting

* [Bugfix] 修复cluster创建的bug

* [Bugfix] 修复title显示Jumpserver

* [Bugfix] setting tables not found

* [Bugfix] settings add option

* [Feature] 添加后端paging

* [Bugfix] 资产列表选择别的页会报错

* [Update] check all 只选择当前页面

* [Bugfix] user login ip

* [Bugfix] for login ip

* [Bugfix] 修复资产列表显示bug

* [Remove] labels

* [Bugfix] task运行失败,因为tasks没有设置

* [Feature] 增加标签

* [Bugfix] 读取不到prefix

* For storage

* [Change] 修改部分翻译

* [Update] 启用ldap移动位置

* [Update] 修改翻译

* [Feature] 支持es存储命令

* Update README.md

* [Feature] 添加es支持

* [update] 修改用户创建时 姓名和用户名的位置

* [Update] 修改install.md

* [Update] remote default PAGE_SIZE stting

* [Feature] terminal config load

* [Feature] es support

* [Update] 修改requirement

* [Update] 修改requirements

* [Update] 修改dictfiled

* [Fix] 修改Logger

* [Bugfix] 倒序显示

* [Update] 修改默认头像和logo

* [Update] 修改django-celery-beat的版本

* [Feature] 添加修改用户密码api

* add logo test

* [Bugfix] 修复一些bug

* [Update] 修改copyrite

* [Update] 修改copyright

* Update ISSUE_TEMPLATE.md

* [Update] 修改禁止排序的颜色

* [Feature] 标签管理功能

* [Bugfix] git status

* [Model] 修改create_by字段

* [Update] 修改位置

* [Update] 修改签名md5算法

* [Feature] 资产列表标签搜索

* [Feature] 添加资产详情标签

* [Bugfix] 修复资产搜索bug

* [Update] ansible disk bug

* [Update] ansible disk bug

* [Bugfix] 修复获取kvmcpu的bug

* [Bugfix] 修复bsd获取cpu数量bug

* [Bugfix] 修改翻译

* [Bugfix] 资产model 太长

* [Bugfix] 修改项目结构描述

修正"项目多语言目录"

* Update project_structure.md

* [Update] add debug log

* refactor: rename folder i18n

* [Feature] 添加链接token

* [Feature] Label 删除修改

* [Update] 修改部分翻译

* [Update] 修改小bug

* [Update] 修复获取资产信息异常bug

* [Bugfix] 修复系统用户上传秘钥的bug

* [Update] 修改获取资产信息产生的异常

* [Update] 删除部分资产属性

* [Bugfix] 资产批量便捷

* [Update] 修改认证

* [Feature] 支持popover

* [Feature] tree

* [Feature] 添加资产树

* [Feature] 使用ztree

* [Feature] tree增删功能

* [Bugfix] 修复组详情bug

* [Bugfix] 修复组详情bug

* [Bugfix] 修改创建label时报错的bug

* [Bugfix] 修改label api bug

* [Update] 去掉资产组添加

* [Update] 修改ztrr

* Update README.md

* [Update] 修改资产创建

* [Bugfix] 修复ldap认证bug

* [Update] 修改一处翻译

* [Update] 更改授权规则前commit

* [Abandon] ...

* Update README.md

* Update README.md

* Update README.md

* [Feature] 完成资产授权和资产添加

* [Update] 修改授权

* [Bugfix] 修改创建系统用户的bug

* feat: rdp support

* [Update] 拆分asset api module

* [Update] 资产列表选中和移除资产

* [Feature] 更改perms api

* [Update] 使用资产树,去掉集群和资产组

* [Update] 修改系统用户推送,拆分assets的部分模块

* [Update] 完成树形改造

* [Update] 完成资产书

* [Update] 修改资产model

* ubuntu16.04 deb_requirements.txt update (#1007)

* Update run server.py (#915)

Fix  for not callable error when  config.py not exists

* [Update]一些修改

* [Update] 修改初始

* feat: replay setting page and api

* 增加隐藏树功能

* [Update] 修改翻译

* 对齐菜单文字。修改英文

* feat: update app setting

* fix: app get replay storage

* [Update] 修改文案

* [Docs] 初始化doc

* [Bugfix] 用户csv导入编码问题

* [Update] 修改设置的一些require

* [Bugfix] 修复管理用户无法查看的bug

* [Update] 修改授权api, windows资产只有rdp协议,linux只有ssh协议

* [Update] terminal可以更改名称

* [Update] 统一copyright

* [Update] 修改文档

* [Bugfix] 修复资产禁用还可以登录

* [Update] 修改文案

* [Update] 支持拖拽更新

* [Bugfix] 修复bug,修改celery beat版本依赖

* [Update] 修改一些小问题

* 添加普通用户使用内容

* [Update] 修改一些文案

* Update README.md

* Update README.md

* Update README.md

* 用户列表

* [Update] 修改一些bug和文案

* [Delete] 删除build 页面

* 用户管理模块更新

* 用户管理模块更新

* [Update] 修改conf

* [Update] bugfix

* [Update] 更新文档地址

* 用户管理模块更新

* [Update] 修改部分翻译和文档

* 管理用户

* 权限管理文档

* 权限管理文档命名修改

* 作业中心文档

* 细节改动

* 添加贡献者

* 截图

* [Update] 修改一些bug

* add ignore

* [Update] 修改文档

* fix bug

* add ignore

* [Igore] force

* [Update] 修改链接

* [Update] 修改版本

* 标签管理

* 会话管理,权限管理,作业中心文档

* [Update] 增加批量终端session api

* [Update] 修改Node value唯一

* 系统设置

* 快速启动修改

* [Bugfix] 修复首页无法显示数据的bug

* feat: s3 replay file get

* feat: update

* [Update] 修改bug

* [Update] 修改Model

* [Update] 修改文档地址

* [Update] 修改gua的安装

* [Update] 修改推送任务

* [Update] 修改安装分支

* feat: update the requirements

* [Update] 更新README

* fix: remove gssapi==1.2.4

* [Update] 修改readme

* fix(config = settings.TERMINAL_REPLAY_STORAGE.items()):

* fix: AttributeError: 'SessionReplayViewSet' object has no attribute 'ACCESS_KEY'

* [Update] 修改readme

* [Update] index
2018-03-07 23:18:34 +08:00
ibuler
14510b08a4 Merge branch 'github_master' into github_dev 2018-03-07 23:13:17 +08:00
ibuler
f2251bbb32 [Update] index 2018-03-07 22:38:37 +08:00
liuzheng712
580f1f2a82 Merge branch 'dev' of bitbucket.org:jumpserver/core into dev 2018-03-07 22:15:41 +08:00
liuzheng712
3c6c05f83e fix: update 2018-03-07 22:15:17 +08:00
ibuler
4e0f81e447 Merge remote-tracking branch 'origin/dev' into dev 2018-03-07 22:15:06 +08:00
ibuler
629ff39026 [Update] 修改readme 2018-03-07 22:10:30 +08:00
liuzheng712
51d7e51119 fix: AttributeError: 'SessionReplayViewSet' object has no attribute 'ACCESS_KEY' 2018-03-07 22:05:50 +08:00
liuzheng712
3c32048aa8 Merge branch 'dev' of bitbucket.org:jumpserver/core into dev 2018-03-07 21:55:33 +08:00
liuzheng712
166a3fff5c fix(config = settings.TERMINAL_REPLAY_STORAGE.items()): 2018-03-07 21:54:58 +08:00
ibuler
476a00d270 Merge branch 'docs' into dev 2018-03-07 21:41:08 +08:00
ibuler
2e51a28deb Merge remote-tracking branch 'origin/dev' into dev 2018-03-07 21:39:04 +08:00
ibuler
0bd33ec823 [Update] 修改readme 2018-03-07 21:38:34 +08:00
liuzheng712
813e10ed11 fix: remove gssapi==1.2.4 2018-03-07 21:35:18 +08:00
ibuler
8b4d7c0dd7 Merge remote-tracking branch 'origin/dev' into dev 2018-03-07 21:33:26 +08:00
ibuler
395dc2e505 [Update] 更新README 2018-03-07 21:32:49 +08:00
liuzheng712
2406a7590b feat: update the requirements 2018-03-07 21:28:06 +08:00
ibuler
f86322fa15 [Update] 修改安装分支 2018-03-07 21:26:19 +08:00
ibuler
88c10e851b [Update] 修改推送任务 2018-03-07 21:24:13 +08:00
老广
c2abd58dcb Merge to dev (#1051)
* [Update] 修改 success message, 添加资产组时可以添加资产

* [Update] system user form add label

* [Update] set default cluster

* [Update] 修改一些翻译

* [Bugfix] 修复重置密码bug

* [Bugfix] 默认default cluster

* [Bugfix] 用户添加报错

* 修改tab样式

* [Bugfix] 修复了一些显示上的bug

* 修复全选按钮在搜索后仍然选择全部的问题

* [Bugfix] 修复以下bug
1. 查看执行历史异常
2. 用户授权资产页显示message

* [Update] api 返回platform, 并增加web terminal nav

* [Feature] 添加setting页面

* [Feature] 添加basic settings

* [Update] 修改翻译

* [Update] 修改config

* [Update] 启动加载common setting

* [Bugfix] 修复cluster创建的bug

* [Bugfix] 修复title显示Jumpserver

* [Bugfix] setting tables not found

* [Bugfix] settings add option

* [Feature] 添加后端paging

* [Bugfix] 资产列表选择别的页会报错

* [Update] check all 只选择当前页面

* [Bugfix] user login ip

* [Bugfix] for login ip

* [Bugfix] 修复资产列表显示bug

* [Remove] labels

* [Bugfix] task运行失败,因为tasks没有设置

* [Feature] 增加标签

* [Bugfix] 读取不到prefix

* For storage

* [Change] 修改部分翻译

* [Update] 启用ldap移动位置

* [Update] 修改翻译

* [Feature] 支持es存储命令

* Update README.md

* [Feature] 添加es支持

* [update] 修改用户创建时 姓名和用户名的位置

* [Update] 修改install.md

* [Update] remote default PAGE_SIZE stting

* [Feature] terminal config load

* [Feature] es support

* [Update] 修改requirement

* [Update] 修改requirements

* [Update] 修改dictfiled

* [Fix] 修改Logger

* [Bugfix] 倒序显示

* [Update] 修改默认头像和logo

* [Update] 修改django-celery-beat的版本

* [Feature] 添加修改用户密码api

* add logo test

* [Bugfix] 修复一些bug

* [Update] 修改copyrite

* [Update] 修改copyright

* Update ISSUE_TEMPLATE.md

* [Update] 修改禁止排序的颜色

* [Feature] 标签管理功能

* [Bugfix] git status

* [Model] 修改create_by字段

* [Update] 修改位置

* [Update] 修改签名md5算法

* [Feature] 资产列表标签搜索

* [Feature] 添加资产详情标签

* [Bugfix] 修复资产搜索bug

* [Update] ansible disk bug

* [Update] ansible disk bug

* [Bugfix] 修复获取kvmcpu的bug

* [Bugfix] 修复bsd获取cpu数量bug

* [Bugfix] 修改翻译

* [Bugfix] 资产model 太长

* [Bugfix] 修改项目结构描述

修正"项目多语言目录"

* Update project_structure.md

* [Update] add debug log

* refactor: rename folder i18n

* [Feature] 添加链接token

* [Feature] Label 删除修改

* [Update] 修改部分翻译

* [Update] 修改小bug

* [Update] 修复获取资产信息异常bug

* [Bugfix] 修复系统用户上传秘钥的bug

* [Update] 修改获取资产信息产生的异常

* [Update] 删除部分资产属性

* [Bugfix] 资产批量便捷

* [Update] 修改认证

* [Feature] 支持popover

* [Feature] tree

* [Feature] 添加资产树

* [Feature] 使用ztree

* [Feature] tree增删功能

* [Bugfix] 修复组详情bug

* [Bugfix] 修复组详情bug

* [Bugfix] 修改创建label时报错的bug

* [Bugfix] 修改label api bug

* [Update] 去掉资产组添加

* [Update] 修改ztrr

* Update README.md

* [Update] 修改资产创建

* [Bugfix] 修复ldap认证bug

* [Update] 修改一处翻译

* [Update] 更改授权规则前commit

* [Abandon] ...

* Update README.md

* Update README.md

* Update README.md

* [Feature] 完成资产授权和资产添加

* [Update] 修改授权

* [Bugfix] 修改创建系统用户的bug

* feat: rdp support

* [Update] 拆分asset api module

* [Update] 资产列表选中和移除资产

* [Feature] 更改perms api

* [Update] 使用资产树,去掉集群和资产组

* [Update] 修改系统用户推送,拆分assets的部分模块

* [Update] 完成树形改造

* [Update] 完成资产书

* [Update] 修改资产model

* ubuntu16.04 deb_requirements.txt update (#1007)

* Update run server.py (#915)

Fix  for not callable error when  config.py not exists

* [Update]一些修改

* [Update] 修改初始

* feat: replay setting page and api

* 增加隐藏树功能

* [Update] 修改翻译

* 对齐菜单文字。修改英文

* feat: update app setting

* fix: app get replay storage

* [Update] 修改文案

* [Docs] 初始化doc

* [Bugfix] 用户csv导入编码问题

* [Update] 修改设置的一些require

* [Bugfix] 修复管理用户无法查看的bug

* [Update] 修改授权api, windows资产只有rdp协议,linux只有ssh协议

* [Update] terminal可以更改名称

* [Update] 统一copyright

* [Update] 修改文档

* [Bugfix] 修复资产禁用还可以登录

* [Update] 修改文案

* [Update] 支持拖拽更新

* [Bugfix] 修复bug,修改celery beat版本依赖

* [Update] 修改一些小问题

* 添加普通用户使用内容

* [Update] 修改一些文案

* Update README.md

* Update README.md

* Update README.md

* 用户列表

* [Update] 修改一些bug和文案

* [Delete] 删除build 页面

* [Update] 修改conf

* [Update] bugfix

* [Update] 更新文档地址

* [Update] 修改部分翻译和文档

* [Update] 修改一些bug

* [Update] 修改链接

* [Update] 增加批量终端session api

* [Update] 修改Node value唯一

* [Bugfix] 修复首页无法显示数据的bug

* feat: s3 replay file get

* feat: update

* [Update] 修改bug
2018-03-07 21:21:56 +08:00
ibuler
1fa3e98eb0 Merge remote-tracking branch 'github/docs' into docs 2018-03-07 19:13:54 +08:00
ibuler
42af939501 [Update] 修改gua的安装 2018-03-07 19:12:44 +08:00
ibuler
2def25cae6 [Update] 修改文档地址 2018-03-07 19:08:42 +08:00
ibuler
d3fa3d63e7 [Update] 修改Model 2018-03-07 19:05:43 +08:00
ibuler
5cc43f6907 Merge remote-tracking branch 'github/dev' into dev 2018-03-07 18:10:33 +08:00
ibuler
d852f0182b [Update] 修改bug 2018-03-07 17:48:19 +08:00
liuzheng712
9aed73d644 feat: update 2018-03-07 17:18:56 +08:00
liuzheng712
d1a2fe145d Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2018-03-07 17:18:06 +08:00
liuzheng712
83f83d9b07 feat: s3 replay file get 2018-03-07 17:17:31 +08:00
ibuler
c4afd04cbc Merge remote-tracking branch 'github/dev' into dev 2018-03-07 16:13:52 +08:00
ibuler
40d2934de0 [Bugfix] 修复首页无法显示数据的bug 2018-03-07 16:04:13 +08:00
fit2cloud-fengyi
e734fa3b0d 快速启动修改 2018-03-07 14:47:14 +08:00
fit2cloud-fengyi
50b0e024e5 系统设置 2018-03-07 13:07:18 +08:00
fit2cloud-fengyi
f7d1a8b35f 系统设置 2018-03-07 13:03:07 +08:00
ibuler
cd9000e7e9 [Update] 修改Node value唯一 2018-03-07 12:13:03 +08:00
ibuler
af2db2d870 [Update] 增加批量终端session api 2018-03-07 11:28:42 +08:00
shenchenyang
2559899b97 会话管理,权限管理,作业中心文档 2018-03-07 11:17:55 +08:00
fit2cloud-fengyi
86cf7bd6b5 标签管理 2018-03-07 10:35:58 +08:00
ibuler
150855183a [Update] 修改版本 2018-03-06 21:12:51 +08:00
ibuler
0aa4755565 [Update] 修改链接 2018-03-06 21:09:40 +08:00
ibuler
7488d8834e [Igore] force 2018-03-06 18:54:58 +08:00
ibuler
022f6625b1 Merge remote-tracking branch 'github/docs' into docs 2018-03-06 18:54:03 +08:00
fit2cloud-fengyi
115c7c4d50 add ignore 2018-03-06 18:52:09 +08:00
fit2cloud-fengyi
094e862abf fix bug 2018-03-06 18:49:36 +08:00
ibuler
55436ddff1 Merge remote-tracking branch 'github/docs' into docs 2018-03-06 18:41:57 +08:00
ibuler
df1c85494b [Update] 修改文档 2018-03-06 18:41:31 +08:00
fit2cloud-fengyi
8e09151a02 Merge branch 'docs' of https://github.com/jumpserver/jumpserver into docs 2018-03-06 18:30:42 +08:00
fit2cloud-fengyi
18841d6c21 add ignore 2018-03-06 18:29:24 +08:00
ibuler
3fe5dadebc [Update] 修改介绍 2018-03-06 18:27:28 +08:00
ibuler
d3e77b09ef [Merge] with doc 2018-03-06 18:04:03 +08:00
ibuler
25ebb5c442 [Update] 修改一些bug 2018-03-06 17:05:36 +08:00
fit2cloud-fengyi
84c4e69062 截图 2018-03-06 16:54:00 +08:00
fit2cloud-fengyi
ed7f7392db 添加贡献者 2018-03-06 15:42:46 +08:00
fit2cloud-fengyi
457886cb6c Merge branch 'docs' of https://github.com/jumpserver/jumpserver into docs 2018-03-06 15:16:57 +08:00
fit2cloud-fengyi
f724f46e00 细节改动 2018-03-06 15:16:42 +08:00
shenchenyang
6a8d75f00e 作业中心文档 2018-03-06 12:18:08 +08:00
shenchenyang
65619fabc5 权限管理文档命名修改 2018-03-06 11:54:50 +08:00
shenchenyang
370534e28f Merge branch 'docs' of https://github.com/jumpserver/jumpserver into docs 2018-03-06 11:50:20 +08:00
shenchenyang
cefdcb242c 权限管理文档 2018-03-06 11:49:42 +08:00
fit2cloud-fengyi
31ba73e2fa 管理用户 2018-03-06 11:00:19 +08:00
ibuler
86c0a2d28b [Update] 修改部分翻译和文档 2018-03-05 17:14:42 +08:00
fit2cloud-fengyi
4778ac1392 用户管理模块更新 2018-03-05 17:12:02 +08:00
fit2cloud-fengyi
58189df280 用户管理模块更新 2018-03-05 17:09:53 +08:00
ibuler
bc230e0ea5 [Update] 更新文档地址 2018-03-05 15:25:49 +08:00
ibuler
21328382fc [Update] bugfix 2018-03-05 15:05:48 +08:00
ibuler
eca5966257 [Update] 修改conf 2018-03-05 15:04:11 +08:00
fit2cloud-fengyi
42cb54dd2a 用户管理模块更新 2018-03-05 13:10:51 +08:00
fit2cloud-fengyi
d07b427037 用户管理模块更新 2018-03-05 13:10:02 +08:00
ibuler
efcf31b2a9 [Delete] 删除build 页面 2018-03-05 13:09:00 +08:00
ibuler
e123599735 [Update] 修改一些bug和文案 2018-03-05 12:59:14 +08:00
fit2cloud-fengyi
1c204675e5 用户列表 2018-03-05 12:20:40 +08:00
老广
e449a07a3f Update README.md 2018-03-05 10:11:13 +08:00
老广
d4f61b9e69 Update README.md 2018-03-05 10:08:04 +08:00
老广
4451d68d62 Update README.md 2018-03-05 10:07:34 +08:00
ibuler
bd4f96134f [Update] 修改一些文案 2018-03-04 13:01:33 +08:00
fit2cloud-fengyi
767d94875c 添加普通用户使用内容 2018-03-04 09:57:11 +08:00
ibuler
d284c2175d [Update] 修改一些小问题 2018-03-02 15:39:01 +08:00
ibuler
f7ab13952e [Bugfix] 修复bug,修改celery beat版本依赖 2018-03-02 09:36:17 +08:00
ibuler
5dcdeddf22 Merge remote-tracking branch 'github/dev' into dev 2018-03-01 13:39:52 +08:00
ibuler
1018deda96 [Update] 支持拖拽更新 2018-03-01 12:40:41 +08:00
ibuler
5a0068d86a [Update] 修改文案 2018-03-01 10:45:51 +08:00
ibuler
31629fc975 [Bugfix] 修复资产禁用还可以登录 2018-03-01 10:44:55 +08:00
ibuler
634b36c74b [Update] 修改文档 2018-03-01 00:13:53 +08:00
ibuler
521a5c57a2 [Update] 统一copyright 2018-02-28 11:23:04 +08:00
ibuler
1e5b9fb3ef [Update] terminal可以更改名称 2018-02-28 10:39:38 +08:00
ibuler
f886e7c2f5 [Update] 修改授权api, windows资产只有rdp协议,linux只有ssh协议 2018-02-27 19:39:27 +08:00
ibuler
91863107d7 [Bugfix] 修复管理用户无法查看的bug 2018-02-27 15:32:30 +08:00
ibuler
870b863136 [Update] 修改设置的一些require 2018-02-27 15:04:05 +08:00
ibuler
9292e48554 [Bugfix] 用户csv导入编码问题 2018-02-27 12:18:36 +08:00
ibuler
13abd4c751 [Docs] 初始化doc 2018-02-27 10:27:28 +08:00
ibuler
8e2891d7d7 [Update] 修改文案 2018-02-27 10:27:08 +08:00
ibuler
315159e4b6 Merge branch 'dev' of bitbucket.org:jumpserver/core into dev 2018-02-26 18:12:20 +08:00
liuzheng712
fc61dea9b5 fix: app get replay storage 2018-02-26 18:11:46 +08:00
ibuler
08ccac3f66 Merge branch 'dev' of bitbucket.org:jumpserver/core into dev 2018-02-26 18:06:09 +08:00
liuzheng712
ea27d05c58 feat: update app setting 2018-02-26 18:05:41 +08:00
q4speed
05255f850f Merge remote-tracking branch 'origin/dev' into dev 2018-02-26 17:28:18 +08:00
q4speed
c96a107e0a 对齐菜单文字。修改英文 2018-02-26 17:28:03 +08:00
ibuler
e2cb128794 [Update] 修改翻译 2018-02-26 16:57:34 +08:00
ibuler
24fdff0120 Merge remote-tracking branch 'github/dev' into dev 2018-02-26 16:32:40 +08:00
ibuler
8999458a72 Merge branch 'dev' of bitbucket.org:jumpserver/core into dev 2018-02-26 16:32:20 +08:00
q4speed
66b8e8bcf8 增加隐藏树功能 2018-02-26 16:31:51 +08:00
liuzheng
459d0668a8 feat: replay setting page and api 2018-02-26 16:19:29 +08:00
ibuler
c68da5898a [Update] 修改初始 2018-02-26 12:31:33 +08:00
ibuler
1f3b11a223 Merge remote-tracking branch 'github/dev' into dev 2018-02-26 11:38:20 +08:00
ibuler
f1c386714d [Update]一些修改 2018-02-25 22:36:42 +08:00
calmzhu
5193ba2e39 Update run server.py (#915)
Fix  for not callable error when  config.py not exists
2018-02-25 20:09:15 +08:00
bagechashu
eb18648a66 ubuntu16.04 deb_requirements.txt update (#1007) 2018-02-25 20:07:26 +08:00
ibuler
f0dd7d54c8 [Update] 修改资产model 2018-02-25 18:40:15 +08:00
ibuler
08edda35e1 [Update] 完成资产书 2018-02-25 18:08:00 +08:00
ibuler
c8728cace4 [Update] 完成树形改造 2018-02-09 15:24:44 +08:00
ibuler
c7296c2498 [Update] 修改系统用户推送,拆分assets的部分模块 2018-02-09 11:12:40 +08:00
ibuler
f82d939d15 [Update] 使用资产树,去掉集群和资产组 2018-02-08 11:34:21 +08:00
ibuler
3bb6e08985 [Feature] 更改perms api 2018-02-07 23:25:15 +08:00
ibuler
6104acae8f [Update] 资产列表选中和移除资产 2018-02-07 13:39:45 +08:00
ibuler
bd4768c147 [Update] 拆分asset api module 2018-02-06 18:32:02 +08:00
ibuler
d047ff3b3a Merge branch 'dev' into tree 2018-02-06 13:02:30 +08:00
liuzheng712
6edd3f6cf8 feat: rdp support 2018-02-05 21:54:57 +08:00
ibuler
bdf506a555 Merge remote-tracking branch 'github/dev' into dev 2018-02-02 19:04:36 +08:00
ibuler
114289edaf [Bugfix] 修改创建系统用户的bug 2018-02-02 19:04:09 +08:00
ibuler
274cb74097 [Update] 修改授权 2018-02-02 18:52:09 +08:00
ibuler
2d3967872b [Feature] 完成资产授权和资产添加 2018-02-02 17:06:08 +08:00
老广
64da400281 Update README.md 2018-02-01 17:57:50 +08:00
老广
9ffe1b5ab5 Update README.md 2018-02-01 17:56:04 +08:00
老广
83ad72e04b Update README.md 2018-02-01 17:54:26 +08:00
ibuler
653c328e84 [Abandon] ... 2018-02-01 17:14:15 +08:00
ibuler
2bc5e882fd [Update] 更改授权规则前commit 2018-02-01 15:06:44 +08:00
ibuler
45f6a62989 [Update] 修改一处翻译 2018-02-01 12:37:45 +08:00
ibuler
21a7202587 [Bugfix] 修复ldap认证bug 2018-02-01 12:33:05 +08:00
ibuler
02cd3b1a18 Merge remote-tracking branch 'github/dev' into dev 2018-02-01 10:23:32 +08:00
ibuler
a10ff5930f [Update] 修改资产创建 2018-02-01 10:22:20 +08:00
老广
2b4880c784 Update README.md 2018-02-01 10:20:33 +08:00
ibuler
017c3ae36a [Update] 修改ztrr 2018-02-01 01:03:31 +08:00
ibuler
c4b52cb2a3 [Update] 去掉资产组添加 2018-02-01 00:33:00 +08:00
ibuler
5f2ca58d62 [Bugfix] 修改label api bug 2018-01-31 18:59:43 +08:00
ibuler
1c7212014b [Bugfix] 修改创建label时报错的bug 2018-01-31 18:48:07 +08:00
ibuler
97dd411ab8 [Bugfix] 修复组详情bug 2018-01-31 17:06:04 +08:00
ibuler
d2ad10af9a [Bugfix] 修复组详情bug 2018-01-31 17:04:15 +08:00
ibuler
2f06a2b1d3 [Feature] tree增删功能 2018-01-31 17:01:21 +08:00
ibuler
1ac30ed09c [Feature] 使用ztree 2018-01-31 12:46:33 +08:00
ibuler
3603b33a42 [Feature] 添加资产树 2018-01-31 10:46:26 +08:00
ibuler
460fa8e8a9 [Feature] tree 2018-01-30 19:57:47 +08:00
ibuler
9e09a962af [Feature] 支持popover 2018-01-30 18:07:51 +08:00
ibuler
3e62a7f5b7 [Update] 修改认证 2018-01-30 16:12:33 +08:00
ibuler
b4f833740e [Bugfix] 资产批量便捷 2018-01-30 14:54:27 +08:00
ibuler
1fbf4ac08c [Update] 删除部分资产属性 2018-01-30 14:46:02 +08:00
ibuler
da4c5f48a1 [Update] 修改获取资产信息产生的异常 2018-01-30 12:09:33 +08:00
ibuler
1192e8cdad [Bugfix] 修复系统用户上传秘钥的bug 2018-01-30 11:56:41 +08:00
ibuler
bad397e2e6 [Update] 修复获取资产信息异常bug 2018-01-30 10:38:40 +08:00
ibuler
0f15a94b08 Merge remote-tracking branch 'origin/dev' into dev 2018-01-29 17:04:09 +08:00
ibuler
012614562f [Update] 修改小bug 2018-01-29 16:57:50 +08:00
ibuler
8b626ea9d4 [Update] 修改部分翻译 2018-01-29 16:27:21 +08:00
ibuler
58d22b72ec [Feature] Label 删除修改 2018-01-29 16:25:30 +08:00
ibuler
6d552f4680 [Feature] 添加链接token 2018-01-29 16:18:30 +08:00
liuzheng
e821d2995d refactor: rename folder i18n 2018-01-29 11:54:38 +08:00
ibuler
cc4eca2563 [Update] add debug log 2018-01-29 11:53:49 +08:00
liuzheng
04c344e2a0 Update project_structure.md 2018-01-29 11:50:41 +08:00
liuzheng
7f777a4472 Merge pull request #968 from chenchen1750/patch-1
[Bugfix] 修改项目结构描述
2018-01-29 11:49:06 +08:00
chenchen1750
1f792a9e3c [Bugfix] 修改项目结构描述
修正"项目多语言目录"
2018-01-29 11:07:01 +08:00
ibuler
6d4e9c7d30 [Bugfix] 资产model 太长 2018-01-26 17:17:37 +08:00
ibuler
9433d6177a [Bugfix] 修改翻译 2018-01-26 16:50:59 +08:00
ibuler
723805d395 [Bugfix] 修复bsd获取cpu数量bug 2018-01-26 16:28:40 +08:00
ibuler
c553872f90 Merge remote-tracking branch 'github/dev' into dev 2018-01-26 16:22:06 +08:00
ibuler
41756b06e1 [Bugfix] 修复获取kvmcpu的bug 2018-01-26 16:21:14 +08:00
ibuler
9e36a2cbb1 [Update] ansible disk bug 2018-01-26 16:06:36 +08:00
ibuler
f0b9a11482 [Update] ansible disk bug 2018-01-26 16:06:23 +08:00
ibuler
f6d21585ab [Bugfix] 修复资产搜索bug 2018-01-26 15:53:43 +08:00
ibuler
b0fab2453a [Merge] 合并标签功能 2018-01-26 15:49:09 +08:00
ibuler
b2d6645f46 [Feature] 添加资产详情标签 2018-01-26 15:32:23 +08:00
ibuler
cb902362b5 [Feature] 资产列表标签搜索 2018-01-26 14:47:10 +08:00
ibuler
0a2b6494cc [Update] 修改签名md5算法 2018-01-25 16:38:40 +08:00
ibuler
0c935e8922 Merge branch 'dev' of bitbucket.org:jumpserver/core into dev 2018-01-25 16:15:54 +08:00
ibuler
17f91f7f53 [Update] 修改位置 2018-01-25 15:43:52 +08:00
ibuler
d74c2e8466 [Model] 修改create_by字段 2018-01-25 12:17:26 +08:00
ibuler
070c3d73cc [Bugfix] git status 2018-01-25 12:16:26 +08:00
ibuler
7433327d75 [Feature] 标签管理功能 2018-01-23 19:37:22 +08:00
ibuler
a976475617 [Update] 修改禁止排序的颜色 2018-01-23 17:11:26 +08:00
ibuler
f37b331630 Merge with dev 2018-01-23 15:13:24 +08:00
老广
4b0ea63e0a Update ISSUE_TEMPLATE.md 2018-01-23 15:02:34 +08:00
ibuler
9dbf498322 [Update] 修改copyright 2018-01-23 12:40:22 +08:00
ibuler
8e6efac6db [Update] 修改copyrite 2018-01-23 12:39:19 +08:00
ibuler
579e74dd5d [Bugfix] 修复一些bug 2018-01-23 12:22:25 +08:00
ibuler
cad07434ff add logo test 2018-01-23 11:11:20 +08:00
ibuler
f40fbaf602 [Feature] 添加修改用户密码api 2018-01-23 10:36:20 +08:00
ibuler
a43d29d446 [Update] 修改django-celery-beat的版本 2018-01-22 23:57:08 +08:00
ibuler
2f136800c7 [Update] 修改默认头像和logo 2018-01-22 23:47:41 +08:00
ibuler
dd6e9444e1 [Bugfix] 倒序显示 2018-01-22 18:48:16 +08:00
ibuler
7670f521a2 [Fix] 修改Logger 2018-01-22 18:37:28 +08:00
ibuler
cafb0e02a3 [Update] 修改dictfiled 2018-01-22 17:21:03 +08:00
ibuler
9d399c475c [Update] 修改requirements 2018-01-22 12:49:55 +08:00
ibuler
8766a936f3 [Update] 修改requirement 2018-01-22 12:01:32 +08:00
ibuler
968b395bce Merge branch 'es' into dev 2018-01-22 11:51:37 +08:00
ibuler
0a931bbf77 [Feature] es support 2018-01-22 11:38:40 +08:00
ibuler
17181db8a5 [Feature] 支持es存储 2018-01-21 17:27:27 +08:00
ibuler
41f1c3f7f7 [Feature] terminal config load 2018-01-21 15:12:59 +08:00
liuzheng712
c4406c2a33 Merge branch 'master' of github.com:jumpserver/jumpserver into dev 2018-01-21 11:33:44 +08:00
ibuler
7f18821990 [Update] remote default PAGE_SIZE stting 2018-01-20 23:02:17 +08:00
ibuler
8fe3caf2ea [Update] 修改install.md 2018-01-20 22:54:15 +08:00
ibuler
15119bec3c Merge remote-tracking branch 'github/dev' into dev 2018-01-20 22:26:41 +08:00
ibuler
57e508f331 [update] 修改用户创建时 姓名和用户名的位置 2018-01-20 22:26:21 +08:00
ibuler
b936d54a48 [Feature] 添加es支持 2018-01-20 22:22:09 +08:00
老广
5c8dd5676c Merge to master (#944)
* [Update] 修改 success message, 添加资产组时可以添加资产

* [Update] system user form add label

* [Update] set default cluster

* [Update] 修改一些翻译

* [Bugfix] 修复重置密码bug

* [Bugfix] 默认default cluster

* [Bugfix] 用户添加报错

* 修改tab样式

* [Bugfix] 修复了一些显示上的bug

* 修复全选按钮在搜索后仍然选择全部的问题

* [Bugfix] 修复以下bug
1. 查看执行历史异常
2. 用户授权资产页显示message

* [Update] api 返回platform, 并增加web terminal nav

* [Feature] 添加setting页面

* [Feature] 添加basic settings

* [Update] 修改翻译

* [Update] 修改config

* [Update] 启动加载common setting

* [Bugfix] 修复cluster创建的bug

* [Bugfix] 修复title显示Jumpserver

* [Bugfix] setting tables not found

* [Bugfix] settings add option

* [Feature] 添加后端paging

* [Bugfix] 资产列表选择别的页会报错

* [Update] check all 只选择当前页面

* [Bugfix] user login ip

* [Bugfix] for login ip

* [Bugfix] 修复资产列表显示bug

* [Remove] labels

* [Bugfix] task运行失败,因为tasks没有设置

* [Bugfix] 读取不到prefix

* [Change] 修改部分翻译

* [Update] 启用ldap移动位置

* [Update] 修改翻译

* Update README.md
2018-01-20 21:01:55 +08:00
老广
689558b8c7 Update README.md 2018-01-18 18:12:39 +08:00
ibuler
67001dd99f [Feature] 支持es存储命令 2018-01-18 09:56:13 +08:00
ibuler
3c3e9a4113 [Update] 修改翻译 2018-01-17 17:26:25 +08:00
ibuler
d08a1c3ddb [Update] 启用ldap移动位置 2018-01-17 17:22:04 +08:00
ibuler
a77acb7dfb [Change] 修改部分翻译 2018-01-17 17:18:13 +08:00
ibuler
ba5ab21b37 For storage 2018-01-17 17:17:18 +08:00
ibuler
ce9ff67b24 [Bugfix] 读取不到prefix 2018-01-17 16:18:33 +08:00
ibuler
5bbad01909 [Feature] 增加标签 2018-01-16 16:32:06 +08:00
ibuler
01895bafc0 [Bugfix] task运行失败,因为tasks没有设置 2018-01-16 11:40:18 +08:00
ibuler
f1ec53d1bc [Remove] labels 2018-01-16 11:26:30 +08:00
ibuler
d3109a03b2 [Bugfix] 修复资产列表显示bug 2018-01-16 11:20:26 +08:00
ibuler
681ddb5af2 [Bugfix] for login ip 2018-01-16 11:08:05 +08:00
ibuler
29061aa088 [Bugfix] user login ip 2018-01-16 09:58:33 +08:00
ibuler
dad21cadb3 [Update] check all 只选择当前页面 2018-01-15 18:32:07 +08:00
ibuler
7e2d627d3f [Bugfix] 资产列表选择别的页会报错 2018-01-15 18:14:58 +08:00
ibuler
cc18ad9a7f Merge branch 'paging' into dev 2018-01-15 17:41:57 +08:00
ibuler
b2f97a263d [Feature] 添加后端paging 2018-01-15 17:41:49 +08:00
ibuler
8b9e45ad31 [Bugfix] settings add option 2018-01-15 16:00:26 +08:00
ibuler
89e013e2e2 [Bugfix] setting tables not found 2018-01-15 14:17:56 +08:00
ibuler
59a69f0253 [Bugfix] 修复title显示Jumpserver 2018-01-15 13:54:19 +08:00
ibuler
39fff235d1 [Bugfix] 修复cluster创建的bug 2018-01-14 18:02:11 +08:00
ibuler
7316661eb5 [Update] 启动加载common setting 2018-01-12 16:37:55 +08:00
ibuler
f6056426dc [Update] 修改config 2018-01-12 15:57:24 +08:00
ibuler
d48f8dceb5 [Update] 修改翻译 2018-01-12 15:55:08 +08:00
ibuler
abb1a40a4f [Feature] 添加basic settings 2018-01-12 15:43:26 +08:00
ibuler
121f56f44b [Feature] 添加setting页面 2018-01-11 20:10:27 +08:00
ibuler
b49e3b8f84 [Update] api 返回platform, 并增加web terminal nav 2018-01-10 19:39:22 +08:00
ibuler
9bf6b12904 Merge remote-tracking branch 'github/dev' into dev 2018-01-10 18:11:23 +08:00
ibuler
6aaa106aff [Bugfix] 修复以下bug
1. 查看执行历史异常
2. 用户授权资产页显示message
2018-01-10 18:08:27 +08:00
q4speed
576ddbf673 修复全选按钮在搜索后仍然选择全部的问题 2018-01-10 17:56:50 +08:00
ibuler
d415e81add Merge remote-tracking branch 'github/dev' into dev 2018-01-10 17:41:57 +08:00
ibuler
3223d3b74d [Bugfix] 修复了一些显示上的bug 2018-01-10 17:38:10 +08:00
q4speed
7f3d32a876 Merge remote-tracking branch 'origin/dev' into dev 2018-01-10 16:42:57 +08:00
q4speed
6d7b596854 修改tab样式 2018-01-10 16:42:30 +08:00
ibuler
b2b123b41e [Bugfix] 用户添加报错 2018-01-10 15:15:29 +08:00
ibuler
99cd685194 [Bugfix] 默认default cluster 2018-01-10 12:03:35 +08:00
ibuler
7e23d2c234 [Bugfix] 修复重置密码bug 2018-01-10 11:31:25 +08:00
ibuler
b4080be5be [Update] 修改一些翻译 2018-01-10 00:38:21 +08:00
ibuler
22af44211d [Update] set default cluster 2018-01-09 23:33:14 +08:00
ibuler
a4d02adb22 [Update] system user form add label 2018-01-09 23:14:25 +08:00
ibuler
72a82c41ee [Update] 修改 success message, 添加资产组时可以添加资产 2018-01-09 23:07:53 +08:00
ibuler
450a9495ec [Bug&Update] 以下bug和功能
- 修复新建资产不选管理用户和集群异常bug
- profile下拉增加icon
- 授权api增加os和platform
2018-01-09 13:10:28 +08:00
ibuler
c0829194dc [Update] 修改一些bug, 增加执行task的按钮 2018-01-08 19:16:28 +08:00
ibuler
dfaf029a68 [Bugfix] 资产导入bugfix 2018-01-08 15:51:08 +08:00
ibuler
3f1863151a [Bugfix] 修复session搜索bug 2018-01-08 15:15:21 +08:00
ibuler
676d640e70 [Bugfix] 修复导出csv和更新资产环境变量冲突 2018-01-08 14:58:07 +08:00
ibuler
c7d1ba1944 [Update] Middleware 写法升级到新版本, Task login require 2018-01-08 14:18:02 +08:00
ibuler
07a70311df [Bugfix] 修复以下bug
- 删除用户报错,上次更新带来的
- 管理员用户页面会看到所有主机的,而不是授权给自己的
- 授权详情页面 资产显示无效
2018-01-08 11:22:19 +08:00
ibuler
c16471d54f [Bugfix] 资产导入中下载模板异常 2018-01-05 19:09:42 +08:00
ibuler
9a6bc9fec5 [Update] session添加登陆ip 2018-01-05 18:37:36 +08:00
ibuler
1888c698f1 [Bugfix] 修复一些bug 2018-01-05 17:57:02 +08:00
ibuler
6518ae8fa3 Merge remote-tracking branch 'origin/dev' into dev 2018-01-05 14:57:53 +08:00
ibuler
afce4c81a8 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2018-01-05 14:56:50 +08:00
ibuler
28223421b1 [Bugfix] 修复一下bug
1. 添加管理用户时同名的异常
2. 添加授权规则的检查
2018-01-05 14:36:49 +08:00
ibuler
56fd18241d [Update] 修改一些翻译 2018-01-04 09:29:54 +08:00
i317280
7648207ae4 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2018-01-03 17:09:35 +08:00
q4speed
9a2fbba956 修改访问官网链接 2018-01-03 15:07:46 +08:00
ibuler
a327c46229 Merge remote-tracking branch 'github/dev' into dev 2018-01-02 19:37:56 +08:00
ibuler
720dc9ab05 [Bugfix] 修复以下bug
- session时区
- 切换管理和用户页面cookie问题
- 创建权限时验证
- 一些翻译
2018-01-02 19:36:13 +08:00
q4speed
25fa392dda 修复图片高度和大小 2018-01-02 17:51:25 +08:00
q4speed
ad3297ff2f 显示个人头像 2018-01-02 17:40:49 +08:00
q4speed
ab27d1c193 修改菜单折叠起来时Logo 2018-01-02 17:31:20 +08:00
q4speed
8160425672 修改Logo 2018-01-02 17:07:00 +08:00
ibuler
68ccec6b9c [UPdate] 修改cookie设置 2018-01-02 17:02:03 +08:00
q4speed
bf34c2e320 Merge remote-tracking branch 'origin/dev' into dev 2018-01-02 16:50:57 +08:00
q4speed
54bcb33a0e 修改Logo 2018-01-02 16:43:47 +08:00
ibuler
6d15f60c5c 更改默认使用Dev配置文件 2018-01-02 15:23:06 +08:00
ibuler
239a829c20 [Bugfix] support xvdx disk 2018-01-02 14:49:52 +08:00
ibuler
4dbf332a56 [Bugfix] 修复刷新硬件的bug 2018-01-02 14:29:37 +08:00
ibuler
539bf7bec8 Update doc 2018-01-02 10:47:32 +08:00
ibuler
c6eaf894a4 Update doc 2018-01-02 10:46:20 +08:00
ibuler
5eef584c7d [Bugfix] 解决打印print失败问题,原因在于运行docker没有tty,也可以添加PYTHONIOENCODING环境变量解决 2018-01-01 23:45:27 +08:00
ibuler
f4a39aba00 [Debug] 无法print中文 2018-01-01 22:00:08 +08:00
ibuler
3300d20c1c [Update] 修改beat 启动,判断pid文件是否存在 2018-01-01 21:04:43 +08:00
ibuler
edcafa7275 [Update] 修改运行 beat方式 2018-01-01 20:54:48 +08:00
ibuler
e8a72c8c7d [Update] 修改一些翻译 2018-01-01 19:55:37 +08:00
ibuler
73a99de55c [Update] 修改terminal翻译 2018-01-01 15:39:35 +08:00
ibuler
3b6403b2f2 [Update] 修改一些翻译 2018-01-01 15:08:33 +08:00
ibuler
c48531c586 [Update] 添加和修改部分翻译 2018-01-01 00:45:12 +08:00
ibuler
0916757eb8 [Bugfix] 修复一些bug 2017-12-31 21:51:25 +08:00
ibuler
0b299344a7 [Update] 修改database 表现 2017-12-31 12:20:08 +08:00
ibuler
13b610c140 [Change] 仅点击checkbox选中 2017-12-31 11:14:02 +08:00
ibuler
7558c2b3e4 [Debug] debug print error 2017-12-30 23:02:10 +08:00
ibuler
ddd18e10e0 [Debug] debug print error 2017-12-30 22:53:59 +08:00
ibuler
a755882100 [Bugfix] 修复打印中文报错异常 2017-12-30 22:26:05 +08:00
ibuler
6586bef84c [Modify] Add access log 2017-12-30 20:45:54 +08:00
ibuler
e0a3fafbd5 [Bugfix] 详见trello 2017-12-30 02:29:29 +08:00
ibuler
3804ab532d [Update] 修改信号 2017-12-29 23:53:45 +08:00
ibuler
158678c2db [Bugfix] 修改bug,显示cluster 2017-12-28 18:01:15 +08:00
ibuler
2cceb281c2 Merge remote-tracking branch 'github/new_api' into dev 2017-12-28 17:44:59 +08:00
liuzheng712
14de3ba5de Merge branch 'new_api' of github.com:jumpserver/jumpserver into new_api 2017-12-28 17:41:00 +08:00
liuzheng712
ac3ee4c317 feat: luna window 2017-12-28 17:39:24 +08:00
ibuler
69f723c8f5 [Bugfix] requirements 添加openssh-cleints 2017-12-28 16:06:07 +08:00
ibuler
b6b0d6a9be [bugfix] Beat start failed, rm pidfile 2017-12-28 14:34:17 +08:00
ibuler
f992347fc5 [Update] 修改become信息 2017-12-28 14:25:56 +08:00
ibuler
e20444983d [Bugfix] 修改html bug 2017-12-28 12:25:43 +08:00
ibuler
827246ed4c Update ansible remote tmp 2017-12-28 12:03:02 +08:00
ibuler
f58c45f7be [Update] 修改run celery 2017-12-28 11:39:12 +08:00
ibuler
84fa7b5f17 [Bugfix] 修改启动脚本,celery beat可能不存在 2017-12-28 10:52:40 +08:00
ibuler
8e9ad7d645 [Update] 修改runserver运行 2017-12-28 01:18:50 +08:00
ibuler
825edadc13 [Update] Add build no 2017-12-28 01:08:39 +08:00
ibuler
1c20f519a9 [Bugfix] 修改小的bug 2017-12-27 11:22:44 +08:00
ibuler
0848893f1e [Bugfix] 修复时间日期搜索的bug,select2模块自适应长度 2017-12-27 01:29:23 +08:00
ibuler
3d705dbeaa [Bugfix] 修复一些bug 2017-12-27 00:14:46 +08:00
ibuler
22b7b84a45 [Update] 修改创建默认集群,组等名称为Default 2017-12-26 22:14:37 +08:00
ibuler
6a6d0cf279 [Update] Change user model 2017-12-26 02:01:19 +08:00
ibuler
a39e0a1db9 [Docker] For docker update 2017-12-26 01:54:10 +08:00
ibuler
f4beccddae [Update] 修改注册流程 2017-12-25 12:22:49 +08:00
ibuler
8b081cd6b0 [Update] 修改 运行脚本 2017-12-25 00:36:14 +08:00
ibuler
209200dc4f [Update] 修改migrations 2017-12-24 23:23:05 +08:00
ibuler
d0ef1e715e [Add] Add initial migrations 2017-12-24 23:21:05 +08:00
ibuler
bf9bb1b973 [Update] 修改ops task运行 2017-12-24 18:53:07 +08:00
ibuler
30efec1b09 [Update] 修改 task 运行机制 2017-12-22 21:42:12 +08:00
ibuler
4893c4664d [Update] 修改task定时运行机制 2017-12-22 02:08:29 +08:00
ibuler
47040a61c8 [Bugfix] User first login 2017-12-21 19:33:16 +08:00
ibuler
da113b99af [Update] Remove IDE python version file 2017-12-21 19:30:18 +08:00
ibuler
146ddc3ee0 [Update] Remove docker file 2017-12-21 19:26:51 +08:00
ibuler
d33ae9d9d9 [Update] login add cookie test 2017-12-21 19:15:12 +08:00
ibuler
d51b3eff6a [Update] 修改settings和配置文件 2017-12-21 18:54:29 +08:00
ibuler
ec45c56868 [Change] 修改media地址 2017-12-21 16:04:24 +08:00
ibuler
e1b23a7cb4 [Bugfix] 修复无法记录日志的bug 2017-12-21 15:28:21 +08:00
ibuler
76df1de634 [Bugfix] 修复一些明显的bug 2017-12-21 11:31:13 +08:00
ibuler
5a92972120 [Bugfix] 修复ops一些功能的bug 2017-12-20 11:30:15 +08:00
ibuler
3f89701b84 [Bugfix] 修复授权和资产的一部分bug 2017-12-19 19:38:09 +08:00
ibuler
b0eace6ad8 [Bugfix] 基本完成assets模块 2017-12-19 12:41:00 +08:00
ibuler
b4e1ac1953 [Bugfix] 修复资产模块中一些bug 2017-12-18 22:48:19 +08:00
ibuler
a308000d2e [Bugfix] 修改users模块一些bug 2017-12-18 18:38:30 +08:00
ibuler
e1163895e2 [Update] 系统用户api允许app user获取 2017-12-16 17:21:30 +08:00
ibuler
2f638f5938 [Merge] 2017-12-16 17:18:16 +08:00
ibuler
1b6fd4d13a [Bugfix] 修改普通用户获取资产详情的bug 2017-12-16 17:16:40 +08:00
i317280
fb6a187639 feat: find a bug 2017-12-16 16:58:10 +08:00
ibuler
0f64d39cc5 Merge branch 'new_api' of github.com:jumpserver/jumpserver into new_api 2017-12-15 18:56:21 +08:00
ibuler
5e1eff19b7 [Change] Add terminal migrations force 2017-12-15 18:55:36 +08:00
ibuler
8da8c14ace [Update] 修改requirements 2017-12-15 18:53:21 +08:00
ibuler
6cee62696c [Feature] 翻译,并增加设置导航 2017-12-15 17:38:46 +08:00
ibuler
11fa3e08e9 [Bugfix] 修改ts_to_date bug 2017-12-15 17:08:22 +08:00
ibuler
2d0be8f996 [git status 2017-12-15 17:07:52 +08:00
ibuler
b97d5b0960 [Feature] assets task 修改 2017-12-15 15:50:15 +08:00
ibuler
08e1788426 [Feature] 打算拆分下载和上传为独立模块,时间有限暂时放弃 2017-12-14 21:27:14 +08:00
ibuler
160b01ec12 Merge branch 'new_api' into ops 2017-12-13 18:10:41 +08:00
ibuler
17ddb3bbbd [Feture] 使用信号解耦 2017-12-13 17:21:08 +08:00
liuzheng712
ef1bbc29b5 fix: update the terminal app 2017-12-13 00:34:04 +08:00
ibuler
ffa7e58025 [Bugfix] Terminal status报异常 2017-12-12 16:05:21 +08:00
ibuler
18fbfb449d [Feature] 修改url pattern 精确匹配uuid 2017-12-12 15:45:01 +08:00
ibuler
cb57002682 [Change] 去掉前端html中的hardcode 99991937 2017-12-12 14:38:51 +08:00
ibuler
722863428d [Feature] 添加context_processor 2017-12-12 14:28:12 +08:00
ibuler
99b4c66b5e [Feature] 添加signals 解耦代码 2017-12-12 12:19:45 +08:00
ibuler
cbc000696e [Feature] 修改adminuser systemuser cluster及他们的关系 2017-12-11 17:08:43 +08:00
ibuler
0c9e24dc59 [Feture] 添加ops 页面 2017-12-11 00:29:25 +08:00
ibuler
18fd04d63c [Stash] Test case 通不过,import error 2017-12-08 10:15:27 +08:00
ibuler
27a1849b1d [Change] 修改idc => cluster 2017-12-07 16:25:50 +08:00
ibuler
32fd9bb42f Merge branch 'new_api' into ops 2017-12-07 13:28:29 +08:00
ibuler
15f6d5c9c0 [Bugfix] terminal tasks main()运行时,可能数据库还不存在 2017-12-07 13:28:11 +08:00
ibuler
2b3551f1fc [Feature] 修改ansible和ops 2017-12-07 13:01:33 +08:00
ibuler
e57121a780 [Feature] 优化Ops ansible api 2017-12-06 18:31:51 +08:00
ibuler
ec8106e43d [Feature] 完成登陆日志 2017-12-05 12:24:31 +08:00
ibuler
a5f9735906 [Update] 更新index view 2017-12-04 20:15:47 +08:00
ibuler
71a3079221 [Feature] 离线session完成 2017-12-04 16:41:00 +08:00
ibuler
9b6696bb6e [Feture] session detail页面,包含命令列表 2017-12-04 00:21:26 +08:00
ibuler
01558c985a [Feture] 添加session 列表,并支持kill session 2017-12-03 20:42:36 +08:00
ibuler
bf8fa95597 [Update] 修改session 2017-12-01 21:22:32 +08:00
ibuler
94462bddb3 [Bugfix] 修改api view name 2017-12-01 17:55:19 +08:00
ibuler
1920a0f03d [Bugfix] 修改nav小bug 2017-12-01 17:37:46 +08:00
ibuler
f74422e4bb [Bugfix] 更改application models后导致 terminalstatus报错 2017-12-01 17:35:28 +08:00
ibuler
3120cef335 [Change] 修改导航列表 2017-12-01 17:29:57 +08:00
ibuler
61a8d95f46 [Change] Rename applications -> terminal 2017-12-01 17:28:47 +08:00
ibuler
19051d36dc [Update] 修改一些bug 2017-12-01 17:00:55 +08:00
ibuler
7910292e0f [Feature] 修改命令上传api 2017-11-29 19:27:04 +08:00
ibuler
26607bc327 [Copyright] 修改copyrite 2017-11-23 14:35:16 +08:00
ibuler
e7f38ec894 [Change] 全部使用uuid作为主键 2017-11-23 14:08:01 +08:00
ibuler
d80cbe270a [Update] 修改用户获取自己授权资产的api,和查询用户授权资产组等api的Url和结构 2017-11-23 11:26:17 +08:00
ibuler
818ead85ca [Feature] 增加上传replay log的api 2017-11-22 10:54:59 +08:00
ibuler
7f9ce57318 修改 terminal上报接口和api 2017-11-14 09:44:16 +08:00
ibuler
3639b190e3 Update perm api 2017-11-01 23:23:11 +08:00
ibuler
034f0a02b1 Update terminal api 2017-10-31 11:34:20 +08:00
ibuler
30c4fc9514 App user get user perms 2017-10-22 17:53:46 +08:00
603 changed files with 103643 additions and 16535 deletions

7
.dockerignore Normal file
View File

@@ -0,0 +1,7 @@
.git
logs/*
data/*
.github
tmp/*
django.db
celerybeat.pid

View File

@@ -1,7 +1,8 @@
[简述你的问题]
##### 使用版本
[请提供你使用的Jumpserver版本 0.3.2 或 0.4.0]
[请提供你使用的Jumpserver版本 1.x.x 注: 0.3.x不再提供支持]
##### 问题复现步骤
1. [步骤1]

9
.gitignore vendored
View File

@@ -5,6 +5,7 @@
.env
env
env*
venv
dist
build
*.egg
@@ -24,3 +25,11 @@ tags
jumpserver.iml
.python-version
tmp/*
sessions/*
media
celerybeat.pid
django.db
celerybeat-schedule.db
data/static
docs/_build/
xpack

View File

@@ -1 +0,0 @@
system

View File

@@ -1,22 +0,0 @@
FROM jumpserver/python:v3.6.1
LABEL MAINTAINER Jumpserver Team <ibuler@qq.com>
COPY . /opt/jumpserver
WORKDIR /opt/jumpserver
RUN yum -y install epel-release && yum clean all -y
RUN cd requirements && yum -y install $(cat rpm_requirements.txt) && yum clean all -y
RUN cd requirements && pip install -r requirements.txt
RUN rm -f data/db.sqlite3
RUN rm -r .git
RUN rm -f config.py
VOLUME /opt/jumpserver/data
VOLUME /opt/jumpserver/logs
RUN cp config_docker.py config.py
EXPOSE 8080
CMD cd utils && sh make_migrations.sh && sh init_db.sh && cd .. && python run_server.py

View File

@@ -1,16 +0,0 @@
FROM centos:centos6
LABEL MAINTAINER Jumpserver Team <ibuler@qq.com>
WORKDIR /tmp
RUN yum -y install wget sqlite-devel xz gcc automake zlib-devel openssl-devel; yum clean all
# Install Python
RUN wget https://www.python.org/ftp/python/3.6.1/Python-3.6.1.tar.xz && \
tar xvf Python-3.6.1.tar.xz && cd Python-3.6.1 && ./configure && make && make install && \
rm -rf /tmp/{Python-3.6.1.tar.xz,Python-3.6.1}
RUN mv /usr/bin/python /usr/bin/python2
RUN ln -s /usr/local/bin/python3 /usr/bin/python && ln -s /usr/local/bin/pip3 /usr/bin/pip
RUN sed -i 's@/usr/bin/python@/usr/bin/python2@g' /usr/bin/yum

106
README.md
View File

@@ -1,110 +1,52 @@
## Jumpserver
[![Python3](https://img.shields.io/badge/python-3.6-green.svg?style=plastic)](https://www.python.org/)
[![Django](https://img.shields.io/badge/django-1.11-brightgreen.svg?style=plastic)](https://www.djangoproject.com/)
[![Ansible](https://img.shields.io/badge/ansible-2.2.2.0-blue.svg?style=plastic)](https://www.ansible.com/)
[![Paramiko](https://img.shields.io/badge/paramiko-2.1.2-green.svg?style=plastic)](http://www.paramiko.org/)
Jumpserver is a open source proxy server, developed by `Python` and `Django`, aim to help
companies to efficiently user, assets, authority and audit management
Jumpserver是一款使用Python, Django开发的开源跳板机系统, 助力互联网企业高效 用户、资产、权限、审计 管理
### Feature 功能
- Auth 统一认证
- CMDB 资产管理
- Perm 统一授权
- Audit 审计
- LDAP AUTH 支持LDAP认证
- Web terminal
- SSH Server
[![Django](https://img.shields.io/badge/django-2.1-brightgreen.svg?style=plastic)](https://www.djangoproject.com/)
[![Ansible](https://img.shields.io/badge/ansible-2.4.2.0-blue.svg?style=plastic)](https://www.ansible.com/)
[![Paramiko](https://img.shields.io/badge/paramiko-2.4.1-green.svg?style=plastic)](http://www.paramiko.org/)
### Environment 环境
* Python 3.6
* Django 1.11
----
### Install 安装
Using docker compose to setup it
Jumpserver是全球首款完全开源的堡垒机使用GNU GPL v2.0开源协议,是符合 4A 的专业运维审计系统。
使用docker compose 安装一键完成docker compose 安装见 docker官方
Jumpserver使用Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 解决方案,交互界面美观、用户体验好。
  $ docker-compose up
Jumpserver采纳分布式架构支持多机房跨区域部署中心节点提供 API各机房部署登录节点可横向扩展、无并发限制。
### Usage 使用
  1. Visit http://$HOST:8080 (访问 http://你的主机IP:8080 来访问 Jumpserver)
  2. Click left navigation visit Applications-Terminal and accept coco and luna register
(点击左侧 应用程序接受 Coco和Luna的注册)
  3. Click Assets-Admin user, Create admin user
(添加 管理用户)
4. Click Assets-System user, Create system user
(添加 系统用户)
  5. Click Assets-Asset, Add a asset
(添加 资产)
  6. Click Perms-Asset permission, Add a perm rule
(添加授权规则授权给admin)
  7. Connect ssh server coco (连接 ssh server coco)
ssh -p2222 $USER@$Host
  8. Visit web terminal server Luna, click server test connection
(访问 访问Luna点击左侧服务器连接测试)
http://$HOST:5000
### Snapshot 截图
改变世界,从一点点开始。
https://github.com/jumpserver/jumpserver/issues/438
----
### 功能
### Demo
![Jumpserver功能](https://jumpserver-release.oss-cn-hangzhou.aliyuncs.com/Jumpserver13.jpg "Jumpserver功能")
demo使用了开发者模式并发只能为1
### 开始使用
- Jumpserver: [访问](http://demo.jumpserver.org:8080) 账号: admin 密码: admin
快速开始文档 [Docker安装](http://docs.jumpserver.org/zh/docs/dockerinstall.html)
- Luna: [访问](http://demo.jumpserver.org:5000) 同Jumpserver认证
一步一步安装文档 [详细部署](http://docs.jumpserver.org/zh/docs/step_by_step.html)
- Coco: ssh -p 2222 admin@demo.jumpserver.org 密码: admin
也可以查看我们完整文档包括了使用和开发 [文档](http://docs.jumpserver.org)
### ROADMAP
### Demo 和 截图
参见 https://github.com/jumpserver/jumpserver/milestone/2
我们提供了DEMO和截图可以让你快速了解Jumpserver
### Docs 开发者文档
[DEMO](http://demo.jumpserver.org)
[截图](http://docs.jumpserver.org/zh/docs/snapshot.html)
### SDK
* [Project structure 项目结构描述](https://github.com/jumpserver/jumpserver/blob/dev/docs/project_structure.md)
* [Code style Python代码规范](https://github.com/jumpserver/jumpserver/blob/dev/docs/python_style_guide.md)
* [Api style API设计规范](https://github.com/jumpserver/jumpserver/blob/dev/docs/api_style_guide.md)
我们还编写了一些SDK供你其它系统快速和Jumpserver APi交互
### Contributor 贡献者
#### 0.4.0
- ibuler <广宏伟>
- 小彧 <李磊> Django资深开发者为users模块贡献了很多代码
- sofia <周小侠> 资深前端工程师, luna前端代码贡献者和现在维护者
- liuz <刘正> 全栈工程师, 编写了luna大部分代码
- jiaxiangkong <陈尚委> Jumpserver测试运营
- [python](https://github.com/jumpserver/jumpserver-python-sdk) Jumpserver其它组件使用这个SDK完成交互
- [java](https://github.com/KaiJunYan/jumpserver-java-sdk.git) 恺珺同学提供的Java版本的SDK
#### 0.3.2
- halcyon <王墉> DevOps 资深开发者, 0.3.2 核心开发者之一
- yumaojun03 <喻茂峻> DevOps 资深开发者jperm开发者擅长Python, Go以及PAAS平台开发
- kelianchun <柯连春> DevOps 资产开发者fix了很多connect.py bug
### 开发者群
如果你为Jumpserver贡献过代码请加一下群 需要验证一下你的github id
群号: 489385245
### License & Copyright
Copyright (c) 2014-2017 Beijing Duizhan Tech, Inc., All rights reserved.
Copyright (c) 2014-2018 Beijing Duizhan Tech, Inc., All rights reserved.
Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

View File

@@ -1,7 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#
if __name__ == '__main__':
pass
__version__ = "1.4.3"

View File

@@ -1,103 +0,0 @@
# -*- coding: utf-8 -*-
#
from collections import OrderedDict
import copy
from rest_framework.generics import ListCreateAPIView
from rest_framework import viewsets
from rest_framework.views import APIView, Response
from rest_framework.permissions import AllowAny
from django.shortcuts import get_object_or_404
from rest_framework.decorators import api_view
from .models import Terminal, TerminalHeatbeat
from .serializers import TerminalSerializer, TerminalHeatbeatSerializer
from .hands import IsSuperUserOrAppUser, IsAppUser, ProxyLog, \
IsSuperUserOrAppUserOrUserReadonly
from common.utils import get_object_or_none
class TerminalRegisterView(ListCreateAPIView):
queryset = Terminal.objects.all()
serializer_class = TerminalSerializer
permission_classes = (AllowAny,)
def create(self, request, *args, **kwargs):
name = request.data.get('name', '')
remote_addr = request.META.get('X-Real-IP') or \
request.META.get('REMOTE_ADDR')
serializer = self.serializer_class(
data={'name': name, 'remote_addr': remote_addr})
if get_object_or_none(Terminal, name=name):
return Response({'msg': 'Already register, Need '
'administrator active it'}, status=200)
if serializer.is_valid():
terminal = serializer.save()
app_user, access_key = terminal.create_related_app_user()
data = OrderedDict()
data['terminal'] = copy.deepcopy(serializer.data)
data['user'] = app_user.to_json()
data['access_key_id'] = access_key.id
data['access_key_secret'] = access_key.secret
return Response(data, status=201)
else:
data = {'msg': 'Not valid', 'detail': ';'.join(serializer.errors)}
return Response(data, status=400)
def list(self, request, *args, **kwargs):
return Response('', status=404)
class TerminalViewSet(viewsets.ModelViewSet):
queryset = Terminal.objects.all()
serializer_class = TerminalSerializer
permission_classes = (IsSuperUserOrAppUserOrUserReadonly,)
def create(self, request, *args, **kwargs):
return Response({'msg': 'Use register view except that'}, status=404)
# def destroy(self, request, *args, **kwargs):
# instance = self.get_object()
# if instance.user is not None:
# instance.user.delete()
# return super(TerminalViewSet, self).destroy(request, *args, **kwargs)
tasks = OrderedDict()
# tasks = {1: [{'name': 'kill_proxy', 'proxy_log_id': 23}]}
class TerminalHeatbeatViewSet(viewsets.ModelViewSet):
queryset = TerminalHeatbeat.objects.all()
serializer_class = TerminalHeatbeatSerializer
permission_classes = (IsAppUser,)
def create(self, request, *args, **kwargs):
terminal = request.user.terminal
TerminalHeatbeat.objects.create(terminal=terminal)
task = tasks.get(terminal.name)
tasks[terminal.name] = []
return Response({'msg': 'Success',
'tasks': task},
status=201)
class TerminateConnectionView(APIView):
def post(self, request, *args, **kwargs):
if isinstance(request.data, dict):
data = [request.data]
else:
data = request.data
for d in data:
proxy_log_id = d.get('proxy_log_id')
proxy_log = get_object_or_404(ProxyLog, id=proxy_log_id)
terminal_id = proxy_log.terminal
if terminal_id in tasks:
tasks[terminal_id].append({'name': 'kill_proxy',
'proxy_log_id': proxy_log_id})
else:
tasks[terminal_id] = [{'name': 'kill_proxy',
'proxy_log_id': proxy_log_id}]
return Response({'msg': 'get it'})

View File

@@ -1,7 +0,0 @@
from __future__ import unicode_literals
from django.apps import AppConfig
class ApplicationsConfig(AppConfig):
name = 'applications'

View File

@@ -1,18 +0,0 @@
# ~*~ coding: utf-8 ~*~
#
from django import forms
from .models import Terminal
class TerminalForm(forms.ModelForm):
class Meta:
model = Terminal
fields = ['name', 'remote_addr', 'type', 'url', 'comment']
help_texts = {
'url': 'Example: ssh://192.168.1.1:22 or http://jms.jumpserver.org, that user login'
}
widgets = {
'name': forms.TextInput(attrs={'readonly': 'readonly'})
}

View File

@@ -1,8 +0,0 @@
# -*- coding: utf-8 -*-
#
from users.models import User
from users.permissions import IsSuperUserOrAppUser, IsAppUser, \
IsSuperUserOrAppUserOrUserReadonly
from audits.models import ProxyLog
from users.utils import AdminUserRequiredMixin

View File

@@ -1,62 +0,0 @@
from __future__ import unicode_literals
from django.db import models
from django.utils.translation import ugettext_lazy as _
from users.models import User
class Terminal(models.Model):
TYPE_CHOICES = (
('SSH', 'SSH Terminal'),
('Web', 'Web Terminal')
)
name = models.CharField(max_length=30, unique=True, verbose_name=_('Name'))
remote_addr = models.GenericIPAddressField(verbose_name=_('Remote address'), blank=True, null=True)
type = models.CharField(choices=TYPE_CHOICES, max_length=3, blank=True, verbose_name=_('Terminal type'))
user = models.OneToOneField(User, related_name='terminal', verbose_name='Application user',
null=True, on_delete=models.CASCADE)
url = models.CharField(max_length=100, blank=True, verbose_name=_('URL to login'))
is_accepted = models.BooleanField(default=False, verbose_name='Is Accepted')
date_created = models.DateTimeField(auto_now_add=True)
comment = models.TextField(blank=True, verbose_name=_('Comment'))
@property
def is_active(self):
if self.user and self.user.is_active:
return True
return False
@is_active.setter
def is_active(self, active):
if self.user:
self.user.is_active = active
self.user.save()
def create_related_app_user(self):
user, access_key = User.create_app_user(name=self.name, comment=self.comment)
self.user = user
self.save()
return user, access_key
def delete(self, using=None, keep_parents=False):
if self.user:
self.user.delete()
return super(Terminal, self).delete(using=using, keep_parents=keep_parents)
def __unicode__(self):
active = 'Active' if self.user and self.user.is_active else 'Disabled'
return '%s: %s' % (self.name, active)
__str__ = __unicode__
class Meta:
ordering = ('is_accepted',)
class TerminalHeatbeat(models.Model):
terminal = models.ForeignKey(Terminal, on_delete=models.CASCADE)
date_created = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'terminal_heatbeat'

View File

@@ -1,41 +0,0 @@
# -*- coding: utf-8 -*-
#
from django.utils import timezone
from rest_framework import serializers
from .models import Terminal, TerminalHeatbeat
from .hands import ProxyLog
class TerminalSerializer(serializers.ModelSerializer):
proxy_online = serializers.SerializerMethodField()
is_alive = serializers.SerializerMethodField()
class Meta:
model = Terminal
fields = ['id', 'name', 'remote_addr', 'type', 'url', 'comment',
'is_accepted', 'is_active', 'get_type_display',
'proxy_online', 'is_alive']
@staticmethod
def get_proxy_online(obj):
return ProxyLog.objects.filter(terminal=obj.name, is_finished=False).count()
@staticmethod
def get_is_alive(obj):
log = obj.terminalheatbeat_set.last()
if log and timezone.now() - log.date_created < timezone.timedelta(seconds=600):
return True
else:
return False
class TerminalHeatbeatSerializer(serializers.ModelSerializer):
date_start = serializers.DateTimeField
class Meta:
model = TerminalHeatbeat
if __name__ == '__main__':
pass

View File

@@ -1,24 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from django.conf.urls import url
from rest_framework import routers
from .. import api
app_name = 'applications'
router = routers.DefaultRouter()
router.register(r'v1/terminal/heatbeat', api.TerminalHeatbeatViewSet, 'terminal-heatbeat')
router.register(r'v1/terminal', api.TerminalViewSet, 'terminal')
urlpatterns = [
url(r'^v1/terminal/register/$', api.TerminalRegisterView.as_view(),
name='terminal-register'),
url(r'^v1/terminate/connection/$', api.TerminateConnectionView.as_view(),
name='terminate-connection')
# url(r'^v1/terminal/heatbeat/$', api.TestHeatbeat.as_view())
]
urlpatterns += router.urls

View File

@@ -1,21 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from django.conf.urls import url
from .. import views
app_name = 'applications'
urlpatterns = [
url(r'^terminal/$', views.TerminalListView.as_view(), name='terminal-list'),
url(r'^terminal/(?P<pk>\d+)/$', views.TerminalDetailView.as_view(),
name='terminal-detail'),
url(r'^terminal/(?P<pk>\d+)/connect/$', views.TerminalConnectView.as_view(),
name='terminal-connect'),
url(r'^terminal/(?P<pk>\d+)/update$', views.TerminalUpdateView.as_view(),
name='terminal-update'),
url(r'^terminal/(?P<pk>\d+)/modal/accept$', views.TerminalModelAccept.as_view(),
name='terminal-modal-accept'),
]

View File

@@ -1,220 +0,0 @@
# ~*~ coding: utf-8 ~*~
# Copyright (C) 2014-2017 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
#
# Licensed under the GNU General Public License v2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.gnu.org/licenses/gpl-2.0.html
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from rest_framework import generics
from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
from django.shortcuts import get_object_or_404
from common.mixins import IDInFilterMixin
from common.utils import get_object_or_none
from .hands import IsSuperUser, IsAppUser, IsValidUser, \
get_user_granted_assets, push_users
from .models import AssetGroup, Asset, IDC, SystemUser, AdminUser
from . import serializers
from .tasks import update_assets_hardware_info
from .utils import test_admin_user_connective_manual
class AssetViewSet(IDInFilterMixin, BulkModelViewSet):
"""API endpoint that allows Asset to be viewed or edited."""
queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer
permission_classes = (IsValidUser,)
def get_queryset(self):
if self.request.user.is_superuser:
queryset = super(AssetViewSet, self).get_queryset()
else:
queryset = get_user_granted_assets(self.request.user)
idc_id = self.request.query_params.get('idc_id', '')
system_users_id = self.request.query_params.get('system_user_id', '')
asset_group_id = self.request.query_params.get('asset_group_id', '')
admin_user_id = self.request.query_params.get('admin_user_id', '')
if idc_id:
queryset = queryset.filter(idc__id=idc_id)
if system_users_id:
queryset = queryset.filter(system_users__id=system_users_id)
if admin_user_id:
queryset = queryset.filter(admin_user__id=admin_user_id)
if asset_group_id:
queryset = queryset.filter(groups__id=asset_group_id)
return queryset
class AssetGroupViewSet(IDInFilterMixin, BulkModelViewSet):
"""Asset group api set, for add,delete,update,list,retrieve resource"""
queryset = AssetGroup.objects.all()
serializer_class = serializers.AssetGroupSerializer
permission_classes = (IsSuperUser,)
class AssetUpdateGroupApi(generics.RetrieveUpdateAPIView):
"""Asset update it's group api"""
queryset = Asset.objects.all()
serializer_class = serializers.AssetUpdateGroupSerializer
permission_classes = (IsSuperUser,)
class AssetGroupUpdateApi(generics.RetrieveUpdateAPIView):
"""Asset group, update it's asset member"""
queryset = AssetGroup.objects.all()
serializer_class = serializers.AssetGroupUpdateSerializer
permission_classes = (IsSuperUser,)
class AssetGroupUpdateSystemUserApi(generics.RetrieveUpdateAPIView):
"""Asset group push system user"""
queryset = AssetGroup.objects.all()
serializer_class = serializers.AssetGroupUpdateSystemUserSerializer
permission_classes = (IsSuperUser,)
class IDCUpdateAssetsApi(generics.RetrieveUpdateAPIView):
"""IDC update asset member"""
queryset = IDC.objects.all()
serializer_class = serializers.IDCUpdateAssetsSerializer
permission_classes = (IsSuperUser,)
class IDCViewSet(IDInFilterMixin, BulkModelViewSet):
"""IDC api set, for add,delete,update,list,retrieve resource"""
queryset = IDC.objects.all()
serializer_class = serializers.IDCSerializer
permission_classes = (IsSuperUser,)
class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet):
"""Admin user api set, for add,delete,update,list,retrieve resource"""
queryset = AdminUser.objects.all()
serializer_class = serializers.AdminUserSerializer
permission_classes = (IsSuperUser,)
class SystemUserViewSet(IDInFilterMixin, BulkModelViewSet):
"""System user api set, for add,delete,update,list,retrieve resource"""
queryset = SystemUser.objects.all()
serializer_class = serializers.SystemUserSerializer
permission_classes = (IsSuperUser,)
class SystemUserUpdateApi(generics.RetrieveUpdateAPIView):
"""Asset update it's system user
when update then push system user to asset.
"""
queryset = Asset.objects.all()
serializer_class = serializers.AssetUpdateSystemUserSerializer
permission_classes = (IsSuperUser,)
def patch(self, request, *args, **kwargs):
asset = self.get_object()
old_system_users = set(asset.system_users.all())
response = super(SystemUserUpdateApi, self).patch(request, *args, **kwargs)
system_users_new = set(asset.system_users.all())
system_users = system_users_new - old_system_users
system_users = [system_user._to_secret_json() for system_user in system_users]
push_users.delay([asset._to_secret_json()], system_users)
return response
class SystemUserUpdateAssetsApi(generics.RetrieveUpdateAPIView):
"""System user update it's assets"""
queryset = SystemUser.objects.all()
serializer_class = serializers.SystemUserUpdateAssetsSerializer
permission_classes = (IsSuperUser,)
class SystemUserUpdateAssetGroupApi(generics.RetrieveUpdateAPIView):
"""System user update asset group"""
queryset = SystemUser.objects.all()
serializer_class = serializers.SystemUserUpdateAssetGroupSerializer
permission_classes = (IsSuperUser,)
class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
"""Asset bulk update api"""
queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer
permission_classes = (IsSuperUser,)
class SystemUserAuthInfoApi(generics.RetrieveAPIView):
"""Get system user auth info"""
queryset = SystemUser.objects.all()
permission_classes = (IsAppUser,)
def retrieve(self, request, *args, **kwargs):
system_user = self.get_object()
data = {
'id': system_user.id,
'name': system_user.name,
'username': system_user.username,
'password': system_user.password,
'private_key': system_user.private_key,
'auth_method': system_user.auth_method,
}
return Response(data)
class AssetRefreshHardwareView(generics.RetrieveAPIView):
"""Refresh asset hardware info"""
queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer
permission_classes = (IsSuperUser,)
def retrieve(self, request, *args, **kwargs):
asset_id = kwargs.get('pk')
asset = get_object_or_404(Asset, pk=asset_id)
summary = update_assets_hardware_info([asset])
if len(summary['failed']) == 0:
return super(AssetRefreshHardwareView, self).retrieve(request, *args, **kwargs)
else:
return Response('', status=502)
class AssetAdminUserTestView(AssetRefreshHardwareView):
"""Test asset admin user connectivity"""
queryset = Asset.objects.all()
permission_classes = (IsSuperUser,)
def retrieve(self, request, *args, **kwargs):
asset_id = kwargs.get('pk')
asset = get_object_or_404(Asset, pk=asset_id)
result = test_admin_user_connective_manual([asset])
if result:
return Response('1')
else:
return Response('0', status=502)
class AssetGroupPushSystemUserView(generics.UpdateAPIView):
"""Asset group push system user api"""
queryset = AssetGroup.objects.all()
permission_classes = (IsSuperUser,)
serializer_class = serializers.AssetSerializer
def patch(self, request, *args, **kwargs):
asset_group = self.get_object()
assets = asset_group.assets.all()
system_user_id = self.request.data['system_user']
system_user = get_object_or_none(SystemUser, id=system_user_id)
if not assets or not system_user:
return Response('Invalid system user id or asset group id', status=404)
task = push_users.delay([asset._to_secret_json() for asset in assets],
system_user._to_secret_json())
return Response(task.id)

View File

@@ -0,0 +1,7 @@
from .admin_user import *
from .asset import *
from .label import *
from .system_user import *
from .node import *
from .domain import *
from .cmd_filter import *

View File

@@ -0,0 +1,83 @@
# ~*~ coding: utf-8 ~*~
# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
#
# Licensed under the GNU General Public License v2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.gnu.org/licenses/gpl-2.0.html
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.db import transaction
from rest_framework import generics
from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
from common.mixins import IDInFilterMixin
from common.utils import get_logger
from ..hands import IsOrgAdmin
from ..models import AdminUser, Asset
from .. import serializers
from ..tasks import test_admin_user_connectability_manual
logger = get_logger(__file__)
__all__ = [
'AdminUserViewSet', 'ReplaceNodesAdminUserApi',
'AdminUserTestConnectiveApi', 'AdminUserAuthApi',
]
class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet):
"""
Admin user api set, for add,delete,update,list,retrieve resource
"""
queryset = AdminUser.objects.all()
serializer_class = serializers.AdminUserSerializer
permission_classes = (IsOrgAdmin,)
class AdminUserAuthApi(generics.UpdateAPIView):
queryset = AdminUser.objects.all()
serializer_class = serializers.AdminUserAuthSerializer
permission_classes = (IsOrgAdmin,)
class ReplaceNodesAdminUserApi(generics.UpdateAPIView):
queryset = AdminUser.objects.all()
serializer_class = serializers.ReplaceNodeAdminUserSerializer
permission_classes = (IsOrgAdmin,)
def update(self, request, *args, **kwargs):
admin_user = self.get_object()
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
nodes = serializer.validated_data['nodes']
assets = []
for node in nodes:
assets.extend([asset.id for asset in node.get_all_assets()])
with transaction.atomic():
Asset.objects.filter(id__in=assets).update(admin_user=admin_user)
return Response({"msg": "ok"})
else:
return Response({'error': serializer.errors}, status=400)
class AdminUserTestConnectiveApi(generics.RetrieveAPIView):
"""
Test asset admin user connectivity
"""
queryset = AdminUser.objects.all()
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
admin_user = self.get_object()
task = test_admin_user_connectability_manual.delay(admin_user)
return Response({"task": task.id})

132
apps/assets/api/asset.py Normal file
View File

@@ -0,0 +1,132 @@
# -*- coding: utf-8 -*-
#
import random
from rest_framework import generics
from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
from rest_framework.pagination import LimitOffsetPagination
from django.shortcuts import get_object_or_404
from django.db.models import Q
from common.mixins import IDInFilterMixin
from common.utils import get_logger
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from ..models import Asset, AdminUser, Node
from .. import serializers
from ..tasks import update_asset_hardware_info_manual, \
test_asset_connectability_manual
from ..utils import LabelFilter
logger = get_logger(__file__)
__all__ = [
'AssetViewSet', 'AssetListUpdateApi',
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi',
'AssetGatewayApi'
]
class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
"""
API endpoint that allows Asset to be viewed or edited.
"""
filter_fields = ("hostname", "ip")
search_fields = filter_fields
ordering_fields = ("hostname", "ip", "port", "cpu_cores")
queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer
pagination_class = LimitOffsetPagination
permission_classes = (IsOrgAdminOrAppUser,)
def filter_node(self):
node_id = self.request.query_params.get("node_id")
if not node_id:
return
node = get_object_or_404(Node, id=node_id)
show_current_asset = self.request.query_params.get("show_current_asset") in ('1', 'true')
if node.is_root():
if show_current_asset:
self.queryset = self.queryset.filter(
Q(nodes=node_id) | Q(nodes__isnull=True)
).distinct()
return
if show_current_asset:
self.queryset = self.queryset.filter(nodes=node).distinct()
else:
self.queryset = self.queryset.filter(
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
).distinct()
def filter_admin_user_id(self):
admin_user_id = self.request.query_params.get('admin_user_id')
if admin_user_id:
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
self.queryset = self.queryset.filter(admin_user=admin_user)
def get_queryset(self):
self.queryset = super().get_queryset()\
.prefetch_related('labels', 'nodes')\
.select_related('admin_user')
self.filter_admin_user_id()
self.filter_node()
return self.queryset
class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
"""
Asset bulk update api
"""
queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer
permission_classes = (IsOrgAdmin,)
class AssetRefreshHardwareApi(generics.RetrieveAPIView):
"""
Refresh asset hardware info
"""
queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
asset_id = kwargs.get('pk')
asset = get_object_or_404(Asset, pk=asset_id)
task = update_asset_hardware_info_manual.delay(asset)
return Response({"task": task.id})
class AssetAdminUserTestApi(generics.RetrieveAPIView):
"""
Test asset admin user connectivity
"""
queryset = Asset.objects.all()
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
asset_id = kwargs.get('pk')
asset = get_object_or_404(Asset, pk=asset_id)
task = test_asset_connectability_manual.delay(asset)
return Response({"task": task.id})
class AssetGatewayApi(generics.RetrieveAPIView):
queryset = Asset.objects.all()
permission_classes = (IsOrgAdminOrAppUser,)
def retrieve(self, request, *args, **kwargs):
asset_id = kwargs.get('pk')
asset = get_object_or_404(Asset, pk=asset_id)
if asset.domain and \
asset.domain.gateways.filter(protocol=asset.protocol).exists():
gateway = random.choice(asset.domain.gateways.filter(protocol=asset.protocol))
serializer = serializers.GatewayWithAuthSerializer(instance=gateway)
return Response(serializer.data)
else:
return Response({"msg": "Not have gateway"}, status=404)

View File

@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
#
from rest_framework_bulk import BulkModelViewSet
from django.shortcuts import get_object_or_404
from ..hands import IsOrgAdmin
from ..models import CommandFilter, CommandFilterRule
from .. import serializers
__all__ = ['CommandFilterViewSet', 'CommandFilterRuleViewSet']
class CommandFilterViewSet(BulkModelViewSet):
permission_classes = (IsOrgAdmin,)
queryset = CommandFilter.objects.all()
serializer_class = serializers.CommandFilterSerializer
class CommandFilterRuleViewSet(BulkModelViewSet):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.CommandFilterRuleSerializer
def get_queryset(self):
fpk = self.kwargs.get('filter_pk')
if not fpk:
return CommandFilterRule.objects.none()
group = get_object_or_404(CommandFilter, pk=fpk)
return group.rules.all().order_by('priority')

54
apps/assets/api/domain.py Normal file
View File

@@ -0,0 +1,54 @@
# ~*~ coding: utf-8 ~*~
from rest_framework_bulk import BulkModelViewSet
from rest_framework.views import APIView, Response
from django.views.generic.detail import SingleObjectMixin
from common.utils import get_logger
from common.permissions import IsOrgAdmin, IsAppUser, IsOrgAdminOrAppUser
from ..models import Domain, Gateway
from ..utils import test_gateway_connectability
from .. import serializers
logger = get_logger(__file__)
__all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"]
class DomainViewSet(BulkModelViewSet):
queryset = Domain.objects.all()
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.DomainSerializer
def get_serializer_class(self):
if self.request.query_params.get('gateway'):
return serializers.DomainWithGatewaySerializer
return super().get_serializer_class()
def get_permissions(self):
if self.request.query_params.get('gateway'):
self.permission_classes = (IsOrgAdminOrAppUser,)
return super().get_permissions()
class GatewayViewSet(BulkModelViewSet):
filter_fields = ("domain",)
search_fields = filter_fields
queryset = Gateway.objects.all()
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.GatewaySerializer
class GatewayTestConnectionApi(SingleObjectMixin, APIView):
permission_classes = (IsOrgAdmin,)
model = Gateway
object = None
def get(self, request, *args, **kwargs):
self.object = self.get_object(Gateway.objects.all())
ok, e = test_gateway_connectability(self.object)
if ok:
return Response("ok")
else:
return Response({"failed": e}, status=404)

41
apps/assets/api/label.py Normal file
View File

@@ -0,0 +1,41 @@
# ~*~ coding: utf-8 ~*~
# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
#
# Licensed under the GNU General Public License v2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.gnu.org/licenses/gpl-2.0.html
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from rest_framework_bulk import BulkModelViewSet
from django.db.models import Count
from common.utils import get_logger
from ..hands import IsOrgAdmin
from ..models import Label
from .. import serializers
logger = get_logger(__file__)
__all__ = ['LabelViewSet']
class LabelViewSet(BulkModelViewSet):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.LabelSerializer
def list(self, request, *args, **kwargs):
if request.query_params.get("distinct"):
self.serializer_class = serializers.LabelDistinctSerializer
self.queryset = self.queryset.values("name").distinct()
return super().list(request, *args, **kwargs)
def get_queryset(self):
self.queryset = Label.objects.annotate(asset_count=Count("assets"))
return self.queryset

238
apps/assets/api/node.py Normal file
View File

@@ -0,0 +1,238 @@
# ~*~ coding: utf-8 ~*~
# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
#
# Licensed under the GNU General Public License v2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.gnu.org/licenses/gpl-2.0.html
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from rest_framework import generics, mixins, viewsets
from rest_framework.serializers import ValidationError
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
from django.utils.translation import ugettext_lazy as _
from django.shortcuts import get_object_or_404
from django.db.models import Count
from common.utils import get_logger, get_object_or_none
from ..hands import IsOrgAdmin
from ..models import Node
from ..tasks import update_assets_hardware_info_util, test_asset_connectability_util
from .. import serializers
logger = get_logger(__file__)
__all__ = [
'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi',
'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'NodeReplaceAssetsApi',
'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi',
'TestNodeConnectiveApi'
]
class NodeViewSet(viewsets.ModelViewSet):
queryset = Node.objects.all()
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeSerializer
def perform_create(self, serializer):
child_key = Node.root().get_next_child_key()
serializer.validated_data["key"] = child_key
serializer.save()
def update(self, request, *args, **kwargs):
node = self.get_object()
if node.is_root():
node_value = node.value
post_value = request.data.get('value')
if node_value != post_value:
return Response(
{"msg": _("You can't update the root node name")},
status=400
)
return super().update(request, *args, **kwargs)
class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
queryset = Node.objects.all()
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeSerializer
instance = None
def counter(self):
values = [
child.value[child.value.rfind(' '):]
for child in self.get_object().get_children()
if child.value.startswith("新节点 ")
]
values = [int(value) for value in values if value.strip().isdigit()]
count = max(values)+1 if values else 1
return count
def post(self, request, *args, **kwargs):
if not request.data.get("value"):
request.data["value"] = _("New node {}").format(self.counter())
return super().post(request, *args, **kwargs)
def create(self, request, *args, **kwargs):
instance = self.get_object()
value = request.data.get("value")
values = [child.value for child in instance.get_children()]
if value in values:
raise ValidationError(
'The same level node name cannot be the same'
)
node = instance.create_child(value=value)
return Response(
{"id": node.id, "key": node.key, "value": node.value},
status=201,
)
def get_object(self):
pk = self.kwargs.get('pk') or self.request.query_params.get('id')
if not pk:
node = None
else:
node = get_object_or_404(Node, pk=pk)
return node
def get_queryset(self):
queryset = []
query_all = self.request.query_params.get("all")
query_assets = self.request.query_params.get('assets')
node = self.get_object()
if node is None:
node = Node.root()
node.assets__count = node.get_all_assets().count()
queryset.append(node)
if query_all:
children = node.get_all_children()
else:
children = node.get_children()
queryset.extend(list(children))
if query_assets:
assets = node.get_assets()
for asset in assets:
node_fake = Node()
node_fake.assets__count = 0
node_fake.id = asset.id
node_fake.is_node = False
node_fake.key = node.key + ':0'
node_fake.value = asset.hostname
queryset.append(node_fake)
queryset = sorted(queryset, key=lambda x: x.is_node, reverse=True)
return queryset
def get(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
class NodeAssetsApi(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetSerializer
def get_queryset(self):
node_id = self.kwargs.get('pk')
query_all = self.request.query_params.get('all')
instance = get_object_or_404(Node, pk=node_id)
if query_all:
return instance.get_all_assets()
else:
return instance.get_assets()
class NodeAddChildrenApi(generics.UpdateAPIView):
queryset = Node.objects.all()
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeAddChildrenSerializer
instance = None
def put(self, request, *args, **kwargs):
instance = self.get_object()
nodes_id = request.data.get("nodes")
children = [get_object_or_none(Node, id=pk) for pk in nodes_id]
for node in children:
if not node:
continue
node.parent = instance
return Response("OK")
class NodeAddAssetsApi(generics.UpdateAPIView):
serializer_class = serializers.NodeAssetsSerializer
queryset = Node.objects.all()
permission_classes = (IsOrgAdmin,)
instance = None
def perform_update(self, serializer):
assets = serializer.validated_data.get('assets')
instance = self.get_object()
instance.assets.add(*tuple(assets))
class NodeRemoveAssetsApi(generics.UpdateAPIView):
serializer_class = serializers.NodeAssetsSerializer
queryset = Node.objects.all()
permission_classes = (IsOrgAdmin,)
instance = None
def perform_update(self, serializer):
assets = serializer.validated_data.get('assets')
instance = self.get_object()
if instance != Node.root():
instance.assets.remove(*tuple(assets))
else:
assets = [asset for asset in assets if asset.nodes.count() > 1]
instance.assets.remove(*tuple(assets))
class NodeReplaceAssetsApi(generics.UpdateAPIView):
serializer_class = serializers.NodeAssetsSerializer
queryset = Node.objects.all()
permission_classes = (IsOrgAdmin,)
instance = None
def perform_update(self, serializer):
assets = serializer.validated_data.get('assets')
instance = self.get_object()
for asset in assets:
asset.nodes.set([instance])
class RefreshNodeHardwareInfoApi(APIView):
permission_classes = (IsOrgAdmin,)
model = Node
def get(self, request, *args, **kwargs):
node_id = kwargs.get('pk')
node = get_object_or_404(self.model, id=node_id)
assets = node.assets.all()
# task_name = _("更新节点资产硬件信息: {}".format(node.name))
task_name = _("Update node asset hardware information: {}").format(node.name)
task = update_assets_hardware_info_util.delay(assets, task_name=task_name)
return Response({"task": task.id})
class TestNodeConnectiveApi(APIView):
permission_classes = (IsOrgAdmin,)
model = Node
def get(self, request, *args, **kwargs):
node_id = kwargs.get('pk')
node = get_object_or_404(self.model, id=node_id)
assets = node.assets.all()
# task_name = _("测试节点下资产是否可连接: {}".format(node.name))
task_name = _("Test if the assets under the node are connectable: {}".format(node.name))
task = test_asset_connectability_util.delay(assets, task_name=task_name)
return Response({"task": task.id})

View File

@@ -0,0 +1,143 @@
# ~*~ coding: utf-8 ~*~
# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
#
# Licensed under the GNU General Public License v2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.gnu.org/licenses/gpl-2.0.html
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.shortcuts import get_object_or_404
from rest_framework import generics
from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
from rest_framework.pagination import LimitOffsetPagination
from common.utils import get_logger
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from ..models import SystemUser, Asset
from .. import serializers
from ..tasks import push_system_user_to_assets_manual, \
test_system_user_connectability_manual, push_system_user_a_asset_manual, \
test_system_user_connectability_a_asset
logger = get_logger(__file__)
__all__ = [
'SystemUserViewSet', 'SystemUserAuthInfoApi',
'SystemUserPushApi', 'SystemUserTestConnectiveApi',
'SystemUserAssetsListView', 'SystemUserPushToAssetApi',
'SystemUserTestAssetConnectabilityApi', 'SystemUserCommandFilterRuleListApi',
]
class SystemUserViewSet(BulkModelViewSet):
"""
System user api set, for add,delete,update,list,retrieve resource
"""
queryset = SystemUser.objects.all()
serializer_class = serializers.SystemUserSerializer
permission_classes = (IsOrgAdminOrAppUser,)
class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
"""
Get system user auth info
"""
queryset = SystemUser.objects.all()
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.SystemUserAuthSerializer
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
instance.clear_auth()
return Response(status=204)
class SystemUserPushApi(generics.RetrieveAPIView):
"""
Push system user to cluster assets api
"""
queryset = SystemUser.objects.all()
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
system_user = self.get_object()
nodes = system_user.nodes.all()
for node in nodes:
system_user.assets.add(*tuple(node.get_all_assets()))
task = push_system_user_to_assets_manual.delay(system_user)
return Response({"task": task.id})
class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
"""
Push system user to cluster assets api
"""
queryset = SystemUser.objects.all()
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
system_user = self.get_object()
task = test_system_user_connectability_manual.delay(system_user)
return Response({"task": task.id})
class SystemUserAssetsListView(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetSerializer
pagination_class = LimitOffsetPagination
filter_fields = ("hostname", "ip")
search_fields = filter_fields
def get_object(self):
pk = self.kwargs.get('pk')
return get_object_or_404(SystemUser, pk=pk)
def get_queryset(self):
system_user = self.get_object()
return system_user.assets.all()
class SystemUserPushToAssetApi(generics.RetrieveAPIView):
queryset = SystemUser.objects.all()
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
system_user = self.get_object()
asset_id = self.kwargs.get('aid')
asset = get_object_or_404(Asset, id=asset_id)
task = push_system_user_a_asset_manual.delay(system_user, asset)
return Response({"task": task.id})
class SystemUserTestAssetConnectabilityApi(generics.RetrieveAPIView):
queryset = SystemUser.objects.all()
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
system_user = self.get_object()
asset_id = self.kwargs.get('aid')
asset = get_object_or_404(Asset, id=asset_id)
task = test_system_user_connectability_a_asset.delay(system_user, asset)
return Response({"task": task.id})
class SystemUserCommandFilterRuleListApi(generics.ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
def get_serializer_class(self):
from ..serializers import CommandFilterRuleSerializer
return CommandFilterRuleSerializer
def get_queryset(self):
pk = self.kwargs.get('pk', None)
system_user = get_object_or_404(SystemUser, pk=pk)
return system_user.cmd_filter_rules

View File

@@ -5,3 +5,7 @@ from django.apps import AppConfig
class AssetsConfig(AppConfig):
name = 'assets'
def ready(self):
from . import signals_handler
super().ready()

38
apps/assets/const.py Normal file
View File

@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
#
UPDATE_ASSETS_HARDWARE_TASKS = [
{
'name': "setup",
'action': {
'module': 'setup'
}
}
]
ADMIN_USER_CONN_CACHE_KEY = "ADMIN_USER_CONN_{}"
TEST_ADMIN_USER_CONN_TASKS = [
{
"name": "ping",
"action": {
"module": "ping",
}
}
]
ASSET_ADMIN_CONN_CACHE_KEY = "ASSET_ADMIN_USER_CONN_{}"
SYSTEM_USER_CONN_CACHE_KEY = "SYSTEM_USER_CONN_{}"
TEST_SYSTEM_USER_CONN_TASKS = [
{
"name": "ping",
"action": {
"module": "ping",
}
}
]
TASK_OPTIONS = {
'timeout': 10,
'forks': 10,
}

View File

@@ -1,361 +0,0 @@
# coding:utf-8
from django import forms
from django.utils.translation import gettext_lazy as _
from .models import IDC, Asset, AssetGroup, AdminUser, SystemUser
from common.utils import validate_ssh_private_key, ssh_pubkey_gen, ssh_key_gen, get_logger
logger = get_logger(__file__)
class AssetCreateForm(forms.ModelForm):
class Meta:
model = Asset
fields = [
'hostname', 'ip', 'public_ip', 'port', 'type', 'comment',
'admin_user', 'idc', 'groups', 'status', 'env', 'is_active'
]
widgets = {
'groups': forms.SelectMultiple(
attrs={'class': 'select2',
'data-placeholder': _('Select asset groups')}),
'admin_user': forms.Select(
attrs={'class': 'select2',
'data-placeholder': _('Select asset admin user')}),
}
help_texts = {
'hostname': '* required',
'ip': '* required',
'system_users': _('System user will be granted for user to login '
'assets (using ansible create automatic)'),
'admin_user': _('Admin user should be exist on asset already, '
'And have sudo ALL permission'),
}
def clean_admin_user(self):
if not self.cleaned_data['admin_user']:
raise forms.ValidationError(_('Select admin user'))
return self.cleaned_data['admin_user']
class AssetUpdateForm(forms.ModelForm):
class Meta:
model = Asset
fields = [
'hostname', 'ip', 'port', 'groups', 'admin_user', 'idc', 'is_active',
'type', 'env', 'status', 'public_ip', 'remote_card_ip', 'cabinet_no',
'cabinet_pos', 'number', 'comment'
]
widgets = {
'groups': forms.SelectMultiple(
attrs={'class': 'select2',
'data-placeholder': _('Select asset groups')}),
'admin_user': forms.Select(
attrs={'class': 'select2',
'data-placeholder': _('Select asset admin user')}),
}
help_texts = {
'hostname': '* required',
'ip': '* required',
'system_users': _('System user will be granted for user '
'to login assets (using ansible create automatic)'),
'admin_user': _('Admin user should be exist on asset '
'already, And have sudo ALL permission'),
}
class AssetBulkUpdateForm(forms.ModelForm):
assets = forms.MultipleChoiceField(
required=True,
help_text='* required',
label=_('Select assets'),
widget=forms.SelectMultiple(
attrs={
'class': 'select2',
'data-placeholder': _('Select assets')
}
)
)
port = forms.IntegerField(min_value=1, max_value=65535,
required=False, label=_('Port'))
class Meta:
model = Asset
fields = [
'assets', 'port', 'groups', 'admin_user', 'idc',
'type', 'env', 'status',
]
widgets = {
'groups': forms.SelectMultiple(
attrs={'class': 'select2',
'data-placeholder': _('Select asset groups')}),
'admin_user': forms.Select(
attrs={'class': 'select2',
'data-placeholder': _('Select asset admin user')}),
}
def save(self, commit=True):
cleaned_data = {k: v for k, v in self.cleaned_data.items() if v is not None}
assets_id = cleaned_data.pop('assets')
groups = cleaned_data.pop('groups')
assets = Asset.objects.filter(id__in=assets_id)
assets.update(**cleaned_data)
if groups:
for asset in assets:
asset.groups.set(groups)
return assets
class AssetGroupForm(forms.ModelForm):
# See AdminUserForm comment same it
assets = forms.ModelMultipleChoiceField(
queryset=Asset.objects.all(),
label=_('Asset'),
required=False,
widget=forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Select assets')})
)
def __init__(self, *args, **kwargs):
if kwargs.get('instance', None):
initial = kwargs.get('initial', {})
initial['assets'] = kwargs['instance'].assets.all()
super(AssetGroupForm, self).__init__(*args, **kwargs)
def _save_m2m(self):
super(AssetGroupForm, self)._save_m2m()
assets = self.cleaned_data['assets']
self.instance.assets.clear()
self.instance.assets.add(*tuple(assets))
class Meta:
model = AssetGroup
fields = [
"name", "comment",
]
help_texts = {
'name': '* required',
}
class IDCForm(forms.ModelForm):
# See AdminUserForm comment same it
assets = forms.ModelMultipleChoiceField(
queryset=Asset.objects.all(),
label=_('Asset'),
required=False,
widget=forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Select assets')})
)
def __init__(self, *args, **kwargs):
if kwargs.get('instance'):
initial = kwargs.get('initial', {})
initial['assets'] = kwargs['instance'].assets.all()
super(IDCForm, self).__init__(*args, **kwargs)
def _save_m2m(self):
super(IDCForm, self)._save_m2m()
assets = self.cleaned_data['assets']
self.instance.assets.clear()
self.instance.assets.add(*tuple(assets))
class Meta:
model = IDC
fields = ['name', "bandwidth", "operator", 'contact',
'phone', 'address', 'intranet', 'extranet', 'comment']
widgets = {
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
'intranet': forms.Textarea(
attrs={'placeholder': 'IP段之间用逗号隔开192.168.1.0/24,192.168.1.0/24'}),
'extranet': forms.Textarea(
attrs={'placeholder': 'IP段之间用逗号隔开201.1.32.1/24,202.2.32.1/24'})
}
help_texts = {
'name': '* required'
}
class AdminUserForm(forms.ModelForm):
# Form field name can not start with `_`, so redefine it,
password = forms.CharField(
widget=forms.PasswordInput, max_length=100,
strip=True, required=False,
help_text=_('If also set private key, use that first'),
)
# Need use upload private key file except paste private key content
private_key_file = forms.FileField(required=False)
def save(self, commit=True):
# Because we define custom field, so we need rewrite :method: `save`
admin_user = super(AdminUserForm, self).save(commit=commit)
password = self.cleaned_data['password']
private_key = self.cleaned_data['private_key_file']
if password:
admin_user.password = password
if private_key:
public_key = ssh_pubkey_gen(private_key)
admin_user.private_key = private_key
admin_user.public_key = public_key
admin_user.save()
return admin_user
def clean_private_key_file(self):
private_key_file = self.cleaned_data['private_key_file']
if private_key_file:
private_key = private_key_file.read()
if not validate_ssh_private_key(private_key):
raise forms.ValidationError(_('Invalid private key'))
return private_key
return private_key_file
def clean(self):
password = self.cleaned_data['password']
private_key_file = self.cleaned_data.get('private_key_file', '')
if not self.instance and not (password or private_key_file):
raise forms.ValidationError(
_('Password and private key file must be input one'))
class Meta:
model = AdminUser
fields = ['name', 'username', 'password',
'private_key_file', 'comment']
widgets = {
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
}
help_texts = {
'name': '* required',
'username': '* required',
}
class SystemUserForm(forms.ModelForm):
# Admin user assets define, let user select, save it in form not in view
auto_generate_key = forms.BooleanField(initial=True, required=False)
# Form field name can not start with `_`, so redefine it,
password = forms.CharField(widget=forms.PasswordInput, required=False,
max_length=100, strip=True)
# Need use upload private key file except paste private key content
private_key_file = forms.FileField(required=False)
def __init__(self, *args, **kwargs):
super(SystemUserForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
# Because we define custom field, so we need rewrite :method: `save`
system_user = super(SystemUserForm, self).save(commit=commit)
password = self.cleaned_data['password']
private_key_file = self.cleaned_data.get('private_key_file')
if system_user.auth_method == 'P':
if password:
system_user.password = password
elif system_user.auth_method == 'K':
if self.cleaned_data['auto_generate_key']:
private_key, public_key = ssh_key_gen(username=system_user.name)
logger.info('Generate private key and public key')
else:
private_key = private_key_file.read().strip()
public_key = ssh_pubkey_gen(private_key=private_key)
system_user.private_key = private_key
system_user.public_key = public_key
system_user.save()
return self.instance
def clean_private_key_file(self):
if self.data['auth_method'] == 'K' and \
not self.cleaned_data['auto_generate_key']:
if not self.cleaned_data['private_key_file']:
raise forms.ValidationError(_('Private key required'))
else:
key_string = self.cleaned_data['private_key_file'].read()
self.cleaned_data['private_key_file'].seek(0)
if not validate_ssh_private_key(key_string):
raise forms.ValidationError(_('Invalid private key'))
return self.cleaned_data['private_key_file']
def clean_password(self):
if self.data['auth_method'] == 'P':
if not self.cleaned_data.get('password'):
raise forms.ValidationError(_('Password required'))
return self.cleaned_data['password']
class Meta:
model = SystemUser
fields = [
'name', 'username', 'protocol', 'auto_generate_key', 'password',
'private_key_file', 'auth_method', 'auto_push', 'sudo',
'comment', 'shell'
]
widgets = {
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
}
help_texts = {
'name': '* required',
'username': '* required',
'auto_push': 'Auto push system user to asset',
}
class SystemUserUpdateForm(forms.ModelForm):
# Admin user assets define, let user select, save it in form not in view
auto_generate_key = forms.BooleanField(initial=False, required=False)
# Form field name can not start with `_`, so redefine it,
password = forms.CharField(widget=forms.PasswordInput, required=False,
max_length=100, strip=True)
# Need use upload private key file except paste private key content
private_key_file = forms.FileField(required=False)
def __init__(self, *args, **kwargs):
super(SystemUserUpdateForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
# Because we define custom field, so we need rewrite :method: `save`
system_user = super(SystemUserUpdateForm, self).save(commit=commit)
password = self.cleaned_data['password']
private_key_file = self.cleaned_data.get('private_key_file')
if system_user.auth_method == 'P' and password:
system_user.password = password
elif system_user.auth_method == 'K' and private_key_file:
private_key = private_key_file.read().strip()
public_key = ssh_pubkey_gen(private_key=private_key)
system_user.private_key = private_key
system_user.public_key = public_key
system_user.save()
return self.instance
def clean_private_key_file(self):
if self.data['auth_method'] == 'K' and self.cleaned_data['private_key_file']:
key_string = self.cleaned_data['private_key_file'].read()
self.cleaned_data['private_key_file'].seek(0)
if not validate_ssh_private_key(key_string):
raise forms.ValidationError(_('Invalid private key'))
return self.cleaned_data['private_key_file']
class Meta:
model = SystemUser
fields = [
'name', 'username', 'protocol', 'auto_generate_key', 'password',
'private_key_file', 'auth_method', 'auto_push', 'sudo',
'comment', 'shell'
]
widgets = {
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
}
help_texts = {
'name': '* required',
'username': '* required',
'auto_push': 'Auto push system user to asset',
}
class FileForm(forms.Form):
file = forms.FileField()

View File

@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
#
from .asset import *
from .label import *
from .user import *
from .domain import *
from .cmd_filter import *

155
apps/assets/forms/asset.py Normal file
View File

@@ -0,0 +1,155 @@
# -*- coding: utf-8 -*-
#
from django import forms
from django.utils.translation import gettext_lazy as _
from common.utils import get_logger
from orgs.mixins import OrgModelForm
from ..models import Asset, AdminUser
logger = get_logger(__file__)
__all__ = ['AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm']
class AssetCreateForm(OrgModelForm):
class Meta:
model = Asset
fields = [
'hostname', 'ip', 'public_ip', 'port', 'comment',
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
'domain', 'protocol',
]
widgets = {
'nodes': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Nodes')
}),
'admin_user': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Admin user')
}),
'labels': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Label')
}),
'port': forms.TextInput(),
'domain': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Domain')
}),
}
labels = {
'nodes': _("Node"),
}
help_texts = {
'hostname': '* required',
'ip': '* required',
'port': '* required',
'admin_user': _(
'root or other NOPASSWD sudo privilege user existed in asset,'
'If asset is windows or other set any one, more see admin user left menu'
),
'platform': _("Windows 2016 RDP protocol is different, If is window 2016, set it"),
'domain': _("If your have some network not connect with each other, you can set domain")
}
class AssetUpdateForm(OrgModelForm):
class Meta:
model = Asset
fields = [
'hostname', 'ip', 'port', 'nodes', 'is_active', 'platform',
'public_ip', 'number', 'comment', 'admin_user', 'labels',
'domain', 'protocol',
]
widgets = {
'nodes': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Node')
}),
'admin_user': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Admin user')
}),
'labels': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Label')
}),
'port': forms.TextInput(),
'domain': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Domain')
}),
}
labels = {
'nodes': _("Node"),
}
help_texts = {
'hostname': '* required',
'ip': '* required',
'port': '* required',
'cluster': '* required',
'admin_user': _(
'root or other NOPASSWD sudo privilege user existed in asset,'
'If asset is windows or other set any one, more see admin user left menu'
),
'platform': _("Windows 2016 RDP protocol is different, If is window 2016, set it"),
'domain': _("If your have some network not connect with each other, you can set domain")
}
class AssetBulkUpdateForm(OrgModelForm):
assets = forms.ModelMultipleChoiceField(
required=True, help_text='* required',
label=_('Select assets'), queryset=Asset.objects.all(),
widget=forms.SelectMultiple(
attrs={
'class': 'select2',
'data-placeholder': _('Select assets')
}
)
)
port = forms.IntegerField(
label=_('Port'), required=False, min_value=1, max_value=65535,
)
admin_user = forms.ModelChoiceField(
required=False, queryset=AdminUser.objects,
label=_("Admin user"),
widget=forms.Select(
attrs={
'class': 'select2',
'data-placeholder': _('Admin user')
}
)
)
class Meta:
model = Asset
fields = [
'assets', 'port', 'admin_user', 'labels', 'nodes', 'platform'
]
widgets = {
'labels': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Label')}
),
'nodes': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Node')}
),
}
def save(self, commit=True):
changed_fields = []
for field in self._meta.fields:
if self.data.get(field) not in [None, '']:
changed_fields.append(field)
cleaned_data = {k: v for k, v in self.cleaned_data.items()
if k in changed_fields}
assets = cleaned_data.pop('assets')
labels = cleaned_data.pop('labels', [])
nodes = cleaned_data.pop('nodes')
assets = Asset.objects.filter(id__in=[asset.id for asset in assets])
assets.update(**cleaned_data)
if labels:
for label in labels:
label.assets.add(*tuple(assets))
if nodes:
for node in nodes:
node.assets.add(*tuple(assets))
return assets

View File

@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
#
from django import forms
from orgs.mixins import OrgModelForm
from ..models import CommandFilter, CommandFilterRule
__all__ = ['CommandFilterForm', 'CommandFilterRuleForm']
class CommandFilterForm(OrgModelForm):
class Meta:
model = CommandFilter
fields = ['name', 'comment']
class CommandFilterRuleForm(OrgModelForm):
class Meta:
model = CommandFilterRule
fields = [
'filter', 'type', 'content', 'priority', 'action', 'comment'
]
widgets = {
'content': forms.Textarea(attrs={
'placeholder': 'eg:\r\nreboot\r\nrm -rf'
}),
}

View File

@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
#
from django import forms
from django.utils.translation import gettext_lazy as _
from orgs.mixins import OrgModelForm
from ..models import Domain, Asset, Gateway
from .user import PasswordAndKeyAuthForm
__all__ = ['DomainForm', 'GatewayForm']
class DomainForm(forms.ModelForm):
assets = forms.ModelMultipleChoiceField(
queryset=Asset.objects.all(), label=_('Asset'), required=False,
widget=forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Select assets')}
)
)
class Meta:
model = Domain
fields = ['name', 'comment', 'assets']
def __init__(self, *args, **kwargs):
if kwargs.get('instance', None):
initial = kwargs.get('initial', {})
initial['assets'] = kwargs['instance'].assets.all()
super().__init__(*args, **kwargs)
def save(self, commit=True):
instance = super().save(commit=commit)
assets = self.cleaned_data['assets']
instance.assets.set(assets)
return instance
class GatewayForm(PasswordAndKeyAuthForm, OrgModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
password_field = self.fields.get('password')
password_field.help_text = _('Password should not contain special characters')
def save(self, commit=True):
# Because we define custom field, so we need rewrite :method: `save`
instance = super().save()
password = self.cleaned_data.get('password')
private_key, public_key = super().gen_keys()
instance.set_auth(password=password, private_key=private_key)
return instance
class Meta:
model = Gateway
fields = [
'name', 'ip', 'port', 'username', 'protocol', 'domain', 'password',
'private_key_file', 'is_active', 'comment',
]
widgets = {
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
}
help_texts = {
'name': '* required',
'username': '* required',
}

View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
#
from django import forms
from django.utils.translation import gettext_lazy as _
from ..models import Label, Asset
__all__ = ['LabelForm']
class LabelForm(forms.ModelForm):
assets = forms.ModelMultipleChoiceField(
queryset=Asset.objects.all(), label=_('Asset'), required=False,
widget=forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Select assets')}
)
)
class Meta:
model = Label
fields = ['name', 'value', 'assets']
def __init__(self, *args, **kwargs):
if kwargs.get('instance', None):
initial = kwargs.get('initial', {})
initial['assets'] = kwargs['instance'].assets.all()
super().__init__(*args, **kwargs)
def save(self, commit=True):
label = super().save(commit=commit)
assets = self.cleaned_data['assets']
label.assets.set(assets)
return label

157
apps/assets/forms/user.py Normal file
View File

@@ -0,0 +1,157 @@
# -*- coding: utf-8 -*-
#
from django import forms
from django.utils.translation import gettext_lazy as _
from common.utils import validate_ssh_private_key, ssh_pubkey_gen, get_logger
from orgs.mixins import OrgModelForm
from ..models import AdminUser, SystemUser
logger = get_logger(__file__)
__all__ = [
'FileForm', 'SystemUserForm', 'AdminUserForm', 'PasswordAndKeyAuthForm',
]
class FileForm(forms.Form):
file = forms.FileField()
class PasswordAndKeyAuthForm(forms.ModelForm):
# Form field name can not start with `_`, so redefine it,
password = forms.CharField(
widget=forms.PasswordInput, max_length=128,
strip=True, required=False,
help_text=_('Password or private key passphrase'),
label=_("Password"),
)
# Need use upload private key file except paste private key content
private_key_file = forms.FileField(required=False, label=_("Private key"))
def clean_private_key_file(self):
private_key_file = self.cleaned_data['private_key_file']
password = self.cleaned_data['password']
if private_key_file:
key_string = private_key_file.read()
private_key_file.seek(0)
if not validate_ssh_private_key(key_string, password):
raise forms.ValidationError(_('Invalid private key'))
return private_key_file
def validate_password_key(self):
password = self.cleaned_data['password']
private_key_file = self.cleaned_data.get('private_key_file', '')
if not password and not private_key_file:
raise forms.ValidationError(_(
'Password and private key file must be input one'
))
def gen_keys(self):
password = self.cleaned_data.get('password', '') or None
private_key_file = self.cleaned_data['private_key_file']
public_key = private_key = None
if private_key_file:
private_key = private_key_file.read().strip().decode('utf-8')
public_key = ssh_pubkey_gen(private_key=private_key, password=password)
return private_key, public_key
class AdminUserForm(PasswordAndKeyAuthForm):
def save(self, commit=True):
# Because we define custom field, so we need rewrite :method: `save`
admin_user = super().save(commit=commit)
password = self.cleaned_data.get('password', '') or None
private_key, public_key = super().gen_keys()
admin_user.set_auth(password=password, public_key=public_key, private_key=private_key)
return admin_user
def clean(self):
super().clean()
if not self.instance:
super().validate_password_key()
class Meta:
model = AdminUser
fields = ['name', 'username', 'password', 'private_key_file', 'comment']
widgets = {
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
}
help_texts = {
'name': '* required',
'username': '* required',
}
class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
# Admin user assets define, let user select, save it in form not in view
auto_generate_key = forms.BooleanField(initial=True, required=False)
def save(self, commit=True):
# Because we define custom field, so we need rewrite :method: `save`
system_user = super().save()
password = self.cleaned_data.get('password', '') or None
login_mode = self.cleaned_data.get('login_mode', '') or None
protocol = self.cleaned_data.get('protocol') or None
auto_generate_key = self.cleaned_data.get('auto_generate_key', False)
private_key, public_key = super().gen_keys()
if login_mode == SystemUser.MANUAL_LOGIN or \
protocol in [SystemUser.RDP_PROTOCOL, SystemUser.TELNET_PROTOCOL]:
system_user.auto_push = 0
auto_generate_key = False
system_user.save()
if auto_generate_key:
logger.info('Auto generate key and set system user auth')
system_user.auto_gen_auth()
else:
system_user.set_auth(password=password, private_key=private_key,
public_key=public_key)
return system_user
def clean(self):
super().clean()
auto_generate = self.cleaned_data.get('auto_generate_key')
if not self.instance and not auto_generate:
super().validate_password_key()
def is_valid(self):
validated = super().is_valid()
username = self.cleaned_data.get('username')
login_mode = self.cleaned_data.get('login_mode')
if login_mode == SystemUser.AUTO_LOGIN and not username:
self.add_error(
"username", _('* Automatic login mode,'
' must fill in the username.')
)
return False
return validated
class Meta:
model = SystemUser
fields = [
'name', 'username', 'protocol', 'auto_generate_key',
'password', 'private_key_file', 'auto_push', 'sudo',
'comment', 'shell', 'priority', 'login_mode', 'cmd_filters',
]
widgets = {
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
'cmd_filters': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Command filter')
}),
}
help_texts = {
'name': '* required',
'username': '* required',
'auto_push': _('Auto push system user to asset'),
'priority': _('High level will be using login asset as default, '
'if user was granted more than 2 system user'),
'login_mode': _('If you choose manual login mode, you do not '
'need to fill in the username and password.')
}

View File

@@ -6,13 +6,11 @@
Other module of this app shouldn't connect with other app.
:copyright: (c) 2014-2017 by Jumpserver Team.
:copyright: (c) 2014-2018 by Jumpserver Team.
:license: GPL v2, see LICENSE for more details.
"""
from users.utils import AdminUserRequiredMixin
from users.permissions import IsAppUser, IsSuperUser, IsValidUser
from common.permissions import AdminUserRequiredMixin
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
from users.models import User, UserGroup
from perms.utils import get_user_granted_assets
from perms.tasks import push_users

View File

@@ -0,0 +1,168 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-12-21 16:06
from __future__ import unicode_literals
import assets.models.utils
from django.db import migrations, models
import django.db.models.deletion
import uuid
def add_default_group(apps, schema_editor):
group_model = apps.get_model("assets", "AssetGroup")
db_alias = schema_editor.connection.alias
group_model.objects.using(db_alias).create(
name="Default"
)
def add_default_cluster(apps, schema_editor):
cluster_model = apps.get_model("assets", "Cluster")
db_alias = schema_editor.connection.alias
cluster_model.objects.using(db_alias).create(
name="Default"
)
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='AdminUser',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
('username', models.CharField(max_length=16, verbose_name='Username')),
('_password', models.CharField(blank=True, max_length=256, null=True, verbose_name='Password')),
('_private_key', models.TextField(blank=True, max_length=4096, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key')),
('_public_key', models.TextField(blank=True, max_length=4096, verbose_name='SSH public key')),
('comment', models.TextField(blank=True, verbose_name='Comment')),
('date_created', models.DateTimeField(auto_now_add=True)),
('date_updated', models.DateTimeField(auto_now=True)),
('created_by', models.CharField(max_length=32, null=True, verbose_name='Created by')),
('become', models.BooleanField(default=True)),
('become_method', models.CharField(choices=[('sudo', 'sudo'), ('su', 'su')], default='sudo', max_length=4)),
('become_user', models.CharField(default='root', max_length=64)),
('_become_pass', models.CharField(default='', max_length=128)),
],
options={
'ordering': ['name'],
},
),
migrations.CreateModel(
name='Asset',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('ip', models.GenericIPAddressField(db_index=True, verbose_name='IP')),
('hostname', models.CharField(max_length=128, unique=True, verbose_name='Hostname')),
('port', models.IntegerField(default=22, verbose_name='Port')),
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
('type', models.CharField(blank=True, choices=[('Server', 'Server'), ('VM', 'VM'), ('Switch', 'Switch'), ('Router', 'Router'), ('Firewall', 'Firewall'), ('Storage', 'Storage')], default='Server', max_length=16, null=True, verbose_name='Asset type')),
('env', models.CharField(blank=True, choices=[('Prod', 'Production'), ('Dev', 'Development'), ('Test', 'Testing')], default='Prod', max_length=8, null=True, verbose_name='Asset environment')),
('status', models.CharField(blank=True, choices=[('In use', 'In use'), ('Out of use', 'Out of use')], default='In use', max_length=12, null=True, verbose_name='Asset status')),
('public_ip', models.GenericIPAddressField(blank=True, null=True, verbose_name='Public IP')),
('remote_card_ip', models.CharField(blank=True, max_length=16, null=True, verbose_name='Remote control card IP')),
('cabinet_no', models.CharField(blank=True, max_length=32, null=True, verbose_name='Cabinet number')),
('cabinet_pos', models.IntegerField(blank=True, null=True, verbose_name='Cabinet position')),
('number', models.CharField(blank=True, max_length=32, null=True, verbose_name='Asset number')),
('vendor', models.CharField(blank=True, max_length=64, null=True, verbose_name='Vendor')),
('model', models.CharField(blank=True, max_length=54, null=True, verbose_name='Model')),
('sn', models.CharField(blank=True, max_length=128, null=True, verbose_name='Serial number')),
('cpu_model', models.CharField(blank=True, max_length=64, null=True, verbose_name='CPU model')),
('cpu_count', models.IntegerField(null=True, verbose_name='CPU count')),
('cpu_cores', models.IntegerField(null=True, verbose_name='CPU cores')),
('memory', models.CharField(blank=True, max_length=64, null=True, verbose_name='Memory')),
('disk_total', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Disk total')),
('disk_info', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Disk info')),
('platform', models.CharField(blank=True, max_length=128, null=True, verbose_name='Platform')),
('os', models.CharField(blank=True, max_length=128, null=True, verbose_name='OS')),
('os_version', models.CharField(blank=True, max_length=16, null=True, verbose_name='OS version')),
('os_arch', models.CharField(blank=True, max_length=16, null=True, verbose_name='OS arch')),
('hostname_raw', models.CharField(blank=True, max_length=128, null=True, verbose_name='Hostname raw')),
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')),
('admin_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.AdminUser', verbose_name='Admin user')),
],
),
migrations.CreateModel(
name='AssetGroup',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=64, unique=True, verbose_name='Name')),
('created_by', models.CharField(blank=True, max_length=32, verbose_name='Created by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('comment', models.TextField(blank=True, verbose_name='Comment')),
],
options={
'ordering': ['name'],
},
),
migrations.CreateModel(
name='Cluster',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=32, verbose_name='Name')),
('bandwidth', models.CharField(blank=True, max_length=32, verbose_name='Bandwidth')),
('contact', models.CharField(blank=True, max_length=128, verbose_name='Contact')),
('phone', models.CharField(blank=True, max_length=32, verbose_name='Phone')),
('address', models.CharField(blank=True, max_length=128, verbose_name='Address')),
('intranet', models.TextField(blank=True, verbose_name='Intranet')),
('extranet', models.TextField(blank=True, verbose_name='Extranet')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('operator', models.CharField(blank=True, max_length=32, verbose_name='Operator')),
('created_by', models.CharField(blank=True, max_length=32, verbose_name='Created by')),
('comment', models.TextField(blank=True, verbose_name='Comment')),
('admin_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.AdminUser', verbose_name='Admin user')),
],
options={
'ordering': ['name'],
},
),
migrations.CreateModel(
name='SystemUser',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
('username', models.CharField(max_length=16, verbose_name='Username')),
('_password', models.CharField(blank=True, max_length=256, null=True, verbose_name='Password')),
('_private_key', models.TextField(blank=True, max_length=4096, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key')),
('_public_key', models.TextField(blank=True, max_length=4096, verbose_name='SSH public key')),
('comment', models.TextField(blank=True, verbose_name='Comment')),
('date_created', models.DateTimeField(auto_now_add=True)),
('date_updated', models.DateTimeField(auto_now=True)),
('created_by', models.CharField(max_length=32, null=True, verbose_name='Created by')),
('priority', models.IntegerField(default=10, verbose_name='Priority')),
('protocol', models.CharField(choices=[('ssh', 'ssh')], default='ssh', max_length=16, verbose_name='Protocol')),
('auto_push', models.BooleanField(default=True, verbose_name='Auto push')),
('sudo', models.TextField(default='/sbin/ifconfig', verbose_name='Sudo')),
('shell', models.CharField(default='/bin/bash', max_length=64, verbose_name='Shell')),
('cluster', models.ManyToManyField(blank=True, to='assets.Cluster', verbose_name='Cluster')),
],
options={
'ordering': ['name'],
},
),
migrations.AddField(
model_name='asset',
name='cluster',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assets', to='assets.Cluster', verbose_name='Cluster'),
),
migrations.AddField(
model_name='asset',
name='groups',
field=models.ManyToManyField(blank=True, related_name='assets', to='assets.AssetGroup', verbose_name='Asset groups'),
),
migrations.AlterUniqueTogether(
name='asset',
unique_together=set([('ip', 'port')]),
),
migrations.RunPython(add_default_cluster),
migrations.RunPython(add_default_group),
]

View File

@@ -1,9 +1,12 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from .idc import *
#
from .user import *
from .label import Label
from .cluster import *
from .group import *
from .domain import *
from .node import *
from .asset import *
from .cmd_filter import *
from .utils import *

View File

@@ -2,96 +2,138 @@
# -*- coding: utf-8 -*-
#
from __future__ import unicode_literals
import uuid
import logging
import random
from functools import reduce
from collections import defaultdict
from django.db import models
import logging
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from django.core.cache import cache
from . import IDC, AssetGroup, AdminUser, SystemUser
from ..const import ASSET_ADMIN_CONN_CACHE_KEY
from .user import AdminUser, SystemUser
from orgs.mixins import OrgModelMixin, OrgManager
__all__ = ['Asset']
logger = logging.getLogger(__name__)
def get_default_idc():
return IDC.initial()
def default_cluster():
from .cluster import Cluster
name = "Default"
defaults = {"name": name}
cluster, created = Cluster.objects.get_or_create(
defaults=defaults, name=name
)
return cluster.id
class Asset(models.Model):
STATUS_CHOICES = (
('In use', _('In use')),
('Out of use', _('Out of use')),
)
TYPE_CHOICES = (
('Server', _('Server')),
('VM', _('VM')),
('Switch', _('Switch')),
('Router', _('Router')),
('Firewall', _('Firewall')),
('Storage', _("Storage")),
)
ENV_CHOICES = (
('Prod', 'Production'),
('Dev', 'Development'),
('Test', 'Testing'),
)
def default_node():
try:
from .node import Node
return Node.root()
except:
return None
class AssetQuerySet(models.QuerySet):
def active(self):
return self.filter(is_active=True)
def valid(self):
return self.active()
class Asset(OrgModelMixin):
# Important
PLATFORM_CHOICES = (
('Linux', 'Linux'),
('Unix', 'Unix'),
('MacOS', 'MacOS'),
('BSD', 'BSD'),
('Windows', 'Windows'),
('Windows2016', 'Windows(2016)'),
('Other', 'Other'),
)
SSH_PROTOCOL = 'ssh'
RDP_PROTOCOL = 'rdp'
TELNET_PROTOCOL = 'telnet'
PROTOCOL_CHOICES = (
(SSH_PROTOCOL, 'ssh'),
(RDP_PROTOCOL, 'rdp'),
(TELNET_PROTOCOL, 'telnet (beta)'),
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname'))
hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
protocol = models.CharField(max_length=128, default=SSH_PROTOCOL, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol'))
port = models.IntegerField(default=22, verbose_name=_('Port'))
groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets',
verbose_name=_('Asset groups'))
admin_user = models.ForeignKey(AdminUser, null=True, blank=True, related_name='assets',
on_delete=models.SET_NULL, verbose_name=_("Admin user"))
system_users = models.ManyToManyField(SystemUser, blank=True,
related_name='assets',
verbose_name=_("System User"))
idc = models.ForeignKey(IDC, blank=True, null=True, related_name='assets',
on_delete=models.SET_NULL, verbose_name=_('IDC'),)
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
domain = models.ForeignKey("assets.Domain", null=True, blank=True,
related_name='assets', verbose_name=_("Domain"),
on_delete=models.SET_NULL)
nodes = models.ManyToManyField('assets.Node', default=default_node,
related_name='assets',
verbose_name=_("Nodes"))
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True,
default='Server', verbose_name=_('Asset type'),)
env = models.CharField(choices=ENV_CHOICES, max_length=8, blank=True, null=True,
default='Prod', verbose_name=_('Asset environment'),)
status = models.CharField(choices=STATUS_CHOICES, max_length=12, null=True, blank=True,
default='In use', verbose_name=_('Asset status'))
# Auth
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT,
null=True, verbose_name=_("Admin user"))
# Some information
public_ip = models.GenericIPAddressField(max_length=32, blank=True,
null=True, verbose_name=_('Public IP'))
remote_card_ip = models.CharField(max_length=16, null=True, blank=True,
verbose_name=_('Remote control card IP'))
cabinet_no = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Cabinet number'))
cabinet_pos = models.IntegerField(null=True, blank=True, verbose_name=_('Cabinet position'))
public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP'))
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
# Collect
vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor'))
model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model'))
sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number'))
vendor = models.CharField(max_length=64, null=True, blank=True,
verbose_name=_('Vendor'))
model = models.CharField(max_length=54, null=True, blank=True,
verbose_name=_('Model'))
sn = models.CharField(max_length=128, null=True, blank=True,
verbose_name=_('Serial number'))
cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model'))
cpu_model = models.CharField(max_length=64, null=True, blank=True,
verbose_name=_('CPU model'))
cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count'))
cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores'))
memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory'))
disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total'))
disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info'))
cpu_vcpus = models.IntegerField(null=True, verbose_name=_('CPU vcpus'))
memory = models.CharField(max_length=64, null=True, blank=True,
verbose_name=_('Memory'))
disk_total = models.CharField(max_length=1024, null=True, blank=True,
verbose_name=_('Disk total'))
disk_info = models.CharField(max_length=1024, null=True, blank=True,
verbose_name=_('Disk info'))
platform = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Platform'))
os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS'))
os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version'))
os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch'))
hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw'))
os = models.CharField(max_length=128, null=True, blank=True,
verbose_name=_('OS'))
os_version = models.CharField(max_length=16, null=True, blank=True,
verbose_name=_('OS version'))
os_arch = models.CharField(max_length=16, blank=True, null=True,
verbose_name=_('OS arch'))
hostname_raw = models.CharField(max_length=128, blank=True, null=True,
verbose_name=_('Hostname raw'))
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
labels = models.ManyToManyField('assets.Label', blank=True,
related_name='assets',
verbose_name=_("Labels"))
created_by = models.CharField(max_length=32, null=True, blank=True,
verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now_add=True, null=True,
blank=True,
verbose_name=_('Date created'))
comment = models.TextField(max_length=128, default='', blank=True,
verbose_name=_('Comment'))
def __unicode__(self):
return '%s <%s: %s>' % (self.hostname, self.ip, self.port)
__str__ = __unicode__
objects = OrgManager.from_queryset(AssetQuerySet)()
def __str__(self):
return '{0.hostname}({0.ip})'.format(self)
@property
def is_valid(self):
@@ -102,34 +144,102 @@ class Asset(models.Model):
return True, ''
return False, warning
def is_unixlike(self):
if self.platform not in ("Windows", "Windows2016"):
return True
else:
return False
def get_nodes(self):
from .node import Node
nodes = self.nodes.all() or [Node.root()]
return nodes
def get_all_nodes(self, flat=False):
nodes = []
for node in self.get_nodes():
_nodes = node.get_ancestor(with_self=True)
_nodes.append(_nodes)
if flat:
nodes = list(reduce(lambda x, y: set(x) | set(y), nodes))
return nodes
@classmethod
def get_queryset_by_fullname_list(cls, fullname_list):
org_fullname_map = defaultdict(list)
for fullname in fullname_list:
hostname, org = cls.split_fullname(fullname)
org_fullname_map[org].append(hostname)
filter_arg = Q()
for org, hosts in org_fullname_map.items():
if org.is_real():
filter_arg |= Q(hostname__in=hosts, org_id=org.id)
else:
filter_arg |= Q(Q(org_id__isnull=True) | Q(org_id=''), hostname__in=hosts)
return Asset.objects.filter(filter_arg)
@property
def hardware_info(self):
if self.cpu_count:
return '{} Core {} {}'.format(
self.cpu_vcpus or self.cpu_count * self.cpu_cores,
self.memory, self.disk_total
)
else:
return ''
@property
def is_connective(self):
if not self.is_unixlike():
return True
val = cache.get(ASSET_ADMIN_CONN_CACHE_KEY.format(self.hostname))
if val == 1:
return True
else:
return False
def to_json(self):
return {
info = {
'id': self.id,
'hostname': self.hostname,
'ip': self.ip,
'port': self.port,
}
if self.domain and self.domain.gateway_set.all():
info["gateways"] = [d.id for d in self.domain.gateway_set.all()]
return info
def get_auth_info(self):
if self.admin_user:
return {
'username': self.admin_user.username,
'password': self.admin_user.password,
'private_key': self.admin_user.private_key_file,
'become': self.admin_user.become_info,
}
def _to_secret_json(self):
"""Ansible use it create inventory"""
return {
'id': self.id,
'hostname': self.hostname,
'ip': self.ip,
'port': self.port,
'groups': [group.name for group in self.groups.all()],
'username': self.admin_user.username if self.admin_user else '',
'password': self.admin_user.password if self.admin_user else '',
'private_key': self.admin_user.private_key_file if self.admin_user else None,
'become': {
'method': self.admin_user.become_method,
'user': self.admin_user.become_user,
'pass': self.admin_user.become_pass,
} if self.admin_user and self.admin_user.become else {},
}
"""
Ansible use it create inventory, First using asset user,
otherwise using cluster admin user
Todo: May be move to ops implements it
"""
data = self.to_json()
if self.admin_user:
admin_user = self.admin_user
data.update({
'username': admin_user.username,
'password': admin_user.password,
'private_key': admin_user.private_key_file,
'become': admin_user.become_info,
'groups': [node.value for node in self.nodes.all()],
})
return data
class Meta:
unique_together = ('ip', 'port')
unique_together = [('org_id', 'hostname')]
verbose_name = _("Asset")
@classmethod
def generate_fake(cls, count=100):
@@ -139,18 +249,16 @@ class Asset(models.Model):
seed()
for i in range(count):
asset = cls(ip='%s.%s.%s.%s' % (i, i, i, i),
ip = [str(i) for i in random.sample(range(255), 4)]
asset = cls(ip='.'.join(ip),
hostname=forgery_py.internet.user_name(True),
admin_user=choice(AdminUser.objects.all()),
idc=choice(IDC.objects.all()),
port=22,
created_by='Fake')
try:
asset.save()
asset.system_users = [choice(SystemUser.objects.all()) for i in range(3)]
asset.groups = [choice(AssetGroup.objects.all()) for i in range(3)]
logger.debug('Generate fake asset : %s' % asset.ip)
except IntegrityError:
print('Error continue')
continue

134
apps/assets/models/base.py Normal file
View File

@@ -0,0 +1,134 @@
# -*- coding: utf-8 -*-
#
import os
import uuid
from hashlib import md5
import sshpubkeys
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from common.utils import get_signer, ssh_key_string_to_obj, ssh_key_gen
from common.validators import alphanumeric
from orgs.mixins import OrgModelMixin
from .utils import private_key_validator
signer = get_signer()
class AssetUser(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name'))
username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric])
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
@property
def password(self):
if self._password:
return signer.unsign(self._password)
else:
return None
@password.setter
def password(self, password_raw):
raise AttributeError("Using set_auth do that")
# self._password = signer.sign(password_raw)
@property
def private_key(self):
if self._private_key:
return signer.unsign(self._private_key)
@private_key.setter
def private_key(self, private_key_raw):
raise AttributeError("Using set_auth do that")
# self._private_key = signer.sign(private_key_raw)
@property
def private_key_obj(self):
if self._private_key:
key_str = signer.unsign(self._private_key)
return ssh_key_string_to_obj(key_str, password=self.password)
else:
return None
@property
def private_key_file(self):
if not self.private_key_obj:
return None
project_dir = settings.PROJECT_DIR
tmp_dir = os.path.join(project_dir, 'tmp')
key_str = signer.unsign(self._private_key)
key_name = '.' + md5(key_str.encode('utf-8')).hexdigest()
key_path = os.path.join(tmp_dir, key_name)
if not os.path.exists(key_path):
self.private_key_obj.write_private_key_file(key_path)
os.chmod(key_path, 0o400)
return key_path
@property
def public_key(self):
key = signer.unsign(self._public_key)
if key:
return key
else:
return None
@property
def public_key_obj(self):
if self.public_key:
try:
return sshpubkeys.SSHKey(self.public_key)
except TabError:
pass
return None
def set_auth(self, password=None, private_key=None, public_key=None):
update_fields = []
if password:
self._password = signer.sign(password)
update_fields.append('_password')
if private_key:
self._private_key = signer.sign(private_key)
update_fields.append('_private_key')
if public_key:
self._public_key = signer.sign(public_key)
update_fields.append('_public_key')
if update_fields:
self.save(update_fields=update_fields)
def clear_auth(self):
self._password = ''
self._private_key = ''
self._public_key = ''
self.save()
def auto_gen_auth(self):
password = str(uuid.uuid4())
private_key, public_key = ssh_key_gen(
username=self.username
)
self.set_auth(password=password,
private_key=private_key,
public_key=public_key)
def _to_secret_json(self):
"""Push system user use it"""
return {
'name': self.name,
'username': self.username,
'password': self.password,
'public_key': self.public_key,
'private_key': self.private_key_file,
}
class Meta:
abstract = True

View File

@@ -0,0 +1,64 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
import logging
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
__all__ = ['Cluster']
logger = logging.getLogger(__name__)
class Cluster(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=32, verbose_name=_('Name'))
admin_user = models.ForeignKey('assets.AdminUser', null=True, blank=True, on_delete=models.SET_NULL, verbose_name=_("Admin user"))
bandwidth = models.CharField(max_length=32, blank=True, verbose_name=_('Bandwidth'))
contact = models.CharField(max_length=128, blank=True, verbose_name=_('Contact'))
phone = models.CharField(max_length=32, blank=True, verbose_name=_('Phone'))
address = models.CharField(max_length=128, blank=True, verbose_name=_("Address"))
intranet = models.TextField(blank=True, verbose_name=_('Intranet'))
extranet = models.TextField(blank=True, verbose_name=_('Extranet'))
date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created'))
operator = models.CharField(max_length=32, blank=True, verbose_name=_('Operator'))
created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
def __str__(self):
return self.name
@classmethod
def initial(cls):
return cls.objects.get_or_create(name=_('Default'), created_by=_('System'), comment=_('Default Cluster'))[0]
class Meta:
ordering = ['name']
verbose_name = _("Cluster")
@classmethod
def generate_fake(cls, count=5):
from random import seed, choice
import forgery_py
from django.db import IntegrityError
seed()
for i in range(count):
cluster = cls(name=forgery_py.name.full_name(),
bandwidth='200M',
contact=forgery_py.name.full_name(),
phone=forgery_py.address.phone(),
address=forgery_py.address.city() + forgery_py.address.street_address(),
# operator=choice(['北京联通', '北京电信', 'BGP全网通']),
operator=choice([_('Beijing unicom'), _('Beijing telecom'), _('BGP full netcom')]),
comment=forgery_py.lorem_ipsum.sentence(),
created_by='Fake')
try:
cluster.save()
logger.debug('Generate fake asset group: %s' % cluster.name)
except IntegrityError:
print('Error continue')
continue

View File

@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
#
import uuid
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelMixin
__all__ = [
'CommandFilter', 'CommandFilterRule'
]
class CommandFilter(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=64, verbose_name=_("Name"))
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
comment = models.TextField(blank=True, default='', verbose_name=_("Comment"))
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
created_by = models.CharField(max_length=128, blank=True, default='', verbose_name=_('Created by'))
def __str__(self):
return self.name
class CommandFilterRule(OrgModelMixin):
TYPE_REGEX = 'regex'
TYPE_COMMAND = 'command'
TYPE_CHOICES = (
(TYPE_REGEX, _('Regex')),
(TYPE_COMMAND, _('Command')),
)
ACTION_DENY, ACTION_ALLOW = range(2)
ACTION_CHOICES = (
(ACTION_DENY, _('Deny')),
(ACTION_ALLOW, _('Allow')),
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
filter = models.ForeignKey('CommandFilter', on_delete=models.CASCADE, verbose_name=_("Filter"), related_name='rules')
type = models.CharField(max_length=16, default=TYPE_COMMAND, choices=TYPE_CHOICES, verbose_name=_("Type"))
priority = models.IntegerField(default=50, verbose_name=_("Priority"), help_text=_("1-100, the lower will be match first"),
validators=[MinValueValidator(1), MaxValueValidator(100)])
content = models.TextField(max_length=1024, verbose_name=_("Content"), help_text=_("One line one command"))
action = models.IntegerField(default=ACTION_DENY, choices=ACTION_CHOICES, verbose_name=_("Action"))
comment = models.CharField(max_length=64, blank=True, default='', verbose_name=_("Comment"))
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
created_by = models.CharField(max_length=128, blank=True, default='', verbose_name=_('Created by'))
class Meta:
ordering = ('priority', 'action')
def __str__(self):
return '{} % {}'.format(self.type, self.content)

View File

@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
#
import uuid
import random
from django.db import models
from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelMixin
from .base import AssetUser
__all__ = ['Domain', 'Gateway']
class Domain(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now_add=True, null=True,
verbose_name=_('Date created'))
class Meta:
verbose_name = _("Domain")
def __str__(self):
return self.name
def has_gateway(self):
return self.gateway_set.filter(is_active=True).exists()
@property
def gateways(self):
return self.gateway_set.filter(is_active=True)
def random_gateway(self):
return random.choice(self.gateways)
class Gateway(AssetUser):
SSH_PROTOCOL = 'ssh'
RDP_PROTOCOL = 'rdp'
PROTOCOL_CHOICES = (
(SSH_PROTOCOL, 'ssh'),
(RDP_PROTOCOL, 'rdp'),
)
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
port = models.IntegerField(default=22, verbose_name=_('Port'))
protocol = models.CharField(choices=PROTOCOL_CHOICES, max_length=16, default=SSH_PROTOCOL, verbose_name=_("Protocol"))
domain = models.ForeignKey(Domain, on_delete=models.CASCADE, verbose_name=_("Domain"))
comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment"))
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
def __str__(self):
return self.name
class Meta:
unique_together = [('name', 'org_id')]
verbose_name = _("Gateway")

View File

@@ -4,29 +4,30 @@
from __future__ import unicode_literals
import uuid
from django.db import models
import logging
from django.utils.translation import ugettext_lazy as _
from . import SystemUser
__all__ = ['AssetGroup']
logger = logging.getLogger(__name__)
class AssetGroup(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=64, unique=True, verbose_name=_('Name'))
system_users = models.ManyToManyField(SystemUser, related_name='asset_groups', blank=True)
created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
def __unicode__(self):
def __str__(self):
return self.name
__str__ = __unicode__
class Meta:
ordering = ['name']
verbose_name = _("Asset group")
@classmethod
def initial(cls):

View File

@@ -1,69 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from __future__ import unicode_literals
import logging
from django.db import models
from django.utils.translation import ugettext_lazy as _
__all__ = ['IDC']
logger = logging.getLogger(__name__)
class IDC(models.Model):
name = models.CharField(max_length=32, verbose_name=_('Name'))
bandwidth = models.CharField(
max_length=32, blank=True, verbose_name=_('Bandwidth'))
contact = models.CharField(
max_length=128, blank=True, verbose_name=_('Contact'))
phone = models.CharField(max_length=32, blank=True,
verbose_name=_('Phone'))
address = models.CharField(
max_length=128, blank=True, verbose_name=_("Address"))
intranet = models.TextField(blank=True, verbose_name=_('Intranet'))
extranet = models.TextField(blank=True, verbose_name=_('Extranet'))
date_created = models.DateTimeField(
auto_now_add=True, null=True, verbose_name=_('Date created'))
operator = models.CharField(
max_length=32, blank=True, verbose_name=_('Operator'))
created_by = models.CharField(
max_length=32, blank=True, verbose_name=_('Created by'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
def __unicode__(self):
return self.name
__str__ = __unicode__
@classmethod
def initial(cls):
return cls.objects.get_or_create(name=_('Default'), created_by=_('System'), comment=_('Default IDC'))[0]
class Meta:
ordering = ['name']
@classmethod
def generate_fake(cls, count=5):
from random import seed, choice
import forgery_py
from django.db import IntegrityError
seed()
for i in range(count):
idc = cls(name=forgery_py.name.full_name(),
bandwidth='200M',
contact=forgery_py.name.full_name(),
phone=forgery_py.address.phone(),
address=forgery_py.address.city() + forgery_py.address.street_address(),
operator=choice(['北京联通', '北京电信', 'BGP全网通']),
comment=forgery_py.lorem_ipsum.sentence(),
created_by='Fake')
try:
idc.save()
logger.debug('Generate fake asset group: %s' % idc.name)
except IntegrityError:
print('Error continue')
continue

View File

@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
#
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelMixin
class Label(OrgModelMixin):
SYSTEM_CATEGORY = "S"
USER_CATEGORY = "U"
CATEGORY_CHOICES = (
("S", _("System")),
("U", _("User"))
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_("Name"))
value = models.CharField(max_length=128, verbose_name=_("Value"))
category = models.CharField(max_length=128, choices=CATEGORY_CHOICES, default=USER_CATEGORY, verbose_name=_("Category"))
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
date_created = models.DateTimeField(
auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')
)
@classmethod
def get_queryset_group_by_name(cls):
names = cls.objects.values_list('name', flat=True)
for name in names:
yield name, cls.objects.filter(name=name)
def __str__(self):
return "{}:{}".format(self.name, self.value)
class Meta:
db_table = "assets_label"
unique_together = [('name', 'value', 'org_id')]

239
apps/assets/models/node.py Normal file
View File

@@ -0,0 +1,239 @@
# -*- coding: utf-8 -*-
#
import uuid
from django.db import models, transaction
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from django.core.cache import cache
from orgs.mixins import OrgModelMixin
from orgs.utils import set_current_org, get_current_org
from orgs.models import Organization
__all__ = ['Node']
class Node(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
value = models.CharField(max_length=128, verbose_name=_("Value"))
child_mark = models.IntegerField(default=0)
date_create = models.DateTimeField(auto_now_add=True)
is_node = True
_full_value_cache_key_prefix = '_NODE_VALUE_{}'
class Meta:
verbose_name = _("Node")
def __str__(self):
return self.full_value
def __eq__(self, other):
return self.key == other.key
def __gt__(self, other):
if self.is_root():
return True
self_key = [int(k) for k in self.key.split(':')]
other_key = [int(k) for k in other.key.split(':')]
return self_key.__lt__(other_key)
def __lt__(self, other):
return not self.__gt__(other)
@property
def name(self):
return self.value
@property
def full_value(self):
key = self._full_value_cache_key_prefix.format(self.key)
cached = cache.get(key)
if cached:
return cached
value = self.get_full_value()
self.cache_full_value(value)
return value
def get_full_value(self):
# ancestor = [a.value for a in self.get_ancestor(with_self=True)]
if self.is_root():
return self.value
parent_full_value = self.parent.full_value
value = parent_full_value + ' / ' + self.value
return value
def cache_full_value(self, value):
key = self._full_value_cache_key_prefix.format(self.key)
cache.set(key, value, 3600)
def expire_full_value(self):
key = self._full_value_cache_key_prefix.format(self.key)
cache.delete_pattern(key+'*')
@property
def level(self):
return len(self.key.split(':'))
def get_next_child_key(self):
mark = self.child_mark
self.child_mark += 1
self.save()
return "{}:{}".format(self.key, mark)
def create_child(self, value):
with transaction.atomic():
child_key = self.get_next_child_key()
child = self.__class__.objects.create(key=child_key, value=value)
return child
def get_children(self, with_self=False):
pattern = r'^{0}$|^{}:[0-9]+$' if with_self else r'^{}:[0-9]+$'
return self.__class__.objects.filter(
key__regex=pattern.format(self.key)
)
def get_all_children(self, with_self=False):
pattern = r'^{0}$|^{0}:' if with_self else r'^{0}'
return self.__class__.objects.filter(
key__regex=pattern.format(self.key)
)
def get_sibling(self, with_self=False):
key = ':'.join(self.key.split(':')[:-1])
pattern = r'^{}:[0-9]+$'.format(key)
sibling = self.__class__.objects.filter(
key__regex=pattern.format(self.key)
)
if not with_self:
sibling = sibling.exclude(key=self.key)
return sibling
def get_family(self):
ancestor = self.get_ancestor()
children = self.get_all_children()
return [*tuple(ancestor), self, *tuple(children)]
def get_assets(self):
from .asset import Asset
if self.is_default_node():
assets = Asset.objects.filter(nodes__isnull=True)
else:
assets = Asset.objects.filter(nodes__id=self.id)
return assets
def get_valid_assets(self):
return self.get_assets().valid()
def get_all_assets(self):
from .asset import Asset
pattern = r'^{0}$|^{0}:'.format(self.key)
args = []
kwargs = {}
if self.is_default_node():
args.append(Q(nodes__key__regex=pattern) | Q(nodes=None))
else:
kwargs['nodes__key__regex'] = pattern
assets = Asset.objects.filter(*args, **kwargs)
return assets
def get_all_valid_assets(self):
return self.get_all_assets().valid()
def is_default_node(self):
return self.is_root() and self.key == '0'
def is_root(self):
if self.key.isdigit():
return True
else:
return False
@property
def parent_key(self):
parent_key = ":".join(self.key.split(":")[:-1])
return parent_key
@property
def parent(self):
if self.is_root():
return self
try:
parent = self.__class__.objects.get(key=self.parent_key)
return parent
except Node.DoesNotExist:
return self.__class__.root()
@parent.setter
def parent(self, parent):
if not self.is_node:
self.key = parent.key + ':fake'
return
children = self.get_all_children()
old_key = self.key
with transaction.atomic():
self.key = parent.get_next_child_key()
for child in children:
child.key = child.key.replace(old_key, self.key, 1)
child.save()
self.save()
def get_ancestor(self, with_self=False):
if self.is_root():
root = self.__class__.root()
return [root]
_key = self.key.split(':')
if not with_self:
_key.pop()
ancestor_keys = []
for i in range(len(_key)):
ancestor_keys.append(':'.join(_key))
_key.pop()
ancestor = self.__class__.objects.filter(
key__in=ancestor_keys
).order_by('key')
return ancestor
@classmethod
def create_root_node(cls):
# 如果使用current_org 在set_current_org时会死循环
_current_org = get_current_org()
with transaction.atomic():
if _current_org.is_default():
key = '0'
else:
set_current_org(Organization.root())
org_nodes_roots = cls.objects.filter(key__regex=r'^[0-9]+$')
org_nodes_roots_keys = org_nodes_roots.values_list('key', flat=True) or [0]
key = str(max([int(k) for k in org_nodes_roots_keys]) + 1)
set_current_org(_current_org)
root = cls.objects.create(key=key, value=_current_org.name)
return root
@classmethod
def root(cls):
root = cls.objects.filter(key__regex=r'^[0-9]+$')
if root:
return root[0]
else:
return cls.create_root_node()
@classmethod
def default_node(cls):
defaults = {'value': 'Default'}
return cls.objects.get_or_create(defaults=defaults, key='0')
@classmethod
def get_tree_name_ref(cls):
pass
@classmethod
def generate_fake(cls, count=100):
import random
for i in range(count):
node = random.choice(cls.objects.all())
node.create_child('Node {}'.format(i))

View File

@@ -2,105 +2,74 @@
# -*- coding: utf-8 -*-
#
from __future__ import unicode_literals
import os
import logging
from hashlib import md5
from django.core.exceptions import ValidationError
from django.core.cache import cache
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from common.utils import signer, validate_ssh_private_key, ssh_key_string_to_obj
from common.utils import get_signer
from ..const import SYSTEM_USER_CONN_CACHE_KEY
from .base import AssetUser
__all__ = ['AdminUser', 'SystemUser', 'private_key_validator']
__all__ = ['AdminUser', 'SystemUser',]
logger = logging.getLogger(__name__)
signer = get_signer()
def private_key_validator(value):
if not validate_ssh_private_key(value):
raise ValidationError(
_('%(value)s is not an even number'),
params={'value': value},
)
class AdminUser(models.Model):
class AdminUser(AssetUser):
"""
A privileged user that ansible can use it to push system user and so on
"""
BECOME_METHOD_CHOICES = (
('sudo', 'sudo'),
('su', 'su'),
)
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
username = models.CharField(max_length=16, verbose_name=_('Username'))
_password = models.CharField(
max_length=256, blank=True, null=True, verbose_name=_('Password'))
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'),
validators=[private_key_validator,])
become = models.BooleanField(default=True)
become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4)
become_user = models.CharField(default='root', max_length=64)
become_pass = models.CharField(default='', max_length=128)
_public_key = models.TextField(
max_length=4096, blank=True, verbose_name=_('SSH public key'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now_add=True, null=True)
created_by = models.CharField(
max_length=32, null=True, verbose_name=_('Created by'))
_become_pass = models.CharField(default='', max_length=128)
def __unicode__(self):
def __str__(self):
return self.name
__str__ = __unicode__
@property
def password(self):
if self._password:
return signer.unsign(self._password)
def become_pass(self):
password = signer.unsign(self._become_pass)
if password:
return password
else:
return ''
return ""
@password.setter
def password(self, password_raw):
self._password = signer.sign(password_raw)
@become_pass.setter
def become_pass(self, password):
self._become_pass = signer.sign(password)
@property
def private_key(self):
if self._private_key:
key_str = signer.unsign(self._private_key)
return ssh_key_string_to_obj(key_str)
def become_info(self):
if self.become:
info = {
"method": self.become_method,
"user": self.become_user,
"pass": self.become_pass,
}
else:
return None
info = None
return info
@private_key.setter
def private_key(self, private_key_raw):
self._private_key = signer.sign(private_key_raw)
@property
def private_key_file(self):
if not self.private_key:
return None
project_dir = settings.PROJECT_DIR
tmp_dir = os.path.join(project_dir, 'tmp')
key_name = md5(self._private_key.encode()).hexdigest()
key_path = os.path.join(tmp_dir, key_name)
if not os.path.exists(key_path):
self.private_key.write_private_key_file(key_path)
return key_path
@property
def public_key(self):
return signer.unsign(self._public_key)
@public_key.setter
def public_key(self, public_key_raw):
self._public_key = signer.sign(public_key_raw)
def get_related_assets(self):
assets = self.asset_set.all()
return assets
@property
def assets_amount(self):
return self.assets.count()
return self.get_related_assets().count()
class Meta:
ordering = ['name']
unique_together = [('name', 'org_id')]
verbose_name = _("Admin user")
@classmethod
def generate_fake(cls, count=10):
@@ -123,104 +92,37 @@ class AdminUser(models.Model):
continue
class SystemUser(models.Model):
class SystemUser(AssetUser):
SSH_PROTOCOL = 'ssh'
RDP_PROTOCOL = 'rdp'
TELNET_PROTOCOL = 'telnet'
PROTOCOL_CHOICES = (
('ssh', 'ssh'),
(SSH_PROTOCOL, 'ssh'),
(RDP_PROTOCOL, 'rdp'),
(TELNET_PROTOCOL, 'telnet (beta)'),
)
AUTH_METHOD_CHOICES = (
('P', 'Password'),
('K', 'Public key'),
AUTO_LOGIN = 'auto'
MANUAL_LOGIN = 'manual'
LOGIN_MODE_CHOICES = (
(AUTO_LOGIN, _('Automatic login')),
(MANUAL_LOGIN, _('Manually login'))
)
name = models.CharField(max_length=128, unique=True,
verbose_name=_('Name'))
username = models.CharField(max_length=16, verbose_name=_('Username'))
_password = models.CharField(
max_length=256, blank=True, verbose_name=_('Password'))
protocol = models.CharField(
max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
_private_key = models.TextField(
max_length=8192, blank=True, verbose_name=_('SSH private key'))
_public_key = models.TextField(
max_length=8192, blank=True, verbose_name=_('SSH public key'))
auth_method = models.CharField(choices=AUTH_METHOD_CHOICES, default='K',
max_length=1, verbose_name=_('Auth method'))
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets"))
priority = models.IntegerField(default=10, verbose_name=_("Priority"))
protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
sudo = models.TextField(
max_length=4096, default='/sbin/ifconfig', verbose_name=_('Sudo'))
shell = models.CharField(
max_length=64, default='/bin/bash', verbose_name=_('Shell'))
date_created = models.DateTimeField(auto_now_add=True)
created_by = models.CharField(
max_length=32, blank=True, verbose_name=_('Created by'))
comment = models.TextField(
max_length=128, blank=True, verbose_name=_('Comment'))
sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo'))
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=AUTO_LOGIN, max_length=10, verbose_name=_('Login mode'))
cmd_filters = models.ManyToManyField('CommandFilter', related_name='system_users', verbose_name=_("Command filter"), blank=True)
def __unicode__(self):
return self.name
__str__ = __unicode__
cache_key = "__SYSTEM_USER_CACHED_{}"
@property
def password(self):
if self._password:
return signer.unsign(self._password)
return None
@password.setter
def password(self, password_raw):
self._password = signer.sign(password_raw)
@property
def private_key(self):
if self._private_key:
return signer.unsign(self._private_key)
return None
@private_key.setter
def private_key(self, private_key_raw):
self._private_key = signer.sign(private_key_raw)
@property
def public_key(self):
return signer.unsign(self._public_key)
@public_key.setter
def public_key(self, public_key_raw):
self._public_key = signer.sign(public_key_raw)
def get_assets_inherit_from_asset_groups(self):
assets = set()
asset_groups = self.asset_groups.all()
for asset_group in asset_groups:
for asset in asset_group.assets.all():
setattr(asset, 'is_inherit_from_asset_groups', True)
setattr(asset, 'inherit_from_asset_groups',
getattr(asset, 'inherit_from_asset_groups', set()).add(asset_group))
assets.add(asset)
return assets
def get_assets(self):
assets = set(self.assets.all()
) | self.get_assets_inherit_from_asset_groups()
return list(assets)
def _to_secret_json(self):
"""Push system user use it"""
return {
'name': self.name,
'username': self.username,
'shell': self.shell,
'sudo': self.sudo,
'password': self.password,
'public_key': self.public_key
}
@property
def assets_amount(self):
return self.assets.count()
@property
def asset_group_amount(self):
return self.asset_groups.count()
def __str__(self):
return '{0.name}({0.username})'.format(self)
def to_json(self):
return {
@@ -228,12 +130,63 @@ class SystemUser(models.Model):
'name': self.name,
'username': self.username,
'protocol': self.protocol,
'auth_method': self.auth_method,
'priority': self.priority,
'auto_push': self.auto_push,
}
def get_assets(self):
assets = set(self.assets.all())
return assets
@property
def assets_connective(self):
_result = cache.get(SYSTEM_USER_CONN_CACHE_KEY.format(self.name), {})
return _result
@property
def unreachable_assets(self):
return list(self.assets_connective.get('dark', {}).keys())
@property
def reachable_assets(self):
return self.assets_connective.get('contacted', [])
def is_need_push(self):
if self.auto_push and self.protocol == self.__class__.SSH_PROTOCOL:
return True
else:
return False
def set_cache(self):
cache.set(self.cache_key.format(self.id), self, 3600)
def expire_cache(self):
cache.delete(self.cache_key.format(self.id))
@property
def cmd_filter_rules(self):
from .cmd_filter import CommandFilterRule
rules = CommandFilterRule.objects.filter(
filter__in=self.cmd_filters.all()
).order_by('priority').distinct()
return rules
@classmethod
def get_system_user_by_id_or_cached(cls, sid):
cached = cache.get(cls.cache_key.format(sid))
if cached:
return cached
try:
system_user = cls.objects.get(id=sid)
system_user.set_cache()
return system_user
except cls.DoesNotExist:
return None
class Meta:
ordering = ['name']
unique_together = [('name', 'org_id')]
verbose_name = _("System user")
@classmethod
def generate_fake(cls, count=10):
@@ -254,6 +207,3 @@ class SystemUser(models.Model):
except IntegrityError:
print('Error continue')
continue

View File

@@ -2,22 +2,34 @@
# -*- coding: utf-8 -*-
#
from . import IDC, SystemUser, AdminUser, AssetGroup, Asset
from django.core.exceptions import ValidationError
from common.utils import validate_ssh_private_key
__all__ = ['init_model', 'generate_fake']
def init_model():
for cls in [IDC, SystemUser, AdminUser, AssetGroup, Asset]:
from . import SystemUser, AdminUser, Asset
for cls in [SystemUser, AdminUser, Asset]:
if hasattr(cls, 'initial'):
cls.initial()
def generate_fake():
for cls in [IDC, SystemUser, AdminUser, AssetGroup, Asset]:
from . import SystemUser, AdminUser, Asset
for cls in [SystemUser, AdminUser, Asset]:
if hasattr(cls, 'generate_fake'):
cls.generate_fake()
def private_key_validator(value):
if not validate_ssh_private_key(value):
raise ValidationError(
_('%(value)s is not an even number'),
params={'value': value},
)
if __name__ == '__main__':
pass

View File

@@ -1,189 +0,0 @@
# -*- coding: utf-8 -*-
from django.utils.translation import ugettext_lazy as _
from django.core.cache import cache
from rest_framework import viewsets, serializers, generics
from .models import AssetGroup, Asset, IDC, AdminUser, SystemUser
from common.mixins import IDInFilterMixin
from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin
class AssetGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer):
assets_amount = serializers.SerializerMethodField()
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
class Meta:
model = AssetGroup
list_serializer_class = BulkListSerializer
fields = ['id', 'name', 'comment', 'assets_amount', 'assets']
@staticmethod
def get_assets_amount(obj):
return obj.assets.count()
class AssetUpdateGroupSerializer(serializers.ModelSerializer):
groups = serializers.PrimaryKeyRelatedField(many=True, queryset=AssetGroup.objects.all())
class Meta:
model = Asset
fields = ['id', 'groups']
class AssetUpdateSystemUserSerializer(serializers.ModelSerializer):
system_users = serializers.PrimaryKeyRelatedField(many=True, queryset=SystemUser.objects.all())
class Meta:
model = Asset
fields = ['id', 'system_users']
class AssetGroupUpdateSerializer(serializers.ModelSerializer):
"""update the asset group, and add or delete the asset to the group"""
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
class Meta:
model = AssetGroup
fields = ['id', 'assets']
class AssetGroupUpdateSystemUserSerializer(serializers.ModelSerializer):
system_users = serializers.PrimaryKeyRelatedField(many=True, queryset=SystemUser.objects.all())
class Meta:
model = AssetGroup
fields = ['id', 'system_users']
class IDCUpdateAssetsSerializer(serializers.ModelSerializer):
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
class Meta:
model = IDC
fields = ['id', 'assets']
class AdminUserSerializer(serializers.ModelSerializer):
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
class Meta:
model = AdminUser
fields = '__all__'
def get_field_names(self, declared_fields, info):
fields = super(AdminUserSerializer, self).get_field_names(declared_fields, info)
fields.append('assets_amount')
return fields
class SystemUserSerializer(serializers.ModelSerializer):
class Meta:
model = SystemUser
exclude = ('_password', '_private_key', '_public_key')
def get_field_names(self, declared_fields, info):
fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info)
fields.extend(['assets_amount'])
return fields
class AssetSystemUserSerializer(serializers.ModelSerializer):
class Meta:
model = SystemUser
fields = ('id', 'name', 'username', 'protocol', 'auth_method', 'comment')
class SystemUserUpdateAssetsSerializer(serializers.ModelSerializer):
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
class Meta:
model = SystemUser
fields = ['id', 'assets']
class SystemUserUpdateAssetGroupSerializer(serializers.ModelSerializer):
asset_groups = serializers.PrimaryKeyRelatedField(many=True, queryset=AssetGroup.objects.all())
class Meta:
model = SystemUser
fields = ['id', 'asset_groups']
class SystemUserSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = SystemUser
fields = ('id', 'name', 'username')
class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
# system_users = SystemUserSerializer(many=True, read_only=True)
# admin_user = AdminUserSerializer(many=False, read_only=True)
hardware = serializers.SerializerMethodField()
is_online = serializers.SerializerMethodField()
class Meta(object):
model = Asset
list_serializer_class = BulkListSerializer
fields = '__all__'
@staticmethod
def get_hardware(obj):
if obj.cpu_count:
return '{} Core {} {}'.format(obj.cpu_count*obj.cpu_cores, obj.memory, obj.disk_total)
else:
return ''
@staticmethod
def get_is_online(obj):
hostname = obj.hostname
if cache.get(hostname) == '1':
return True
elif cache.get(hostname) == '0':
return False
else:
return 'Unknown'
def get_field_names(self, declared_fields, info):
fields = super(AssetSerializer, self).get_field_names(declared_fields, info)
fields.extend(['get_type_display', 'get_env_display'])
return fields
class AssetGrantedSerializer(serializers.ModelSerializer):
system_users_granted = AssetSystemUserSerializer(many=True, read_only=True)
is_inherited = serializers.SerializerMethodField()
system_users_join = serializers.SerializerMethodField()
class Meta(object):
model = Asset
fields = ("id", "hostname", "ip", "port", "system_users_granted", "is_inherited",
"is_active", "system_users_join", "comment")
@staticmethod
def get_is_inherited(obj):
if getattr(obj, 'inherited', ''):
return True
else:
return False
@staticmethod
def get_system_users_join(obj):
return ', '.join([system_user.username for system_user in obj.system_users_granted])
class IDCSerializer(BulkSerializerMixin, serializers.ModelSerializer):
assets_amount = serializers.SerializerMethodField()
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
class Meta:
model = IDC
fields = '__all__'
@staticmethod
def get_assets_amount(obj):
return obj.assets.count()
def get_field_names(self, declared_fields, info):
fields = super(IDCSerializer, self).get_field_names(declared_fields, info)
fields.append('assets_amount')
return fields

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
#
from .asset import *
from .admin_user import *
from .label import *
from .system_user import *
from .node import *
from .domain import *
from .cmd_filter import *

View File

@@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
#
from django.core.cache import cache
from rest_framework import serializers
from ..models import Node, AdminUser
from ..const import ADMIN_USER_CONN_CACHE_KEY
from .base import AuthSerializer
class AdminUserSerializer(serializers.ModelSerializer):
"""
管理用户
"""
assets_amount = serializers.SerializerMethodField()
unreachable_amount = serializers.SerializerMethodField()
reachable_amount = serializers.SerializerMethodField()
class Meta:
model = AdminUser
fields = '__all__'
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
return [f for f in fields if not f.startswith('_')]
@staticmethod
def get_unreachable_amount(obj):
data = cache.get(ADMIN_USER_CONN_CACHE_KEY.format(obj.name))
if data:
return len(data.get('dark'))
else:
return 0
@staticmethod
def get_reachable_amount(obj):
data = cache.get(ADMIN_USER_CONN_CACHE_KEY.format(obj.name))
if data:
return len(data.get('contacted'))
else:
return 0
@staticmethod
def get_assets_amount(obj):
return obj.assets_amount
class AdminUserAuthSerializer(AuthSerializer):
class Meta:
model = AdminUser
fields = ['password', 'private_key']
class ReplaceNodeAdminUserSerializer(serializers.ModelSerializer):
"""
管理用户更新关联到的集群
"""
nodes = serializers.PrimaryKeyRelatedField(
many=True, queryset = Node.objects.all()
)
class Meta:
model = AdminUser
fields = ['id', 'nodes']

View File

@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
#
from rest_framework import serializers
from rest_framework_bulk.serializers import BulkListSerializer
from common.mixins import BulkSerializerMixin
from ..models import Asset
from .system_user import AssetSystemUserSerializer
__all__ = [
'AssetSerializer', 'AssetGrantedSerializer', 'MyAssetGrantedSerializer',
]
class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
"""
资产的数据结构
"""
class Meta:
model = Asset
list_serializer_class = BulkListSerializer
fields = '__all__'
validators = []
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
fields.extend([
'hardware_info', 'is_connective', 'org_name'
])
return fields
class AssetGrantedSerializer(serializers.ModelSerializer):
"""
被授权资产的数据结构
"""
system_users_granted = AssetSystemUserSerializer(many=True, read_only=True)
system_users_join = serializers.SerializerMethodField()
# nodes = NodeTMPSerializer(many=True, read_only=True)
class Meta:
model = Asset
fields = (
"id", "hostname", "ip", "port", "system_users_granted",
"is_active", "system_users_join", "os", 'domain',
"platform", "comment", "protocol", "org_id", "org_name",
)
@staticmethod
def get_system_users_join(obj):
system_users = [s.username for s in obj.system_users_granted]
return ', '.join(system_users)
class MyAssetGrantedSerializer(AssetGrantedSerializer):
"""
普通用户获取授权的资产定义的数据结构
"""
class Meta:
model = Asset
fields = (
"id", "hostname", "system_users_granted",
"is_active", "system_users_join", "org_name",
"os", "platform", "comment", "org_id", "protocol"
)

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
#
from rest_framework import serializers
from common.utils import ssh_pubkey_gen
class AuthSerializer(serializers.ModelSerializer):
password = serializers.CharField(required=False, allow_blank=True, allow_null=True, max_length=1024)
private_key = serializers.CharField(required=False, allow_blank=True, allow_null=True, max_length=4096)
def gen_keys(self, private_key=None, password=None):
if private_key is None:
return None, None
public_key = ssh_pubkey_gen(private_key=private_key, password=password)
return private_key, public_key
def save(self, **kwargs):
password = self.validated_data.pop('password', None) or None
private_key = self.validated_data.pop('private_key', None) or None
self.instance = super().save(**kwargs)
if password or private_key:
private_key, public_key = self.gen_keys(private_key, password)
self.instance.set_auth(password=password, private_key=private_key,
public_key=public_key)
return self.instance

View File

@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
#
from rest_framework import serializers
from common.fields import ChoiceDisplayField
from ..models import CommandFilter, CommandFilterRule, SystemUser
class CommandFilterSerializer(serializers.ModelSerializer):
rules = serializers.PrimaryKeyRelatedField(queryset=CommandFilterRule.objects.all(), many=True)
system_users = serializers.PrimaryKeyRelatedField(queryset=SystemUser.objects.all(), many=True)
class Meta:
model = CommandFilter
fields = '__all__'
class CommandFilterRuleSerializer(serializers.ModelSerializer):
serializer_choice_field = ChoiceDisplayField
class Meta:
model = CommandFilterRule
fields = '__all__'

View File

@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
#
from rest_framework import serializers
from ..models import Domain, Gateway
class DomainSerializer(serializers.ModelSerializer):
asset_count = serializers.SerializerMethodField()
gateway_count = serializers.SerializerMethodField()
class Meta:
model = Domain
fields = '__all__'
@staticmethod
def get_asset_count(obj):
return obj.assets.count()
@staticmethod
def get_gateway_count(obj):
return obj.gateway_set.all().count()
class GatewaySerializer(serializers.ModelSerializer):
class Meta:
model = Gateway
fields = [
'id', 'name', 'ip', 'port', 'protocol', 'username',
'domain', 'is_active', 'date_created', 'date_updated',
'created_by', 'comment',
]
class GatewayWithAuthSerializer(GatewaySerializer):
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
fields.extend(
['password', 'private_key']
)
return fields
class DomainWithGatewaySerializer(serializers.ModelSerializer):
gateways = GatewayWithAuthSerializer(many=True, read_only=True)
class Meta:
model = Domain
fields = '__all__'

View File

@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
#
from rest_framework import serializers
from rest_framework_bulk.serializers import BulkListSerializer
from ..models import Label
class LabelSerializer(serializers.ModelSerializer):
asset_count = serializers.SerializerMethodField()
class Meta:
model = Label
fields = '__all__'
list_serializer_class = BulkListSerializer
@staticmethod
def get_asset_count(obj):
return obj.assets.count()
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
fields.extend(['get_category_display'])
return fields
class LabelDistinctSerializer(serializers.ModelSerializer):
value = serializers.SerializerMethodField()
class Meta:
model = Label
fields = ("name", "value")
@staticmethod
def get_value(obj):
labels = Label.objects.filter(name=obj["name"])
return ', '.join([label.value for label in labels])

View File

@@ -0,0 +1,97 @@
# -*- coding: utf-8 -*-
from rest_framework import serializers
from rest_framework_bulk.serializers import BulkListSerializer
from common.mixins import BulkSerializerMixin
from ..models import Asset, Node
from .asset import AssetGrantedSerializer
__all__ = [
'NodeSerializer', "NodeGrantedSerializer", "NodeAddChildrenSerializer",
"NodeAssetsSerializer",
]
class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer):
"""
授权资产组
"""
assets_granted = AssetGrantedSerializer(many=True, read_only=True)
assets_amount = serializers.SerializerMethodField()
parent = serializers.SerializerMethodField()
name = serializers.SerializerMethodField()
class Meta:
model = Node
fields = [
'id', 'key', 'name', 'value', 'parent',
'assets_granted', 'assets_amount', 'org_id',
]
@staticmethod
def get_assets_amount(obj):
return len(obj.assets_granted)
@staticmethod
def get_name(obj):
return obj.name
@staticmethod
def get_parent(obj):
return obj.parent.id
class NodeSerializer(serializers.ModelSerializer):
assets_amount = serializers.SerializerMethodField()
tree_id = serializers.SerializerMethodField()
tree_parent = serializers.SerializerMethodField()
class Meta:
model = Node
fields = [
'id', 'key', 'value', 'assets_amount',
'is_node', 'org_id', 'tree_id', 'tree_parent',
]
list_serializer_class = BulkListSerializer
def validate(self, data):
value = data.get('value')
instance = self.instance if self.instance else Node.root()
children = instance.parent.get_children().exclude(key=instance.key)
values = [child.value for child in children]
if value in values:
raise serializers.ValidationError(
'The same level node name cannot be the same'
)
return data
@staticmethod
def get_assets_amount(obj):
return obj.get_all_assets().count()
@staticmethod
def get_tree_id(obj):
return obj.key
@staticmethod
def get_tree_parent(obj):
return obj.parent_key
def get_fields(self):
fields = super().get_fields()
field = fields["key"]
field.required = False
return fields
class NodeAssetsSerializer(serializers.ModelSerializer):
assets = serializers.PrimaryKeyRelatedField(many=True, queryset = Asset.objects.all())
class Meta:
model = Node
fields = ['assets']
class NodeAddChildrenSerializer(serializers.Serializer):
nodes = serializers.ListField()

View File

@@ -0,0 +1,78 @@
from rest_framework import serializers
from ..models import SystemUser
from .base import AuthSerializer
class SystemUserSerializer(serializers.ModelSerializer):
"""
系统用户
"""
unreachable_amount = serializers.SerializerMethodField()
reachable_amount = serializers.SerializerMethodField()
unreachable_assets = serializers.SerializerMethodField()
reachable_assets = serializers.SerializerMethodField()
assets_amount = serializers.SerializerMethodField()
class Meta:
model = SystemUser
exclude = ('_password', '_private_key', '_public_key')
def get_field_names(self, declared_fields, info):
fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info)
fields.extend([
'get_login_mode_display',
])
return fields
@staticmethod
def get_unreachable_assets(obj):
return obj.unreachable_assets
@staticmethod
def get_reachable_assets(obj):
return obj.reachable_assets
def get_unreachable_amount(self, obj):
return len(self.get_unreachable_assets(obj))
def get_reachable_amount(self, obj):
return len(self.get_reachable_assets(obj))
@staticmethod
def get_assets_amount(obj):
return len(obj.get_assets())
class SystemUserAuthSerializer(AuthSerializer):
"""
系统用户认证信息
"""
class Meta:
model = SystemUser
fields = [
"id", "name", "username", "protocol",
"login_mode", "password", "private_key",
]
class AssetSystemUserSerializer(serializers.ModelSerializer):
"""
查看授权的资产系统用户的数据结构这个和AssetSerializer不同字段少
"""
class Meta:
model = SystemUser
fields = (
'id', 'name', 'username', 'priority',
'protocol', 'comment', 'login_mode'
)
class SystemUserSimpleSerializer(serializers.ModelSerializer):
"""
系统用户最基本信息的数据结构
"""
class Meta:
model = SystemUser
fields = ('id', 'name', 'username')

5
apps/assets/signals.py Normal file
View File

@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
#
from django.dispatch import Signal
on_app_ready = Signal()

View File

@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
#
from collections import defaultdict
from django.db.models.signals import post_save, m2m_changed
from django.dispatch import receiver
from common.utils import get_logger
from .models import Asset, SystemUser, Node
from .tasks import update_assets_hardware_info_util, \
test_asset_connectability_util, push_system_user_to_assets
logger = get_logger(__file__)
def update_asset_hardware_info_on_created(asset):
logger.debug("Update asset `{}` hardware info".format(asset))
update_assets_hardware_info_util.delay([asset])
def test_asset_conn_on_created(asset):
logger.debug("Test asset `{}` connectability".format(asset))
test_asset_connectability_util.delay([asset])
def set_asset_root_node(asset):
logger.debug("Set asset default node: {}".format(Node.root()))
asset.nodes.add(Node.root())
@receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier")
def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
if created:
logger.info("Asset `{}` create signal received".format(instance))
update_asset_hardware_info_on_created(instance)
test_asset_conn_on_created(instance)
@receiver(post_save, sender=SystemUser, dispatch_uid="my_unique_identifier")
def on_system_user_update(sender, instance=None, created=True, **kwargs):
if instance and not created:
logger.info("System user `{}` update signal received".format(instance))
assets = instance.assets.all()
push_system_user_to_assets.delay(instance, assets)
@receiver(m2m_changed, sender=SystemUser.nodes.through)
def on_system_user_nodes_change(sender, instance=None, **kwargs):
if instance and kwargs["action"] == "post_add":
assets = set()
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
for node in nodes:
assets.update(set(node.get_all_assets()))
instance.assets.add(*tuple(assets))
@receiver(m2m_changed, sender=SystemUser.assets.through)
def on_system_user_assets_change(sender, instance=None, **kwargs):
if instance and kwargs["action"] == "post_add":
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
push_system_user_to_assets(instance, assets)
@receiver(m2m_changed, sender=Asset.nodes.through)
def on_asset_node_changed(sender, instance=None, **kwargs):
if isinstance(instance, Asset):
if kwargs['action'] == 'post_add':
logger.debug("Asset node change signal received")
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
system_users_assets = defaultdict(set)
system_users = SystemUser.objects.filter(nodes__in=nodes)
# 清理节点缓存
for system_user in system_users:
system_users_assets[system_user].update({instance})
for system_user, assets in system_users_assets.items():
system_user.assets.add(*tuple(assets))
@receiver(m2m_changed, sender=Asset.nodes.through)
def on_node_assets_changed(sender, instance=None, **kwargs):
if isinstance(instance, Node):
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
if kwargs['action'] == 'post_add':
logger.debug("Node assets change signal received")
# 重新关联系统用户和资产的关系
system_users = SystemUser.objects.filter(nodes=instance)
for system_user in system_users:
system_user.assets.add(*tuple(assets))
@receiver(post_save, sender=Node)
def on_node_update_or_created(sender, instance=None, created=False, **kwargs):
if instance and not created:
instance.expire_full_value()

View File

@@ -1,80 +1,436 @@
# ~*~ coding: utf-8 ~*~
from celery import shared_task
import json
import re
import os
from celery import shared_task
from django.core.cache import cache
from django.utils.translation import ugettext as _
from ops.tasks import run_AdHoc
from common.utils import get_object_or_none, capacity_convert, sum_capacity
from .models import Asset
from common.utils import get_object_or_none, capacity_convert, \
sum_capacity, encrypt_password, get_logger
from ops.celery.utils import register_as_period_task, after_app_shutdown_clean, \
after_app_ready_start
from ops.celery import app as celery_app
from .models import SystemUser, AdminUser, Asset
from . import const
FORKS = 10
TIMEOUT = 60
logger = get_logger(__file__)
CACHE_MAX_TIME = 60*60*60
disk_pattern = re.compile(r'^hd|sd|xvd|vd')
PERIOD_TASK = os.environ.get("PERIOD_TASK", "off")
@shared_task
def update_assets_hardware_info(assets):
task_tuple = (
('setup', ''),
)
summary, result = run_AdHoc(task_tuple, assets, record=False)
for hostname, info in result['contacted'].items():
if info:
info = info[0]['ansible_facts']
else:
def set_assets_hardware_info(result, **kwargs):
"""
Using ops task run result, to update asset info
@shared_task must be exit, because we using it as a task callback, is must
be a celery task also
:param result:
:param kwargs: {task_name: ""}
:return:
"""
result_raw = result[0]
assets_updated = []
for hostname, info in result_raw.get('ok', {}).items():
info = info.get('setup', {}).get('ansible_facts', {})
if not info:
logger.error("Get asset info failed: {}".format(hostname))
continue
asset = get_object_or_none(Asset, hostname=hostname)
asset = Asset.objects.get_object_by_fullname(hostname)
if not asset:
continue
___vendor = info['ansible_system_vendor']
___model = info['ansible_product_version']
___sn = info['ansible_product_serial']
___vendor = info.get('ansible_system_vendor', 'Unknown')
___model = info.get('ansible_product_name', 'Unknown')
___sn = info.get('ansible_product_serial', 'Unknown')
for ___cpu_model in info['ansible_processor']:
if ___cpu_model.endswith('GHz'):
for ___cpu_model in info.get('ansible_processor', []):
if ___cpu_model.endswith('GHz') or ___cpu_model.startswith("Intel"):
break
else:
___cpu_model = 'Unknown'
___cpu_count = info['ansible_processor_count']
___cpu_cores = info['ansible_processor_cores']
___memory = '%s %s' % capacity_convert('{} MB'.format(info['ansible_memtotal_mb']))
___cpu_model = ___cpu_model[:64]
___cpu_count = info.get('ansible_processor_count', 0)
___cpu_cores = info.get('ansible_processor_cores', None) or len(info.get('ansible_processor', []))
___cpu_vcpus = info.get('ansible_processor_vcpus', 0)
___memory = '%s %s' % capacity_convert('{} MB'.format(info.get('ansible_memtotal_mb')))
disk_info = {}
for dev, dev_info in info['ansible_devices'].items():
if dev_info['removable'] == '0':
for dev, dev_info in info.get('ansible_devices', {}).items():
if disk_pattern.match(dev) and dev_info['removable'] == '0':
disk_info[dev] = dev_info['size']
___disk_total = '%s %s' % sum_capacity(disk_info.values())
___disk_info = json.dumps(disk_info)
___platform = info['ansible_system']
___os = info['ansible_distribution']
___os_version = info['ansible_distribution_version']
___os_arch = info['ansible_architecture']
___hostname_raw = info['ansible_hostname']
___platform = info.get('ansible_system', 'Unknown')
___os = info.get('ansible_distribution', 'Unknown')
___os_version = info.get('ansible_distribution_version', 'Unknown')
___os_arch = info.get('ansible_architecture', 'Unknown')
___hostname_raw = info.get('ansible_hostname', 'Unknown')
for k, v in locals().items():
if k.startswith('___'):
setattr(asset, k.strip('_'), v)
asset.save()
return summary
assets_updated.append(asset)
return assets_updated
@shared_task
def update_assets_hardware_period():
assets = Asset.objects.filter(type__in=['Server', 'VM'])
update_assets_hardware_info(assets)
@shared_task
def test_admin_user_connective_period():
assets = Asset.objects.filter(type__in=['Server', 'VM'])
task_tuple = (
('ping', ''),
def update_assets_hardware_info_util(assets, task_name=None):
"""
Using ansible api to update asset hardware info
:param assets: asset seq
:param task_name: task_name running
:return: result summary ['contacted': {}, 'dark': {}]
"""
from ops.utils import update_or_create_ansible_task
if task_name is None:
task_name = _("Update some assets hardware info")
# task_name = _("更新资产硬件信息")
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
hostname_list = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()]
if not hostname_list:
logger.info("Not hosts get, may be asset is not active or not unixlike platform")
return {}
task, created = update_or_create_ansible_task(
task_name, hosts=hostname_list, tasks=tasks, pattern='all',
options=const.TASK_OPTIONS, run_as_admin=True, created_by='System',
)
summary, _ = run_AdHoc(task_tuple, assets, record=False)
for i in summary['success']:
cache.set(i, '1', 2*60*60*60)
result = task.run()
# Todo: may be somewhere using
# Manual run callback function
set_assets_hardware_info(result)
return result
for i, msg in summary['failed']:
cache.set(i, '0', 60*60*60)
@shared_task
def update_asset_hardware_info_manual(asset):
task_name = _("Update asset hardware info")
# task_name = _("更新资产硬件信息")
return update_assets_hardware_info_util([asset], task_name=task_name)
@celery_app.task
@register_as_period_task(interval=3600)
@after_app_ready_start
@after_app_shutdown_clean
def update_assets_hardware_info_period():
"""
Update asset hardware period task
:return:
"""
if PERIOD_TASK != "on":
logger.debug("Period task disabled, update assets hardware info pass")
return
from ops.utils import update_or_create_ansible_task
task_name = _("Update assets hardware info period")
# task_name = _("定期更新资产硬件信息")
hostname_list = [
asset.fullname for asset in Asset.objects.all()
if asset.is_active and asset.is_unixlike()
]
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
# Only create, schedule by celery beat
update_or_create_ansible_task(
task_name, hosts=hostname_list, tasks=tasks, pattern='all',
options=const.TASK_OPTIONS, run_as_admin=True, created_by='System',
interval=60*60*24, is_periodic=True, callback=set_assets_hardware_info.name,
)
## ADMIN USER CONNECTIVE ##
@shared_task
def set_admin_user_connectability_info(result, **kwargs):
admin_user = kwargs.get("admin_user")
task_name = kwargs.get("task_name")
if admin_user is None and task_name is not None:
admin_user = task_name.split(":")[-1]
raw, summary = result
cache_key = const.ADMIN_USER_CONN_CACHE_KEY.format(admin_user)
cache.set(cache_key, summary, CACHE_MAX_TIME)
for i in summary.get('contacted', []):
asset_conn_cache_key = const.ASSET_ADMIN_CONN_CACHE_KEY.format(i)
cache.set(asset_conn_cache_key, 1, CACHE_MAX_TIME)
for i, msg in summary.get('dark', {}).items():
asset_conn_cache_key = const.ASSET_ADMIN_CONN_CACHE_KEY.format(i)
cache.set(asset_conn_cache_key, 0, CACHE_MAX_TIME)
logger.error(msg)
@shared_task
def test_admin_user_connectability_util(admin_user, task_name):
"""
Test asset admin user can connect or not. Using ansible api do that
:param admin_user:
:param task_name:
:return:
"""
from ops.utils import update_or_create_ansible_task
assets = admin_user.get_related_assets()
hosts = [asset.fullname for asset in assets
if asset.is_active and asset.is_unixlike()]
if not hosts:
return
tasks = const.TEST_ADMIN_USER_CONN_TASKS
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
options=const.TASK_OPTIONS, run_as_admin=True, created_by='System',
)
result = task.run()
set_admin_user_connectability_info(result, admin_user=admin_user.name)
return result
@celery_app.task
@register_as_period_task(interval=3600)
@after_app_ready_start
@after_app_shutdown_clean
def test_admin_user_connectability_period():
"""
A period task that update the ansible task period
"""
if PERIOD_TASK != "on":
logger.debug("Period task disabled, test admin user connectability pass")
return
admin_users = AdminUser.objects.all()
for admin_user in admin_users:
task_name = _("Test admin user connectability period: {}".format(admin_user.name))
# task_name = _("定期测试管理账号可连接性: {}".format(admin_user.name))
test_admin_user_connectability_util(admin_user, task_name)
@shared_task
def test_admin_user_connectability_manual(admin_user):
task_name = _("Test admin user connectability: {}").format(admin_user.name)
# task_name = _("测试管理行号可连接性: {}").format(admin_user.name)
return test_admin_user_connectability_util(admin_user, task_name)
@shared_task
def test_asset_connectability_util(assets, task_name=None):
from ops.utils import update_or_create_ansible_task
if task_name is None:
task_name = _("Test assets connectability")
# task_name = _("测试资产可连接性")
hosts = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()]
if not hosts:
logger.info("No hosts, passed")
return {}
tasks = const.TEST_ADMIN_USER_CONN_TASKS
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
options=const.TASK_OPTIONS, run_as_admin=True, created_by='System',
)
result = task.run()
summary = result[1]
for k in summary.get('dark'):
cache.set(const.ASSET_ADMIN_CONN_CACHE_KEY.format(k), 0, CACHE_MAX_TIME)
for k in summary.get('contacted'):
cache.set(const.ASSET_ADMIN_CONN_CACHE_KEY.format(k), 1, CACHE_MAX_TIME)
return summary
@shared_task
def test_asset_connectability_manual(asset):
summary = test_asset_connectability_util([asset])
if summary.get('dark'):
return False, summary['dark']
else:
return True, ""
## System user connective ##
@shared_task
def set_system_user_connectablity_info(result, **kwargs):
summary = result[1]
task_name = kwargs.get("task_name")
system_user = kwargs.get("system_user")
if system_user is None:
system_user = task_name.split(":")[-1]
cache_key = const.SYSTEM_USER_CONN_CACHE_KEY.format(system_user)
cache.set(cache_key, summary, CACHE_MAX_TIME)
@shared_task
def test_system_user_connectability_util(system_user, assets, task_name):
"""
Test system cant connect his assets or not.
:param system_user:
:param assets:
:param task_name:
:return:
"""
from ops.utils import update_or_create_ansible_task
# assets = system_user.get_assets()
hosts = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()]
tasks = const.TEST_SYSTEM_USER_CONN_TASKS
if not hosts:
logger.info("No hosts, passed")
return {}
task, created = update_or_create_ansible_task(
task_name, hosts=hosts, tasks=tasks, pattern='all',
options=const.TASK_OPTIONS,
run_as=system_user.name, created_by="System",
)
result = task.run()
set_system_user_connectablity_info(result, system_user=system_user.name)
return result
@shared_task
def test_system_user_connectability_manual(system_user):
task_name = _("Test system user connectability: {}").format(system_user)
assets = system_user.get_assets()
return test_system_user_connectability_util(system_user, assets, task_name)
@shared_task
def test_system_user_connectability_a_asset(system_user, asset):
task_name = _("Test system user connectability: {} => {}").format(
system_user, asset
)
return test_system_user_connectability_util(system_user, [asset], task_name)
@shared_task
@register_as_period_task(interval=3600)
@after_app_ready_start
@after_app_shutdown_clean
def test_system_user_connectability_period():
if PERIOD_TASK != "on":
logger.debug("Period task disabled, test system user connectability pass")
return
system_users = SystemUser.objects.all()
for system_user in system_users:
task_name = _("Test system user connectability period: {}".format(system_user))
# task_name = _("定期测试系统用户可连接性: {}".format(system_user))
test_system_user_connectability_util(system_user, task_name)
#### Push system user tasks ####
def get_push_system_user_tasks(system_user):
# Set root as system user is dangerous
if system_user.username == "root":
return []
tasks = []
if system_user.password:
tasks.append({
'name': 'Add user {}'.format(system_user.username),
'action': {
'module': 'user',
'args': 'name={} shell={} state=present password={}'.format(
system_user.username, system_user.shell,
encrypt_password(system_user.password, salt="K3mIlKK"),
),
}
})
if system_user.public_key:
tasks.append({
'name': 'Set {} authorized key'.format(system_user.username),
'action': {
'module': 'authorized_key',
'args': "user={} state=present key='{}'".format(
system_user.username, system_user.public_key
)
}
})
if system_user.sudo:
tasks.append({
'name': 'Set {} sudo setting'.format(system_user.username),
'action': {
'module': 'lineinfile',
'args': "dest=/etc/sudoers state=present regexp='^{0} ALL=' "
"line='{0} ALL=(ALL) NOPASSWD: {1}' "
"validate='visudo -cf %s'".format(
system_user.username,
system_user.sudo,
)
}
})
return tasks
@shared_task
def push_system_user_util(system_users, assets, task_name):
from ops.utils import update_or_create_ansible_task
tasks = []
for system_user in system_users:
if not system_user.is_need_push():
msg = "push system user `{}` passed, may be not auto push or ssh " \
"protocol is not ssh".format(system_user.name)
logger.info(msg)
continue
tasks.extend(get_push_system_user_tasks(system_user))
if not tasks:
logger.info("Not tasks, passed")
return {}
hosts = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()]
if not hosts:
logger.info("Not hosts, passed")
return {}
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
options=const.TASK_OPTIONS, run_as_admin=True, created_by='System'
)
return task.run()
@shared_task
def push_system_user_to_assets_manual(system_user):
assets = system_user.get_assets()
# task_name = "推送系统用户到入资产: {}".format(system_user.name)
task_name = _("Push system users to assets: {}").format(system_user.name)
return push_system_user_util([system_user], assets, task_name=task_name)
@shared_task
def push_system_user_a_asset_manual(system_user, asset):
task_name = _("Push system users to asset: {} => {}").format(
system_user.name, asset.fullname
)
return push_system_user_util([system_user], [asset], task_name=task_name)
@shared_task
def push_system_user_to_assets(system_user, assets):
# task_name = _("推送系统用户到入资产: {}").format(system_user.name)
task_name = _("Push system users to assets: {}").format(system_user.name)
return push_system_user_util.delay([system_user], assets, task_name)
# @shared_task
# @register_as_period_task(interval=3600)
# @after_app_ready_start
# # @after_app_shutdown_clean
# def push_system_user_period():
# for system_user in SystemUser.objects.all():
# push_system_user_related_nodes(system_user)

View File

@@ -2,12 +2,11 @@
{% load i18n %}
{% block modal_id %}asset_group_bulk_update_modal{% endblock %}
{% block modal_class %}modal-lg{% endblock %}
{% block modal_title%}{% trans "Update Asset Group" %}{% endblock %}
{% block modal_title%}{% trans "Update asset group" %}{% endblock %}
{% block modal_body %}
{% load bootstrap3 %}
<p class="text-success text-center">{% trans "Hint: only change the field you want to update." %}</p>
<form method="post" class="form-horizontal" action="" id="fm_asset_group_bulk_update">
<div class="form-group">
<label for="assets" class="col-sm-2 control-label">{% trans 'Assets' %}</label>
<div class="col-sm-9" id="select2-container">
@@ -32,7 +31,7 @@
<div class="form-group">
<div class="col-sm-9 col-lg-9 col-sm-offset-2">
<div class="checkbox checkbox-success">
<input type="checkbox" name="enable_otp" checked id="id_enable_otp"><label for="id_enable_otp">{% trans 'Enable-OTP' %}</label>
<input type="checkbox" name="enable_otp" checked id="id_enable_otp"><label for="id_enable_otp">{% trans 'Enable-MFA' %}</label>
</div>
</div>
</div>

View File

@@ -0,0 +1,131 @@
{% extends '_modal.html' %}
{% load i18n %}
{% load static %}
{% block modal_class %}modal-lg{% endblock %}
{% block modal_id %}asset_list_modal{% endblock %}
{% block modal_title%}{% trans "Asset list" %}{% endblock %}
{% block modal_body %}
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
<script src="{% static 'js/jquery.form.min.js' %}"></script>
<style>
.inmodal .modal-header {
padding: 10px 10px;
text-align: center;
}
#assetTree2.ztree * {
background-color: #f8fafb;
}
#assetTree2.ztree {
background-color: #f8fafb;
}
</style>
<div class="wrapper wrapper-content">
<div class="row">
<div class="col-lg-3" id="split-left" style="padding-left: 3px">
<div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
<div class="file-manager ">
<div id="assetTree2" class="ztree">
</div>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div class="col-lg-9 animated fadeInRight" id="split-right">
<div class="mail-box-header">
<table class="table table-striped table-bordered table-hover " id="asset_list_modal_table" style="width: 100%">
<thead>
<tr>
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
<th class="text-center">{% trans 'Hostname' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
var zTree2, asset_table2 = 0;
function initTable2() {
var options = {
ele: $('#asset_list_modal_table'),
ajax_url: '{% url "api-assets:asset-list" %}?show_current_asset=1',
columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" }
],
pageLength: 10
};
asset_table2 = jumpserver.initServerSideDataTable(options);
return asset_table2
}
function onSelected2(event, treeNode) {
var url = asset_table2.ajax.url();
url = setUrlParam(url, "node_id", treeNode.node_id);
setCookie('node_selected', treeNode.id);
asset_table2.ajax.url(url);
asset_table2.ajax.reload();
}
function initTree2() {
var setting = {
view: {
dblClickExpand: false,
showLine: true
},
data: {
simpleData: {
enable: true
}
},
callback: {
onSelected: onSelected2
}
};
var zNodes = [];
$.get("{% url 'api-assets:node-list' %}", function(data, status){
$.each(data, function (index, value) {
value["node_id"] = value["id"];
value["id"] = value["tree_id"];
value["pId"] = value["tree_parent"];
{#value["open"] = true;#}
if (value["key"] === "0") {
value["open"] = true;
}
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
});
zNodes = data;
$.fn.zTree.init($("#assetTree2"), setting, zNodes);
zTree2 = $.fn.zTree.getZTreeObj("assetTree2");
var root = zTree2.getNodes()[0];
zTree2.expandNode(root);
});
}
$(document).ready(function(){
initTable2();
initTree2();
})
</script>
{% endblock %}
{% block modal_button %}
{{ block.super }}
{% endblock %}
{% block modal_confirm_id %}btn_asset_modal_confirm{% endblock %}

View File

@@ -3,8 +3,8 @@
{% load static %}
{% load bootstrap3 %}
{% block custom_head_css_js %}
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
{% endblock %}
{% block content %}
@@ -13,7 +13,7 @@
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5>{% trans 'Create system user' %}</h5>
<h5>{{ action }}</h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
@@ -36,32 +36,36 @@
{% endif %}
<h3>{% trans 'Basic' %}</h3>
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.login_mode layout="horizontal" %}
{% bootstrap_field form.username layout="horizontal" %}
{% bootstrap_field form.priority layout="horizontal" %}
{% bootstrap_field form.protocol layout="horizontal" %}
<h3>{% trans 'Auth' %}</h3>
{% bootstrap_field form.auth_method layout="horizontal" %}
<h3 id="auth_title_id">{% trans 'Auth' %}</h3>
{% block auth %}
<div class="password-auth hidden">
{% bootstrap_field form.password layout="horizontal" %}
</div>
<div class="public-key-auth">
<div class="auto-generate">
<div class="form-group">
<label for="{{ form.auto_generate_key.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto generate key' %}</label>
<div class="col-sm-8">
{{ form.auto_generate_key}}
</div>
</div>
<div>
{% bootstrap_field form.private_key_file layout="horizontal" %}
</div>
</div>
{% endblock %}
<div class="auth-fields">
{% bootstrap_field form.password layout="horizontal" %}
{% bootstrap_field form.private_key_file layout="horizontal" %}
</div>
<div class="form-group">
<label for="{{ form.as_push.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto push' %}</label>
<label for="{{ form.auto_push.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto push' %}</label>
<div class="col-sm-8">
{{ form.auto_push}}
</div>
</div>
{% endblock %}
<div id="command-filter-block">
<h3>{% trans 'Command filter' %}</h3>
{% bootstrap_field form.cmd_filters layout="horizontal" %}
</div>
<h3>{% trans 'Other' %}</h3>
{% bootstrap_field form.sudo layout="horizontal" %}
{% bootstrap_field form.shell layout="horizontal" %}
@@ -80,42 +84,87 @@
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
var auth_method = '#'+'{{ form.auth_method.id_for_label }}';
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
function authMethodDisplay() {
if ($(auth_method).val() == 'P') {
$('.password-auth').removeClass('hidden');
$('.public-key-auth').addClass('hidden');
$('#'+'{{ form.password.id_for_label }}').attr('required', 'required');
$('#'+'{{ form.password.id_for_label }}').removeAttr('disabled');
$('#'+'{{ form.private_key_file.id_for_label }}').removeAttr('required');
<script>
var protocol_id = '#' + '{{ form.protocol.id_for_label }}';
var login_mode_id = '#' + '{{ form.login_mode.id_for_label }}';
} else if ($(auth_method).val() == 'K') {
$('.password-auth').addClass('hidden');
$('.public-key-auth').removeClass('hidden');
$('#'+'{{ form.password.id_for_label }}').removeAttr('required');
$('#'+'{{ form.password.id_for_label }}').attr('disabled', 'disabled');
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
var password_id = '#' + '{{ form.password.id_for_label }}';
var private_key_id = '#' + '{{ form.private_key_file.id_for_label }}';
var auto_push_id = '#' + '{{ form.auto_push.id_for_label }}';
var sudo_id = '#' + '{{ form.sudo.id_for_label }}';
var shell_id = '#' + '{{ form.shell.id_for_label }}';
if ($(auto_generate_key).prop('checked')){
$('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group').addClass('hidden');
} else {
$('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group').removeClass('hidden');
$('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group input').attr('required', 'required');
}
}
var need_change_field = [
auto_generate_key, private_key_id, auto_push_id, sudo_id, shell_id
];
var need_change_field_login_mode = [
auto_generate_key, private_key_id, auto_push_id, password_id
];
function protocolChange() {
if ($(protocol_id + " option:selected").text() === 'rdp') {
$('.auth-fields').removeClass('hidden');
$('#command-filter-block').addClass('hidden');
$.each(need_change_field, function (index, value) {
$(value).closest('.form-group').addClass('hidden')
});
}
else if ($(protocol_id + " option:selected").text() === 'telnet (beta)') {
$('.auth-fields').removeClass('hidden');
$.each(need_change_field, function (index, value) {
$(value).closest('.form-group').addClass('hidden')
});
}
else {
if($(login_mode_id).val() === 'manual'){
$(sudo_id).closest('.form-group').removeClass('hidden');
$(shell_id).closest('.form-group').removeClass('hidden');
return
}
$(document).ready(function () {
$('.select2').select2();
authMethodDisplay();
$(auth_method).change(function () {
authMethodDisplay();
});
$(auto_generate_key).change(function () {
authMethodDisplay();
});
authFieldsDisplay();
$.each(need_change_field, function (index, value) {
$(value).closest('.form-group').removeClass('hidden')
});
}
}
function authFieldsDisplay() {
if ($(auto_generate_key).prop('checked')) {
$('.auth-fields').addClass('hidden');
} else {
$('.auth-fields').removeClass('hidden');
}
}
function loginModeChange(){
if ($(login_mode_id).val() === 'manual'){
$('#auth_title_id').addClass('hidden');
$.each(need_change_field_login_mode, function(index, value){
$(value).closest('.form-group').addClass('hidden')
})
}
else if($(login_mode_id).val() === 'auto'){
$('#auth_title_id').removeClass('hidden');
$(password_id).closest('.form-group').removeClass('hidden')
protocolChange();
}
}
$(document).ready(function () {
$('.select2').select2();
authFieldsDisplay();
protocolChange();
loginModeChange();
})
.on('change', protocol_id, function(){
protocolChange();
})
.on('change', auto_generate_key, function(){
authFieldsDisplay();
})
.on('change', login_mode_id, function(){
loginModeChange();
})
</script>
</script>
{% endblock %}

View File

@@ -0,0 +1,24 @@
{% extends '_modal.html' %}
{% load i18n %}
{% load static %}
<style>
.modal-body {
background-color: white !important;
}
</style>
{% block modal_id %}user_asset_detail_modal{% endblock %}
{% block modal_title %}{% trans "Asset detail" %}{% endblock %}
{% block modal_body %}
<div class="ibox-content" style="background-color: inherit">
<table class="table">
<tbody id="asset_detail_tbody">
</tbody>
</table>
</div>
{% endblock %}
{% block modal_button %}
<button data-dismiss="modal" class="btn btn-white" type="button">{% trans "Close" %}</button>
{% endblock %}

View File

@@ -0,0 +1,137 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li>
<a href="{% url 'assets:admin-user-detail' pk=admin_user.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
</li>
<li class="active">
<a href="{% url 'assets:admin-user-assets' pk=admin_user.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Assets list' %} </a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-8" style="padding-left: 0;">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span style="float: left">{% trans 'Asset list of ' %} <b>{{ admin_user.name }}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table table-striped table-bordered table-hover" id="asset_list_table">
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th>{% trans 'Hostname' %}</th>
<th>{% trans 'IP' %}</th>
<th>{% trans 'Port' %}</th>
<th>{% trans 'Reachable' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %}
</div>
<div class="panel-body">
<table class="table">
<tbody>
<tr class="no-borders-tr">
<td width="50%">{% trans 'Test connective' %}:</td>
<td>
<span style="float: right">
<button type="button" class="btn btn-primary btn-xs btn-test-connective" style="width: 54px">{% trans 'Test' %}</button>
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
function initTable() {
var options = {
ele: $('#asset_list_table'),
buttons: [],
order: [],
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:asset-detail" pk=DEFAULT_PK %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 4, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}}],
ajax_url: '{% url "api-assets:asset-list" %}?admin_user_id={{ admin_user.id }}',
columns: [
{data: function(){return ""}}, {data: "hostname" }, {data: "ip" },
{data: "port" }, {data: "is_connective" }],
op_html: $('#actions').html()
};
jumpserver.initServerSideDataTable(options);
}
$(document).ready(function () {
initTable();
})
.on('click', '.btn-test-connective', function () {
var the_url = "{% url 'api-assets:admin-user-connective' pk=admin_user.id %}";
var success = function (data) {
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({
url: the_url,
method: 'GET',
success: success,
flash_message: false
});
})
</script>
{% endblock %}

View File

@@ -3,8 +3,8 @@
{% load static %}
{% load bootstrap3 %}
{% block custom_head_css_js %}
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
{% endblock %}
{% block content %}
@@ -13,7 +13,7 @@
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5>{% trans 'Create admin user' %}</h5>
<h5>{{ action }}</h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>

View File

@@ -15,20 +15,23 @@
<div class="panel-options">
<ul class="nav nav-tabs">
<li class="active">
<a href="" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
<a href="{% url 'assets:admin-user-detail' pk=admin_user.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
</li>
<li>
<a href="{% url 'assets:admin-user-assets' pk=admin_user.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Assets list' %} </a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'assets:admin-user-update' pk=admin_user.id %}"><i class="fa fa-edit"></i>Update</a>
<a class="btn btn-outline btn-default" href="{% url 'assets:admin-user-update' pk=admin_user.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-danger btn-delete-admin-user">
<i class="fa fa-edit"></i>Delete
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-7" style="padding-left: 0;">
<div class="col-sm-8" style="padding-left: 0;">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label"><b>{{ admin_user.name }}</b></span>
@@ -73,139 +76,28 @@
</table>
</div>
</div>
<div class="ibox float-e-margins">
<div class="ibox-title">
<span style="float: left">{% trans 'Asset list of ' %} <b>{{ admin_user.name }}</b> <span class="badge"> {{ paginator.count }}</span></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table table-hover" id="system_user_assets_table">
<thead>
<tr>
<th>{% trans 'Hostname' %}</th>
<th>{% trans 'IP' %}</th>
<th>{% trans 'Port' %}</th>
<th>{% trans 'Alive' %}</th>
<th>{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
{# {% for asset in page_obj %}#}
{# <tr>#}
{# <td>{{ asset.hostname }}</td>#}
{# <td>{{ asset.ip }}</td>#}
{# <td>{{ asset.port }}</td>#}
{# <td>Alive</td>#}
{# </tr>#}
{# {% endfor %}#}
</tbody>
</table>
{# <div class="row">#}
{# {% include '_pagination.html' %}#}
{# </div>#}
</div>
</div>
</div>
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %}
<i class="fa fa-info-circle"></i> {% trans 'Replace node assets admin user with this' %}
</div>
<div class="panel-body">
<table class="table">
<tbody>
<tr class="no-borders-tr">
<td width="50%">{% trans 'Get install script' %}:</td>
<td>
<span style="float: right">
<button type="button" class="btn btn-primary btn-xs" style="width: 54px">{% trans 'Get' %}</button>
</span>
</td>
</tr>
<tr>
<td width="50%">{% trans 'Retest asset connectivity' %}:</td>
<td>
<span style="float: right">
<button type="button" class="btn btn-primary btn-xs" style="width: 54px">{% trans 'Start' %}</button>
</span>
</td>
</tr>
<tr>
<td width="50%">{% trans 'Reset private key' %}:</td>
<td>
<span style="float: right">
<button type="button" class="btn btn-primary btn-xs" style="width: 54px">{% trans 'Reset' %}</button>
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Replace asset admin user with this' %}
</div>
<div class="panel-body">
<table class="table">
<table class="table group_edit" id="table-clusters">
<tbody>
<form>
<tr class="no-borders-tr">
<td colspan="2">
<select data-placeholder="{% trans 'Select asset' %}" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for asset in assets_remain %}
<option value="{{ asset.id }}">{{ asset.ip }}:{{ asset.port }}</option>
<tr>
<td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Select nodes' %}" id="nodes_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for node in nodes %}
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr class="no-borders-tr">
<td colspan="2">
<button type="button" class="btn btn-info btn-sm btn-replace-asset-admin_user">{% trans 'Replace' %}</button>
</td>
</tr>
</form>
</tbody>
</table>
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Replace asset admin user with this admin user' %}
</div>
<div class="panel-body">
<table class="table">
<tbody>
<form>
<tr class="no-borders-tr">
<td colspan="2">
<select data-placeholder="{% trans 'Select asset groups' %}" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for asset_group in asset_groups %}
<option value="{{ asset_group.id }}">{{ asset_group.name }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr class="no-borders-tr">
<td colspan="2">
<button type="button" class="btn btn-warning btn-sm btn-replace-asset_groups-admin_user">{% trans 'Replace' %}</button>
<tr>
<td colspan="2" class="no-borders">
<button type="button" class="btn btn-primary btn-sm" id="btn-change-admin-user">{% trans 'Confirm' %}</button>
</td>
</tr>
</form>
@@ -224,218 +116,56 @@
{% endblock %}
{% block custom_foot_js %}
<script>
Array.prototype.remove = function(val) {
var index = this.indexOf(val);
if (index > -1) {
this.splice(index, 1);
}
};
Array.prototype.unique = function(){
var res = [];
var json = {};
for(var i = 0; i < this.length; i++){
if(!json[this[i]]){
res.push(this[i]);
json[this[i]] = 1;
}
}
return res;
};
function objectRemove(obj, name, url, data) {
function doRemove() {
var body = data;
var success = function() {
swal('Remove!', "[ "+name+"]"+" has been deleted ", "success");
$(obj).parent().parent().remove();
};
var fail = function() {
swal("Failed", "Remove"+"[ "+name+" ]"+"failed", "error");
};
APIUpdateAttr({
url: url,
body: JSON.stringify(body),
method: 'PATCH',
success: success,
error: fail
function replaceNodeAssetsAdminUser(nodes) {
var the_url = "{% url 'api-assets:replace-nodes-admin-user' pk=admin_user.id %}";
var body = {
nodes: nodes
};
var success = function(data) {
// remove all the selected groups from select > option and rendered ul element;
$('.select2-selection__rendered').empty();
$('#nodes_selected').val('');
$.map(jumpserver.nodes_selected, function(value, index) {
$('#opt_' + index).remove();
});
}
swal({
title: 'Are you sure remove ?',
text: " [" + name + "] ",
type: "warning",
showCancelButton: true,
cancelButtonText: 'Cancel',
confirmButtonColor: "#DD6B55",
confirmButtonText: 'Confirm',
closeOnConfirm: false
}, function () {
doRemove()
// clear jumpserver.groups_selected
jumpserver.nodes_selected = {};
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
success: success
});
}
jumpserver.assets_selected = {};
jumpserver.asset_groups_selected = {};
jumpserver.nodes_selected = {};
$(document).ready(function () {
$('.select2').select2()
.on("select2:select", function (evt) {
var data = evt.params.data;
jumpserver.assets_selected[data.id] = data.text;
jumpserver.asset_groups_selected[data.id] = data.text;
})
.on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.assets_selected[data.id];
delete jumpserver.asset_groups_selected[data.id]
});
var options = {
ele: $('#system_user_assets_table'),
buttons: [],
order: [],
columnDefs: [
{targets: 0, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:asset-detail" pk=99991937 %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
$(td).html(detail_btn.replace('99991937', rowData.id));
}},
{targets: 3, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 4, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:asset-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', rowData.id);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_remove" data-aid="99991937">{% trans "Remove" %}</a>'.replace('99991937', rowData.id);
$(td).html(update_btn + del_btn)
}}
],
ajax_url: '{% url "api-assets:asset-list" %}?admin_user_id={{ admin_user.id }}',
columns: [{data: "hostname" }, {data: "ip" }, {data: "port" },
{data: "is_active" }, {data: "id"}],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
function adminUserDelete(name, url) {
function doDelete() {
var body = {};
var success = function() {
swal('Deleted!', "[ "+name+"]"+" has been deleted ", "success");
window.location.href="{% url 'assets:idc-list' %}";
};
var fail = function() {
swal("Failed", "Delete"+"[ "+name+" ]"+"failed", "error");
};
APIUpdateAttr({
url: url,
body: JSON.stringify(body),
method: 'DELETE',
success: success,
error: fail
});
}
swal({
title: 'Are you sure delete ?',
text: " [" + name + "] ",
type: "warning",
showCancelButton: true,
cancelButtonText: 'Cancel',
confirmButtonColor: "#DD6B55",
confirmButtonText: 'Confirm',
closeOnConfirm: false
}, function () {
doDelete()
});
}
$('.select2').select2()
.on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.nodes_selected[data.id] = data.text;
}).on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.nodes_selected[data.id]
});
})
.on('click', '.btn-replace-asset-admin_user', function () {
if (Object.keys(jumpserver.assets_selected).length === 0) {
return false;
}
jumpserver.asset_groups_selected = {};
var $data_table = $("#system_user_assets_table").DataTable();
var assets = [];
$.map(jumpserver.assets_selected, function(value, index) {
assets.push(parseInt(index));
});
assets.unique();
var data = [];
var admin_user_id = {{ admin_user.id }};
var the_url = '{% url "api-assets:asset-list" %}';
for (var i=0; i<assets.length; i++) {
data.push({"id": assets[i], "admin_user": admin_user_id});
}
APIUpdateAttr({
url: the_url,
body: JSON.stringify(data),
method: 'PATCH'
});
$data_table.ajax.reload();
})
.on('click', '.btn-replace-asset_groups-admin_user', function () {
if (Object.keys(jumpserver.asset_groups_selected).length === 0) {
return false;
}
jumpserver.assets_selected = {};
var $data_table = $("#system_user_assets_table").DataTable();
var asset_groups = [];
var assets = [];
var data = [];
var the_url = '{% url "api-assets:asset-list" %}';
$.map(jumpserver.asset_groups_selected, function(value, index) {
asset_groups.push(parseInt(index));
});
$.ajax({
url: '{% url "api-assets:asset-group-list" %}?id__in=['+asset_groups.join(',')+']',
method: 'GET',
dataType: 'json',
success: function (result) {
for (var i=0; i<result.length; i++) {
for (var j=0; j<result[i]['assets'].length; j++) {
assets.push(result[i]['assets'][j])
}
}
for (var z=0; z<assets.length; z++) {
data.push({"id":assets[z], "admin_user":{{admin_user.id}} });
}
APIUpdateAttr({
url: the_url,
body: JSON.stringify(data),
method: 'PATCH'
});
$data_table.ajax.reload();
}
});
})
.on('click', '.btn_asset_remove', function () {
var $this = $(this);
var the_url = "{% url 'api-assets:admin-user-detail' pk=admin_user.id %}";
var name = $(this).closest("tr").find(":nth-child(1) > a").html();
var assets = [];
var delete_asset_id = $(this).data('aid');
$.ajax({
url: the_url,
method: 'GET',
dataType: 'json',
success: function (result) {
for (var i=0; i<result['assets'].length; i++) {
assets.push(result['assets'][i])
}
assets.remove(delete_asset_id);
var data = {"assets": assets};
objectRemove($this, name, the_url, data);
}
})
}).on('click', '.btn-delete-admin-user', function () {
.on('click', '.btn-delete-admin-user', function () {
var $this = $(this);
var name = "{{ admin_user.name }}";
var uid = "{{ admin_user.id }}";
var the_url = '{% url "api-assets:admin-user-detail" pk=99991937 %}'.replace('99991937', uid);
var the_url = '{% url "api-assets:admin-user-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
var redirect_url = "{% url 'assets:admin-user-list' %}";
objectDelete($this, name, the_url, redirect_url);
})
.on('click', '#btn-change-admin-user', function () {
if (Object.keys(jumpserver.nodes_selected).length === 0) {
return false;
}
var nodes = [];
$.map(jumpserver.nodes_selected, function(value, index) {
nodes.push(index);
});
replaceNodeAssetsAdminUser(nodes);
})
</script>
{% endblock %}

View File

@@ -2,8 +2,19 @@
{% load i18n static %}
{% block table_search %}
{% endblock %}
{% block help_message %}
<div class="alert alert-info help-message">
{# 管理用户是资产被控服务器上的root或拥有 NOPASSWD: ALL sudo权限的用户Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。#}
{# Windows或其它硬件可以随意设置一个#}
{% trans 'Admin users are asset (charged server) on the root, or have NOPASSWD: ALL sudo permissions users, '%}
{% trans 'Jumpserver users of the system using the user to `push system user`, `get assets hardware information`, etc. '%}
{% trans 'You can set any one for Windows or other hardware.' %}
</div>
{% endblock %}
{% block table_container %}
<div class="uc pull-left m-l-5 m-r-5">
<div class="uc pull-left m-r-5">
<a href="{% url "assets:admin-user-create" %}" class="btn btn-sm btn-primary"> {% trans "Create admin user" %} </a>
</div>
<table class="table table-striped table-bordered table-hover " id="admin_user_list_table" >
@@ -14,7 +25,10 @@
</th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Username' %}</th>
<th class="text-center">{% trans 'Asset num' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'Reachable' %}</th>
<th class="text-center">{% trans 'Unreachable' %}</th>
<th class="text-center">{% trans 'Ratio' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
@@ -31,23 +45,53 @@ $(document).ready(function(){
ele: $('#admin_user_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:admin-user-detail" pk=99991937 %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('99991937', rowData.id));
}},
var detail_btn = '<a href="{% url "assets:admin-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 4, createdCell: function (td, cellData) {
var innerHtml = cellData.length > 8 ? cellData.substring(0, 24) + '...': cellData;
$(td).html('<a href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</a>');
}},
{targets: 5, createdCell: function (td, cellData, rowData) {
{# var script_btn = '<a href="{% url "assets:admin-user-update" pk=99991937 %}" class="btn btn-xs btn-primary">{% trans "Script" %}</a>'.replace('99991937', cellData);#}
var update_btn = '<a href="{% url "assets:admin-user-update" pk=99991937 %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData);
{# $(td).html(script_btn + update_btn + del_btn)#}
var innerHtml = "";
if (cellData !== 0) {
innerHtml = "<span class='text-navy'>" + cellData + "</span>";
} else {
innerHtml = "<span>" + cellData + "</span>";
}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData +'">' + innerHtml + '</span>');
}},
{targets: 5, createdCell: function (td, cellData) {
var innerHtml = "";
if (cellData !== 0) {
innerHtml = "<span class='text-danger'>" + cellData + "</span>";
} else {
innerHtml = "<span>" + cellData + "</span>";
}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}},
{targets: 6, createdCell: function (td, cellData, rowData) {
var val = 0;
var innerHtml = "";
var total = rowData.assets_amount;
var reachable = rowData.reachable_amount;
if (total !== 0) {
val = reachable/total * 100;
}
if (val === 100) {
innerHtml = "<span class='text-navy'>" + val + "% </span>";
} else {
var num = new Number(val);
innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>";
}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}},
{targets: 8, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:admin-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
}}],
ajax_url: '{% url "api-assets:admin-user-list" %}',
columns: [{data: function(){return ""}}, {data: "name" }, {data: "username" }, {data: "assets_amount" },
{data: "comment" }, {data: "id" }],
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" }]
};
jumpserver.initDataTable(options);
})
@@ -57,7 +101,7 @@ $(document).ready(function(){
var $data_table = $("#admin_user_list_table").DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var the_url = '{% url "api-assets:admin-user-detail" pk=99991937 %}'.replace('99991937', uid);
var the_url = '{% url "api-assets:admin-user-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
objectDelete($this, name, the_url);
setTimeout( function () {
$data_table.ajax.reload();
@@ -66,6 +110,3 @@ $(document).ready(function(){
});
</script>
{% endblock %}

View File

@@ -5,9 +5,9 @@
{% block form %}
<div class="ydxbd" id="formlists" style="display: block;">
<p id="tags_p" class="mgl-5 c02">选择需要修改属性</p>
<p id="tags_p" class="mgl-5 c02">{% trans 'Select properties that need to be modified' %}</p>
<div class="tagBtnList">
<a class="label label-primary" id="change_all" value="1">全选</a>
<a class="label label-primary" id="change_all" value="1">{% trans 'Select all' %}</a>
{% for field in form %}
{% if field.name != 'assets' %}
<a data-id="{{ field.id_for_label }}" class="label label-default label-primary field-tag" value="1">{{ field.label }}</a>

View File

@@ -2,6 +2,8 @@
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% load asset_tags %}
{% load common_tags %}
{% block form %}
<form action="" method="post" class="form-horizontal">
@@ -14,26 +16,51 @@
<h3>{% trans 'Basic' %}</h3>
{% bootstrap_field form.hostname layout="horizontal" %}
{% bootstrap_field form.ip layout="horizontal" %}
{% bootstrap_field form.public_ip layout="horizontal" %}
{% bootstrap_field form.protocol layout="horizontal" %}
{% bootstrap_field form.port layout="horizontal" %}
{% bootstrap_field form.type layout="horizontal" %}
{% bootstrap_field form.env layout="horizontal" %}
{% bootstrap_field form.platform layout="horizontal" %}
{% bootstrap_field form.public_ip layout="horizontal" %}
{% bootstrap_field form.domain layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Group' %}</h3>
{% bootstrap_field form.idc layout="horizontal" %}
{% bootstrap_field form.groups layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Asset user' %}</h3>
<h3>{% trans 'Auth' %}</h3>
{% bootstrap_field form.admin_user layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Node' %}</h3>
{% bootstrap_field form.nodes layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Labels' %}</h3>
<div class="form-group {% if form.errors.labels %} has-error {% endif %}">
<label for="{{ form.labels.id_for_label }}" class="col-md-2 control-label">{% trans 'Label' %}</label>
<div class="col-md-9">
<select name="labels" class="select2 labels" data-placeholder="{% trans 'Label' %}" style="width: 100%" multiple="" tabindex="4" id="{{ form.labels.id_for_label }}">
{% for name, labels in form.labels.field.queryset|group_labels %}
<optgroup label="{{ name }}">
{% for label in labels %}
{% if label in form.labels.initial %}
<option value="{{ label.id }}" selected>{{ label.value }}</option>
{% else %}
<option value="{{ label.id }}">{{ label.value }}</option>
{% endif %}
{% endfor %}
</optgroup>
{% endfor %}
</select>
{% if form.errors.labels %}
{% for e in form.errors.labels %}
<div class="help-block">{{ e }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="hr-line-dashed"></div>
<h3>{% trans 'Other' %}</h3>
{% bootstrap_field form.comment layout="horizontal" %}
{% bootstrap_field form.is_active layout="horizontal" %}
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
@@ -45,14 +72,31 @@
{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function () {
$('.select2').select2();
$("#id_tags").select2({
tags: true,
maximumSelectionLength: 8 //最多能够选择的个数
//closeOnSelect: false
});
})
</script>
<script>
function format(item) {
var group = item.element.parentElement.label;
return group + ':' + item.text;
}
$(document).ready(function () {
$('.select2').select2({
allowClear: true
});
$(".labels").select2({
allowClear: true,
templateSelection: format
});
$("#id_protocol").change(function (){
var protocol = $("#id_protocol option:selected").text();
var port = 22;
if(protocol === 'rdp'){
port = 3389;
}
if(protocol === 'telnet (beta)'){
port = 23;
}
$("#id_port").val(port);
});
})
</script>
{% endblock %}

View File

@@ -21,11 +21,11 @@
</li>
{% if user.is_superuser %}
<li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'assets:asset-update' pk=asset.id %}"><i class="fa fa-edit"></i>Update</a>
<a class="btn btn-outline btn-default" href="{% url 'assets:asset-update' pk=asset.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-danger btn-delete-asset">
<i class="fa fa-edit"></i>Delete
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
</a>
</li>
{% endif %}
@@ -63,7 +63,7 @@
</tr>
<tr>
<td>{% trans 'Public IP' %}:</td>
<td><b>{{ asset.public_ip }}</b></td>
<td><b>{{ asset.public_ip|default:"" }}</b></td>
</tr>
<tr>
<td>{% trans 'Port' %}:</td>
@@ -71,79 +71,47 @@
</tr>
<tr>
<td>{% trans 'Admin user' %}:</td>
{% if asset.admin_user %}
<td><b>{{ asset.admin_user.name }}</b></td>
{% else %}
<td><b>None</b></td>
{% endif %}
</tr>
<tr>
<td>{% trans 'Remote card IP' %}:</td>
<td><b>{{ asset.remote_card_ip }}</b></td>
</tr>
<tr>
<td>{% trans 'IDC' %}:</td>
<td><b>{{ asset.idc.name }}</b></td>
</tr>
<tr>
<td>{% trans 'Cabinet number' %}:</td>
<td><b>{{ asset.cabinet_no }}</b></td>
</tr>
<tr>
<td>{% trans 'Cabinet position' %}:</td>
<td><b>{{ asset.cabinet_pos }}</b></td>
<td><b>{{ asset.admin_user }}</b></td>
</tr>
<tr>
<td>{% trans 'Vendor' %}:</td>
<td><b>{{ asset.vendor }}</b></td>
<td><b>{{ asset.vendor|default:"" }}</b></td>
</tr>
<tr>
<td>{% trans 'Model' %}:</td>
<td><b>{{ asset.model }}</b></td>
<td><b>{{ asset.model|default:"" }}</b></td>
</tr>
<tr>
<td>{% trans 'CPU' %}:</td>
<td><b>{{ asset.cpu_model }} {{ asset.cpu_count }}*{{ asset.cpu_cores }}</b></td>
<td><b>{{ asset.cpu_model|default:"" }} {{ asset.cpu_count|default:"" }}*{{ asset.cpu_cores|default:"" }}</b></td>
</tr>
<tr>
<td>{% trans 'Memory' %}:</td>
<td><b>{{ asset.memory }}</b></td>
<td><b>{{ asset.memory|default:"" }}</b></td>
</tr>
<tr>
<td>{% trans 'Disk' %}:</td>
<td><b>{{ asset.disk_total }}</b></td>
<td><b>{{ asset.disk_total|default:"" }}</b></td>
</tr>
<tr>
<td>{% trans 'Platform' %}:</td>
<td><b>{{ asset.platform }}</b></td>
<td><b>{{ asset.platform|default:"" }}</b></td>
</tr>
<tr>
<td>{% trans 'OS' %}:</td>
<td><b>{{ asset.os }} {{ asset.os_version }} {{ asset.os_arch }}</b></td>
</tr>
<tr>
<td>{% trans 'Asset status' %}:</td>
<td><b>{{ asset.status }}</b></td>
<td><b>{{ asset.os|default:"" }} {{ asset.os_version|default:"" }} {{ asset.os_arch|default:"" }}</b></td>
</tr>
<tr>
<td>{% trans 'Is active' %}:</td>
<td><b>{{ asset.is_active }}</b></td>
</tr>
<tr>
<td>{% trans 'Asset type' %}:</td>
<td><b>{{ asset.type }}</b></td>
</tr>
<tr>
<td>{% trans 'Asset environment' %}:</td>
<td><b>{{ asset.env }}</b></td>
<td><b>{{ asset.is_active|yesno:"Yes,No" }}</b></td>
</tr>
<tr>
<td>{% trans 'Serial number' %}:</td>
<td><b>{{ asset.sn }}</b></td>
<td><b>{{ asset.sn|default:"" }}</b></td>
</tr>
<tr>
<td>{% trans 'Asset number' %}:</td>
<td><b>{{ asset.number }}</b></td>
<td><b>{{ asset.number|default:"" }}</b></td>
</tr>
<tr>
<td>{% trans 'Created by' %}:</td>
@@ -162,7 +130,7 @@
</div>
</div>
</div>
{% if user.is_superuser %}
{% if user.is_superuser or user.is_org_admin %}
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary">
<div class="panel-heading">
@@ -173,18 +141,21 @@
<tbody>
<tr class="no-borders-tr">
<td width="50%">{% trans 'Active' %}:</td>
<td><span class="pull-right">
<div class="switch">
<div class="onoffswitch">
<input type="checkbox" {% if asset.is_active %} checked {% endif %} class="onoffswitch-checkbox" id="is_active">
<label class="onoffswitch-label" for="is_active">
<span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
</label>
</div>
</div>
</span></td>
<td>
<span class="pull-right">
<div class="switch">
<div class="onoffswitch">
<input type="checkbox" {% if asset.is_active %} checked {% endif %} class="onoffswitch-checkbox" id="is_active">
<label class="onoffswitch-label" for="is_active">
<span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
</label>
</div>
</div>
</span>
</td>
</tr>
{% if asset.is_unixlike %}
<tr>
<td>{% trans 'Refresh hardware' %}:</td>
<td>
@@ -194,13 +165,14 @@
</td>
</tr>
<tr>
<td>{% trans 'Test admin user' %}:</td>
<td>{% trans 'Test connective' %}:</td>
<td>
<span class="pull-right">
<button type="button" class="btn btn-primary btn-xs" id="btn_test_admin_user" style="width: 54px;">{% trans 'Test' %}</button>
<button type="button" class="btn btn-primary btn-xs" id="btn-test-is-alive" style="width: 54px">{% trans 'Test' %}</button>
</span>
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
@@ -208,7 +180,7 @@
<div class="panel panel-info">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Asset groups' %}
<i class="fa fa-info-circle"></i> {% trans 'Nodes' %}
</div>
<div class="panel-body">
<table class="table group_edit" id="add-asset2group">
@@ -216,25 +188,25 @@
<form>
<tr>
<td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Join asset groups' %}" id="groups_selected" class="select2 groups" style="width: 100%" multiple="" tabindex="4">
{% for asset_group in asset_groups_remain %}
<option value="{{ asset_group.id }}" id="opt_{{ asset_group.id }}" >{{ asset_group.name }}</option>
<select data-placeholder="{% trans 'Nodes' %}" id="groups_selected" class="select2 groups" style="width: 100%" multiple="" tabindex="4">
{% for node in nodes_remain %}
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td colspan="2" class="no-borders">
<button type="button" class="btn btn-info btn-sm" id="btn_add_user_group">{% trans 'Confirm' %}</button>
<button type="button" class="btn btn-info btn-sm" id="btn-update-nodes">{% trans 'Confirm' %}</button>
</td>
</tr>
</form>
{% for asset_group in asset_groups %}
{% for node in asset.nodes.all %}
<tr>
<td ><b class="bdg_group" data-gid={{ asset_group.id }}>{{ asset_group.name }}</b></td>
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node }}</b></td>
<td>
<button class="btn btn-danger pull-right btn-xs btn_leave_group" type="button"><i class="fa fa-minus"></i></button>
<button class="btn btn-danger pull-right btn-xs btn-leave-node" type="button"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
@@ -242,42 +214,20 @@
</table>
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Push system users' %}
<i class="fa fa-info-circle"></i> {% trans 'Labels' %}
</div>
<div class="panel-body">
<table class="table group_edit" id="add-asset2systemuser">
<tbody>
<form>
<tr class="no-borders-tr">
<td colspan="2">
<select data-placeholder="{% trans 'Select system users' %}" class="select2 system-user" style="width: 100%" multiple="" tabindex="4">
{% for system_user in system_users_all %}
<option value="{{ system_user.id }}" id="opt_{{ system_user.id }}">{{ system_user.name }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr class="no-borders-tr">
<td colspan="2">
<button type="button" class="btn btn-warning btn-sm btn-system-user">{% trans 'Confirm' %}</button>
</td>
</tr>
</form>
{% for system_user in system_users %}
<tr>
<td ><b class="bdg_group" data-sid={{ system_user.id }}>{{ system_user.name }}</b></td>
<td>
<button class="btn btn-danger btn-xs pull-right btn_leave_system" type="button" style="float: right;"><i class="fa fa-minus"></i></button>
</td>
</tr>
<ul class="tag-list" style="padding: 0">
{% for label in asset.labels.all %}
<li ><a href=""><i class="fa fa-tag"></i> {{ label.name }}:{{ label.value }}</a></li>
{% endfor %}
</tbody>
</table>
</ul>
</div>
</div>
{% endif %}
{% endif %}
</div>
</div>
</div>
@@ -287,57 +237,28 @@
{% endblock %}
{% block custom_foot_js %}
<script>
jumpserver.groups_selected = {};
jumpserver.system_user_selected = {};
function updateAssetGroups(groups) {
var the_url = "{% url 'api-assets:asset-update-group' pk=asset.id %}";
jumpserver.nodes_selected = {};
function updateAssetNodes(nodes) {
var the_url = "{% url 'api-assets:asset-detail' pk=asset.id %}";
var body = {
groups: Object.assign([], groups)
nodes: Object.assign([], nodes)
};
var success = function(data) {
// remove all the selected groups from select > option and rendered ul element;
$('.select2-selection__rendered').empty();
$('#groups_selected').val('');
$.map(jumpserver.groups_selected, function(group_name, index) {
$.map(jumpserver.nodes_selected, function(group_name, index) {
$('#opt_' + index).remove();
// change tr html of user groups.
$('#add-asset2group tbody').append(
'<tr>' +
'<td><b class="bdg_group" data-gid="' + index + '">' + group_name + '</b></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn_leave_group" type="button"><i class="fa fa-minus"></i></button></td>' +
'<td><b class="bdg_node" data-gid="' + index + '">' + group_name + '</b></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn-leave-node" type="button"><i class="fa fa-minus"></i></button></td>' +
'</tr>'
)
});
// clear jumpserver.groups_selected
jumpserver.groups_selected = {};
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
success: success
});
}
function updateAssetSystemUser(system_users) {
var the_url = "{% url 'api-assets:asset-update-system-users' pk=asset.id %}";
var body = {
system_users: Object.assign([], system_users)
};
var success = function(data) {
$('.select2-selection__rendered').empty();
$('#groups_selected').val('');
$.map(jumpserver.system_user_selected, function(name, index) {
$('#opt_' + index).remove();
$('#add-asset2systemuser tbody').append(
'<tr>' +
'<td><b class="bdg_group" data-sid="' + index + '">' + name + '</b></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn_leave_system" type="button"><i class="fa fa-minus"></i></button></td>' +
'</tr>'
)
});
// clear jumpserver.groups_selected
jumpserver.system_user_selected = {};
jumpserver.nodes_selected = {};
};
APIUpdateAttr({
url: the_url,
@@ -348,32 +269,28 @@ function updateAssetSystemUser(system_users) {
function refreshAssetHardware() {
var the_url = "{% url 'api-assets:asset-refresh' pk=asset.id %}";
var success = function (data) {
location.reload();
var success = function(data) {
console.log(data);
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
};
APIUpdateAttr({
url: the_url,
success: success,
method: 'GET'
})
});
}
$(document).ready(function () {
$('.select2.groups').select2().on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.groups_selected[data.id] = data.text;
jumpserver.nodes_selected[data.id] = data.text;
}).on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.groups_selected[data.id]
delete jumpserver.nodes_selected[data.id]
});
$('.select2.system-user').select2().on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.system_user_selected[data.id] = data.text;
}).on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.system_user_selected[data.id]
})
}).on('click', '#is_active', function () {
var the_url = '{% url "api-assets:asset-detail" pk=asset.id %}';
var checked = $(this).prop('checked');
@@ -387,85 +304,61 @@ $(document).ready(function () {
body: JSON.stringify(body),
success_message: success
});
if (status == "False") {
$(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").html('True');
if (status === "False") {
$(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").html('True');
}else{
$(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").html('False');
$(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").html('False');
}
}).on('click', '#btn_add_user_group', function () {
if (Object.keys(jumpserver.groups_selected).length === 0) {
}).on('click', '#btn-update-nodes', function () {
if (Object.keys(jumpserver.nodes_selected).length === 0) {
return false;
}
var groups = $('.bdg_group').map(function() {
var nodes = $('.bdg_node').map(function() {
return $(this).data('gid');
}).get();
$.map(jumpserver.groups_selected, function(value, index) {
groups.push(parseInt(index));
$.map(jumpserver.nodes_selected, function(value, index) {
nodes.push(index);
$('#opt_' + index).remove();
});
updateAssetGroups(groups)
}).on('click', '.btn_leave_group', function() {
updateAssetNodes(nodes)
}).on('click', '.btn-leave-node', function() {
var $this = $(this);
var $tr = $this.closest('tr');
var $badge = $tr.find('.bdg_group');
var $badge = $tr.find('.bdg_node');
var gid = $badge.data('gid');
var group_name = $badge.html() || $badge.text();
$('#groups_selected').append(
'<option value="' + gid + '" id="opt_' + gid + '">' + group_name + '</option>'
);
$tr.remove();
var groups = $('.bdg_group').map(function () {
var groups = $('.bdg_node').map(function () {
return $(this).data('gid');
}).get();
updateAssetGroups(groups)
}).on('click', '.btn-system-user', function () {
if (Object.keys(jumpserver.system_user_selected).length === 0) {
return false;
}
var system_users = $('.bdg_group').map(function() {
return $(this).data('sid');
}).get();
$.map(jumpserver.system_user_selected, function(value, index) {
system_users.push(parseInt(index));
$('#opt_' + index).remove();
});
updateAssetSystemUser(system_users)
}).on('click', '.btn_leave_system', function () {
var $this = $(this);
var $tr = $this.closest('tr');
var $badge = $tr.find('.bdg_group');
var sid = $badge.data('sid');
var name = $badge.html() || $badge.text();
$('#groups_selected').append(
'<option value="' + sid + '" id="opt_' + sid + '">' + name + '</option>'
);
$tr.remove();
var system_users = $('.bdg_group').map(function () {
return $(this).data('sid');
}).get();
updateAssetSystemUser(system_users)
updateAssetNodes(groups)
}).on('click', '.btn-delete-asset', function () {
var $this = $(this);
var name = "{{ asset.hostname }}";
var uid = "{{ asset.id }}";
var the_url = '{% url "api-assets:asset-detail" pk=99991937 %}'.replace('99991937', uid);
var the_url = '{% url "api-assets:asset-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
var redirect_url = "{% url 'assets:asset-list' %}";
objectDelete($this, name, the_url, redirect_url);
}).on('click', '#btn_refresh_asset', function () {
alert('请等待几秒, 等待完成');
refreshAssetHardware()
}).on('click', '#btn_test_admin_user', function () {
$.ajax({
url: '{% url "api-assets:asset-admin-user-test" pk=asset.id %}'
}).done(function (data, textStatue, jqXHR) {
toastr.success('Success')
}).fail(function (data, textStaue, errorThrown) {
toastr.error('Error')
})
})
}).on('click', '#btn-test-is-alive', function () {
var the_url = "{% url 'api-assets:asset-alive-test' pk=asset.id %}";
var success = function(data) {
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
};
APIUpdateAttr({
url: the_url,
method: 'GET',
success: success
});
})
</script>
{% endblock %}

View File

@@ -1,121 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% load bootstrap3 %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-10">
<div class="ibox float-e-margins">
<div id="ibox-content" class="ibox-title">
<h5> {{ action }}</h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<div class="panel blank-panel">
<div class="panel-body">
<div class="tab-content">
<div id="tab-1" class="ibox float-e-margins tab-pane active"></div>
<form id="groupForm" method="post" class="form-horizontal">
{% csrf_token %}
<h3 class="widget-head-color-box">资产组信息</h3>
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.comment layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3 class="widget-head-color-box">用户选择的资产</h3>
<div class="form-group">
<label class="col-sm-2 control-label" id="asset_on_count">已选({{ assets_count }}</label>
<div class="col-sm-9" id="asset_sed">
<div class="form-asset-on" id="add_asset">
<p id="asset_on_p">
{% for asset in assets_on_list %}
<button name='asset_hostname' title='{{ asset.ip }}' type='button' class='btn btn-default btn-xs'>{{ asset.hostname }}</button>
{% endfor %}
</p>
</div>
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-5">
<button class="btn btn-white" type="reset"> 重置 </button>
<button class="btn btn-primary" type="submit"> 提交 </button>
<div id='box2'> </div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 模态框Modal -->
<div class="modal fade" id="modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content" id="box">
<!--此部分为主体内容,将远程加载进来-->
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script type="text/javascript">
$(document).ready(function () {
$('.select2').select2();
$('.select2-system-user').select2();
});
$('#add_asset').on('click',function(){
$('#modal').modal('show');
});
$('#modal').modal({
show: false,
backdrop: 'static',
keyboard: 'false',
remote:"{% url 'assets:asset-modal-list' %}?group_id={{ group_id }}"
});
$('#modal').on('show.bs.modal',function(){
//alert('当调用show方法时立即触发')
});
$('#modal').on('shown.bs.modal',function(){
//alert('当弹窗完全加载完后,再触发;')
});
$('#modal').on('hide.bs.modal',function(){
//alert('当关闭时,立即触发;')
});
$('#modal').on('hidden.bs.modal',function(){
//alert('当关完全关闭后,再触发;')
});
$('#modal').on('loaded.bs.modal',function(){
//alert('当远程数据加载完毕后,再触发;')
});
</script>
{% endblock %}

View File

@@ -1,271 +0,0 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li class="active"><a href="" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a></li>
<li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'assets:asset-group-update' pk=asset_group.id %}"><i class="fa fa-edit"></i>Update</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-7" style="padding-left: 0">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span style="float: left"></span>{% trans 'Asset list of ' %} <b>{{ asset_group.name }}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table table-hover " id="asset_list_table" >
<thead>
<tr>
<th>{% trans 'Hostname' %}</th>
<th>{% trans 'IP' %}</th>
<th>{% trans 'Port' %}</th>
<th>{% trans 'Type' %}</th>
<th>{% trans 'Alive' %}</th>
<th>{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Push system users' %}
</div>
<div class="panel-body">
<table class="table">
<tbody>
<form>
<tr class="no-borders-tr">
<td colspan="2">
<select data-placeholder="{% trans 'Select system users' %}" class="select2 system-user-select" style="width: 100%" multiple="" tabindex="4">
{% for system_user in system_users %}
<option value="{{ system_user.id }}"> {{ system_user.name }} </option>
{% endfor %}
</select>
</td>
</tr>
<tr class="no-borders-tr">
<td colspan="2">
<button type="button" class="btn btn-primary btn-sm btn-push-system-user">{% trans 'Push' %}</button>
</td>
</tr>
</form>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{# </div>#}
{% endblock %}
{% block custom_foot_js %}
<script>
jumpserver.assets_selected = {};
function updateGroupAssets(assets) {
var the_url = "{}";
var body = {
assets: Object.assign([], assets)
};
var $data_table = $("#asset_list_table").DataTable();
var success = function(data) {
$('.select2-selection__rendered').empty();
$.map(jumpserver.assets_selected, function(asset_ip, index) {
$('#opt_' + index).remove();
$data_table.ajax.reload();
});
jumpserver.groups_selected = {};
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
method: 'PUT',
success: success
});
}
function leaveGroup(obj, name, url, data) {
function doDelete() {
var body = data;
var success = function() {
swal('Deleted!', "[ "+name+"]"+" has been deleted ", "success");
$(obj).parent().parent().remove();
};
var fail = function() {
swal("Failed", "Delete"+"[ "+name+" ]"+"failed", "error");
};
APIUpdateAttr({
url: url,
body: JSON.stringify(body),
method: 'PATCH',
success: success,
error: fail
});
}
swal({
title: 'Are you sure delete ?',
text: " [" + name + "] ",
type: "warning",
showCancelButton: true,
cancelButtonText: 'Cancel',
confirmButtonColor: "#DD6B55",
confirmButtonText: 'Confirm',
closeOnConfirm: false
}, function () {
doDelete()
});
}
function pushSystemUser(sysUserID) {
var the_url = "{% url 'api-assets:asset-group-push-system-user' pk=asset_group.id %}";
var body = {
system_user: sysUserID
};
var success = function(data) {
var url = "{% url 'ops:task-detail' pk=234234234 %}".replace("234234234", data);
setTimeout(function () {
location.href = url
}, 1000);
};
APIUpdateAttr({
url: the_url,
method: 'PATCH',
body: JSON.stringify(body),
success: success
});
}
Array.prototype.remove = function(val) {
var index = this.indexOf(val);
if (index > -1) {
this.splice(index, 1);
}
};
Array.prototype.unique = function(){
var res = [];
var json = {};
for(var i = 0; i < this.length; i++){
if(!json[this[i]]){
res.push(this[i]);
json[this[i]] = 1;
}
}
return res;
};
$(document).ready(function () {
$('.select2').select2();
$('.select2.asset-select').select2()
.on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.assets_selected[data.id] = data.text;
console.log(jumpserver.assets_selected)
})
.on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.assets_selected[data.id]
});
var options = {
ele: $('#asset_list_table'),
buttons: [],
order: [],
columnDefs: [
{targets: 0, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:asset-detail" pk=99991937 %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
$(td).html(detail_btn.replace('99991937', rowData.id));
}},
{targets: 4, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 5, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:asset-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', rowData.id);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-aid="99991937">{% trans "Remove" %}</a>'.replace('99991937', rowData.id);
$(td).html(update_btn + del_btn)
}}
],
ajax_url: '{% url "api-assets:asset-list" %}?asset_group_id={{ asset_group.id }}',
columns: [{data: "hostname" }, {data: "ip" }, {data: "port" },
{data: "type" }, {data: "is_active" }, {data: "id"}],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
})
.on('click', ".btn-asset-group-add-asset", function () {
if (Object.keys(jumpserver.assets_selected).length === 0) {
return false;
}
updateGroupAssets(jumpserver.assets_selected);
})
.on('click', '.btn-push-system-user', function () {
var data = $('.system-user-select').select2();
var system_id = data.val()[0];
if (!system_id) {
return false
}
pushSystemUser(system_id)
})
.on('click', '.btn_asset_delete', function () {
var $this = $(this);
var the_url = "{% url 'api-assets:asset-groups-update' pk=asset_group.id %}";
var name = $(this).closest("tr").find(":nth-child(1) > a").html();
var assets = [];
$('#asset_list_table > tbody > tr').map(function () {
assets.push(parseInt($(this).closest("tr").find(":nth-child(1) > a").attr("data-aid")))
});
var delete_asset_id = $(this).data('aid');
assets.remove(delete_asset_id);
var data = {"assets": assets};
leaveGroup($this, name, the_url, data);
})
</script>
{% endblock %}

View File

@@ -1,183 +0,0 @@
{% extends '_base_list.html' %}
{% load i18n static %}
{% block table_search %}
{% endblock %}
{% block table_container %}
<div class="uc pull-left m-l-5 m-r-5">
<a href="{% url "assets:asset-group-create" %}" class="btn btn-sm btn-primary"> {% trans "Create asset group" %} </a>
</div>
<table class="table table-striped table-bordered table-hover " id="asset_groups_list_table" >
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div id="actions" class="hide">
<div class="input-group">
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
<option value="delete">{% trans 'Delete selected' %}</option>
<option value="update">{% trans 'Update selected' %}</option>
</select>
<div class="input-group-btn pull-left" style="padding-left: 5px;">
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
{% trans 'Submit' %}
</button>
</div>
</div>
</div>
{% include 'assets/_asset_group_bulk_update_modal.html' %}
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function(){
var options = {
ele: $('#asset_groups_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:asset-group-detail" pk=99991937 %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('99991937', rowData.id));
}},
{targets: 3, createdCell: function (td, cellData) {
var innerHtml = cellData.length > 30 ? cellData.substring(0, 30) + '...': cellData;
$(td).html('<a href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</a>');
}},
{targets: 4, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:asset-group-update" pk=99991937 %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_group_delete" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData);
$(td).html(update_btn + del_btn)
}}],
ajax_url: '{% url "api-assets:asset-group-list" %}',
columns: [{data: "id"}, {data: "name" }, {data: "assets_amount" }, {data: "comment" }, {data: "id"}],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
})
.on('click', '.btn_asset_group_delete', function () {
var $this = $(this);
var $data_table = $('#asset_groups_list_table').DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var the_url = '{% url "api-assets:asset-group-detail" pk=99991937 %}'.replace('99991937', uid);
objectDelete($this, name, the_url);
setTimeout( function () {
$data_table.ajax.reload();
}, 3000);
})
.on('click', '#btn_bulk_update', function () {
var action = $('#slct_bulk_update').val();
var $data_table = $('#asset_groups_list_table').DataTable();
var id_list = [];
var plain_id_list = [];
$data_table.rows({selected: true}).every(function(){
id_list.push({id: this.data().id});
plain_id_list.push(this.data().id);
});
if (id_list === []) {
return false;
}
var the_url = '{% url "api-assets:asset-group-list" %}';
console.log(plain_id_list);
console.log(the_url);
function doDelete() {
swal({
title: "{% trans 'Are you sure?' %}",
text: "{% trans 'This will delete the selected groups !!!' %}",
type: "warning",
showCancelButton: true,
confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false
}, function() {
var success = function() {
var msg = "{% trans 'Group deleted' %}";
swal("{% trans 'Group Delete' %}", msg, "success");
$('#asset_groups_list_table').DataTable().ajax.reload();
};
var fail = function() {
var msg = "{% trans 'Group deleting failed.' %}";
swal("{% trans 'Group Delete' %}", msg, "error");
};
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail});
$data_table.ajax.reload();
jumpserver.checked = false;
});
}
function doUpdate() {
$('#asset_group_bulk_update_modal').modal('show');
}
switch(action) {
case 'delete':
doDelete();
break;
case 'update':
doUpdate();
break;
default:
break;
}
})
.on('click', '#btn_asset_group_bulk_update', function () {
var json_data = $("#fm_asset_group_bulk_update").serializeObject();
var body = {};
body.enable_otp = (json_data.enable_otp === 'on')? true: false;
if (json_data.type != '') {
body.type = json_data.type;
}
if (json_data.assets != undefined) {
body.assets = json_data.assets;
}
if (typeof body.assets === 'string') {
body.assets = [parseInt(body.assets)]
} else if(typeof body.assets === 'array') {
var new_assets = body.assets.map(Number);
body.assets = new_assets;
}
if (json_data.system_users != undefined) {
body.system_users = json_data.system_users;
}
if (typeof body.system_users === 'string') {
body.system_users = [parseInt(body.system_users)];
} else if (typeof body.system_users === 'array') {
var new_system_users = body.system_users.map(Number);
body.system_users = new_system_users;
}
var post_list = [];
var $data_table = $('#asset_groups_list_table').DataTable()
$data_table.rows({selected: true}).every(function(){
var content = Object.assign({id: this.data().id}, body);
post_list.push(content);
});
if (post_list === []) {
return false
}
var the_url = '{% url "api-assets:asset-group-list" %}';
var success = function() {
var msg = "{% trans 'The selected asset groups has been updated successfully.' %}";
swal("{% trans 'AssetGroup Updated' %}", msg, "success");
$('#asset_groups_list_table').DataTable().ajax.reload();
jumpserver.checked = false;
};
{# console.log(JSON.stringify(post_list));#}
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success});
$('#asset_group_bulk_update_modal').modal('hide');
});
</script>
{% endblock %}

View File

@@ -1,180 +1,578 @@
{% extends '_base_list.html' %}
{% load i18n %}
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block help_message %}
<div class="alert alert-info help-message">
{# 左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的,右侧是属于该节点下的资产#}
{% trans 'The left side is the asset tree, right click to create, delete, and change the tree node, authorization asset is also organized as a node, and the right side is the asset under that node' %}
</div>
{% endblock %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
{# <link href="https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css" rel="stylesheet">#}
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
<script src="{% static 'js/jquery.form.min.js' %}"></script>
<style type="text/css">
div#rMenu {
position:absolute;
visibility:hidden;
text-align: left;
{#top: 100%;#}
top: 0;
left: 0;
z-index: 1000;
{#float: left;#}
padding: 0 0;
margin: 2px 0 0;
list-style: none;
background-clip: padding-box;
}
.dataTables_wrapper .dataTables_processing {
opacity: .9;
border: none;
}
div#rMenu li{
margin: 1px 0;
cursor: pointer;
list-style: none outside none;
}
.dropdown a:hover {
background-color: #f1f1f1
}
</style>
{#<style>#}
{# .custom{#}
{# margin-right:5px;#}
{# }#}
{# #modal .modal-body { max-height: 200px; }#}
{#</style>#}
{% endblock %}
{% block content_left_head %}{% endblock %}
{% block table_search %}
<div class="html5buttons">
<div class="dt-buttons btn-group">
<a class="btn btn-default btn_import" data-toggle="modal" data-target="#asset_import_modal" tabindex="0">
<span>{% trans "Import" %}</span>
</a>
<a class="btn btn-default btn_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a>
</div>
</div>
{% endblock %}
{% block table_container %}
<div class="uc pull-left m-l-5 m-r-5"><a href="{% url "assets:asset-create" %}" class="btn btn-sm btn-primary"> {% trans "Create asset" %} </a></div>
<table class="table table-striped table-bordered table-hover " id="asset_list_table" >
<thead>
<tr>
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
<th class="text-center">{% trans 'Hostname' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'Port' %}</th>
<th class="text-center">{% trans 'Type' %}</th>
<th class="text-center">{% trans 'Env' %}</th>
<th class="text-center">{% trans 'Hardware' %}</th>
<th class="text-center">{% trans 'Valid' %}</th>
<th class="text-center">{% trans 'Alive' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div id="actions" class="hide">
<div class="input-group">
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
<option value="delete">{% trans 'Delete selected' %}</option>
<option value="update">{% trans 'Update selected' %}</option>
<option value="deactive">{% trans 'Deactive selected' %}</option>
<option value="active">{% trans 'Active' %}</option>
</select>
<div class="input-group-btn pull-left" style="padding-left: 5px;">
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
{% trans 'Submit' %}
</button>
</div>
</div>
{% block content %}
<div class="wrapper wrapper-content">
<div class="row">
<div class="col-lg-3" id="split-left" style="padding-left: 3px">
<div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
<div class="file-manager ">
<div id="assetTree" class="ztree">
</div>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div class="col-lg-9 animated fadeInRight" id="split-right">
<div class="tree-toggle">
<div class="btn btn-sm btn-primary tree-toggle-btn" onclick="toggle()">
<i class="fa fa-angle-left fa-x" id="toggle-icon"></i>
</div>
</div>
<div class="mail-box-header">
<div class="uc pull-left m-r-5"><a class="btn btn-sm btn-primary btn-create-asset"> {% trans "Create asset" %} </a></div>
<div class="html5buttons">
<div class="dt-buttons btn-group">
<a class="btn btn-default btn_import" data-toggle="modal" data-target="#asset_import_modal" tabindex="0">
<span>{% trans "Import" %}</span>
</a>
<a class="btn btn-default btn_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a>
</div>
</div>
<div class="btn-group" style="float: right">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>
<ul class="dropdown-menu labels">
{% for label in labels %}
<li><a style="font-weight: bolder">{{ label.name }}:{{ label.value }}</a></li>
{% endfor %}
</ul>
</div>
<table class="table table-striped table-bordered table-hover " id="asset_list_table" style="width: 100%">
<thead>
<tr>
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
<th class="text-center">{% trans 'Hostname' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'Hardware' %}</th>
<th class="text-center">{% trans 'Active' %}</th>
{# <th class="text-center">{% trans 'Reachable' %}</th>#}
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div id="actions" class="hide">
<div class="input-group">
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
<option value="delete">{% trans 'Delete selected' %}</option>
<option value="update">{% trans 'Update selected' %}</option>
<option value="remove">{% trans 'Remove from this node' %}</option>
<option value="deactive">{% trans 'Deactive selected' %}</option>
<option value="active">{% trans 'Active selected' %}</option>
</select>
<div class="input-group-btn pull-left" style="padding-left: 5px;">
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
{% trans 'Submit' %}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="rMenu">
<ul class="dropdown-menu">
<li class="divider"></li>
<li id="m_create" tabindex="-1" onclick="addTreeNode();"><a><i class="fa fa-plus-square-o"></i> {% trans 'Add node' %}</a></li>
<li id="m_del" tabindex="-1" onclick="editTreeNode();"><a><i class="fa fa-pencil-square-o"></i> {% trans 'Rename node' %}</a></li>
<li id="m_del" tabindex="-1" onclick="removeTreeNode();"><a><i class="fa fa-minus-square"></i> {% trans 'Delete node' %}</a></li>
<li class="divider"></li>
<li id="menu_asset_add" class="btn-add-asset" data-toggle="modal" data-target="#asset_list_modal" tabindex="0"><a><i class="fa fa-copy"></i> {% trans 'Add assets to node' %}</a></li>
<li id="menu_asset_move" class="btn-move-asset" data-toggle="modal" data-target="#asset_list_modal" tabindex="0"><a><i class="fa fa-cut"></i> {% trans 'Move assets to node' %}</a></li>
<li class="divider"></li>
<li id="menu_refresh_hardware_info" class="btn-refresh-hardware" tabindex="-1"><a><i class="fa fa-refresh"></i> {% trans 'Refresh node hardware info' %}</a></li>
<li id="menu_test_connective" class="btn-test-connective" tabindex="-1"><a><i class="fa fa-chain"></i> {% trans 'Test node connective' %}</a></li>
<li class="divider"></li>
<li id="show_current_asset" class="btn-show-current-asset" style="display: none;" tabindex="-1"><a><i class="fa fa-hand-o-up"></i> {% trans 'Display only current node assets' %}</a></li>
<li id="show_all_asset" class="btn-show-all-asset" style="display: none;" tabindex="-1"><a><i class="fa fa-th"></i> {% trans 'Displays all child node assets' %}</a></li>
</ul>
</div>
{% include 'assets/_asset_import_modal.html' %}
{#{% include 'assets/_asset_bulk_update_modal.html' %}#}
{% include 'assets/_asset_list_modal.html' %}
{% endblock %}
{% block custom_foot_js %}
<script src="{% static 'js/jquery.form.min.js' %}"></script>
<script type="text/javascript">
window.onload = function (){
var tag_on = document.getElementsByName("tag_on");
var oDiv = document.getElementById("ydxbd");
if(tag_on.length > 0){
oDiv.style.display = "block";
}
};
function tagShow() {
var oDiv = document.getElementById("ydxbd");
if (oDiv.style.display == 'none'){
oDiv.style.display = "block";
}else{
oDiv.style.display = "none";
}
} //onload;
$(document).ready(function(){
<script>
var zTree, rMenu, asset_table, show = 0;
var update_node_action = "";
function initTable() {
var options = {
ele: $('#asset_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:asset-detail" pk=99991937 %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('99991937', rowData.id));
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 7, createdCell: function (td, cellData) {
{targets: 3, createdCell: function (td, cellData, rowData) {
$(td).html(rowData.hardware_info)
}},
{targets: 4, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 8, createdCell: function (td, cellData) {
if (cellData == 'Unknown'){
$(td).html('<i class="fa fa-circle text-warning"></i>')
} else if (!cellData) {
$(td).html('<i class="fa fa-circle text-danger"></i>')
} else {
$(td).html('<i class="fa fa-circle text-navy"></i>')
}
}},
{targets: 9, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:asset-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData);
{targets: 5, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:asset-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
}}
],
ajax_url: '{% url "api-assets:asset-list" %}',
columns: [{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
{data: "get_type_display" }, {data: "get_env_display"}, {data: "hardware"},
{data: "is_active" }, {data: "is_online"}, {data: "id" }],
columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" },
{data: "cpu_cores"}, {data: "is_active", orderable: false },
{data: "id", orderable: false }
],
op_html: $('#actions').html()
};
var table = jumpserver.initDataTable(options);
asset_table = jumpserver.initServerSideDataTable(options);
return asset_table
}
$('.btn_export').click(function () {
var assets = [];
var rows = table.rows('.selected').data();
$.each(rows, function (index, obj) {
assets.push(obj.id)
});
console.log(assets);
$.ajax({
url: "{% url "assets:asset-export" %}",
method: 'POST',
data: JSON.stringify({assets_id: assets}),
dataType: "json",
success: function (data, textStatus) {
window.open(data.redirect)
},
error: function () {
toastr.error('Export failed');
}
})
});
$('#btn_asset_import').click(function() {
var $form = $('#fm_asset_import');
$form.find('.help-block').remove();
function success (data) {
if (data.valid === false) {
$('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_assets'));
} else {
$('#id_created').html(data.created_info);
$('#id_created_detail').html(data.created.join(', '));
$('#id_updated').html(data.updated_info);
$('#id_updated_detail').html(data.updated.join(', '));
$('#id_failed').html(data.failed_info);
$('#id_failed_detail').html(data.failed.join(', '));
var $data_table = $('#asset_list_table').DataTable();
$data_table.ajax.reload();
}
function addTreeNode() {
hideRMenu();
var parentNode = zTree.getSelectedNodes()[0];
if (!parentNode){
return
}
var url = "{% url 'api-assets:node-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", parentNode.node_id );
$.post(url, {}, function (data, status){
if (status === "success") {
var newNode = {
id: data["key"],
name: data["value"],
node_id: data["id"],
pId: parentNode.id
};
newNode.checked = zTree.getSelectedNodes()[0].checked;
zTree.addNodes(parentNode, 0, newNode);
var node = zTree.getNodeByParam('id', newNode.node_id, parentNode);
zTree.editName(node);
} else {
alert("{% trans 'Create node failed' %}")
}
});
}
function removeTreeNode() {
hideRMenu();
var current_node = zTree.getSelectedNodes()[0];
if (!current_node){
return
}
if (current_node.children && current_node.children.length > 0) {
toastr.error("{% trans 'Have child node, cancel' %}");
} else if (current_node.assets_amount !== 0) {
toastr.error("{% trans 'Have assets, cancel' %}");
} else {
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.node_id );
$.ajax({
url: url,
method: "DELETE",
success: function () {
zTree.removeNode(current_node);
}
});
}
}
function editTreeNode() {
hideRMenu();
var current_node = zTree.getSelectedNodes()[0];
if (!current_node){
return
}
if (current_node.value) {
current_node.name = current_node.value;
}
zTree.editName(current_node);
}
function OnRightClick(event, treeId, treeNode) {
if (!treeNode && event.target.tagName.toLowerCase() !== "button" && $(event.target).parents("a").length === 0) {
zTree.cancelSelectedNode();
showRMenu("root", event.clientX, event.clientY);
} else if (treeNode && !treeNode.noR) {
zTree.selectNode(treeNode);
showRMenu("node", event.clientX, event.clientY);
}
}
function showRMenu(type, x, y) {
$("#rMenu ul").show();
x -= 220;
x += document.body.scrollLeft;
y += document.body.scrollTop+document.documentElement.scrollTop;
rMenu.css({"top":y+"px", "left":x+"px", "visibility":"visible"});
$("body").bind("mousedown", onBodyMouseDown);
}
function beforeClick(treeId, treeNode, clickFlag) {
return true;
}
function hideRMenu() {
if (rMenu) rMenu.css({"visibility": "hidden"});
$("body").unbind("mousedown", onBodyMouseDown);
}
function onBodyMouseDown(event){
if (!(event.target.id === "rMenu" || $(event.target).parents("#rMenu").length>0)) {
rMenu.css({"visibility" : "hidden"});
}
}
function onRename(event, treeId, treeNode, isCancel){
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", treeNode.node_id);
var data = {"value": treeNode.name};
if (isCancel){
return
}
APIUpdateAttr({
url: url,
body: JSON.stringify(data),
method: "PATCH",
success_message: "{% trans 'Rename success' %}",
fail_message: "{% trans 'Rename failed, do not change the root node name' %}"
})
}
function onSelected(event, treeNode) {
var url = asset_table.ajax.url();
url = setUrlParam(url, "node_id", treeNode.node_id);
url = setUrlParam(url, "show_current_asset", getCookie('show_current_asset'));
setCookie('node_selected', treeNode.node_id);
asset_table.ajax.url(url);
asset_table.ajax.reload();
}
function selectQueryNode() {
var query_node_id = $.getUrlParam("node");
var cookie_node_id = getCookie('node_selected');
var node;
var node_id;
if (query_node_id !== null) {
node_id = query_node_id
} else if (cookie_node_id !== null) {
node_id = cookie_node_id;
}
node = zTree.getNodesByParam("node_id", node_id, null);
if (node){
zTree.selectNode(node[0]);
}
}
function beforeDrag() {
return true
}
function beforeDrop(treeId, treeNodes, targetNode, moveType) {
var treeNodesNames = [];
$.each(treeNodes, function (index, value) {
treeNodesNames.push(value.value);
});
var msg = "你想移动节点: `" + treeNodesNames.join(",") + "` 到 `" + targetNode.value + "` 下吗?";
return confirm(msg);
}
function onDrag(event, treeId, treeNodes) {
}
function onDrop(event, treeId, treeNodes, targetNode, moveType) {
var treeNodesIds = [];
$.each(treeNodes, function (index, value) {
treeNodesIds.push(value.node_id);
});
var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.node_id);
var body = {nodes: treeNodesIds};
APIUpdateAttr({
url: the_url,
method: "PUT",
body: JSON.stringify(body)
})
}
function initTree() {
var setting = {
view: {
dblClickExpand: false,
showLine: true
},
data: {
simpleData: {
enable: true
}
},
edit: {
enable: true,
showRemoveBtn: false,
showRenameBtn: false,
drag: {
isCopy: true,
isMove: true
}
},
callback: {
onRightClick: OnRightClick,
beforeClick: beforeClick,
onRename: onRename,
onSelected: onSelected,
beforeDrag: beforeDrag,
onDrag: onDrag,
beforeDrop: beforeDrop,
onDrop: onDrop
}
};
var zNodes = [];
$.get("{% url 'api-assets:node-list' %}", function(data, status){
$.each(data, function (index, value) {
value["node_id"] = value["id"];
value["id"] = value["tree_id"];
if (value["tree_id"] !== value["tree_parent"]){
value["pId"] = value["tree_parent"];
} else {
value["isParent"] = true;
}
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
value['value'] = value['value'];
});
zNodes = data;
$.fn.zTree.init($("#assetTree"), setting, zNodes);
zTree = $.fn.zTree.getZTreeObj("assetTree");
var root = zTree.getNodes()[0];
zTree.expandNode(root);
rMenu = $("#rMenu");
selectQueryNode();
});
}
function toggle() {
if (show === 0) {
$("#split-left").hide(500, function () {
$("#split-right").attr("class", "col-lg-12");
$("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
show = 1;
});
} else {
$("#split-right").attr("class", "col-lg-9");
$("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
$("#split-left").show(500);
show = 0;
}
}
$(document).ready(function(){
initTable();
initTree();
if(getCookie('show_current_asset') === '1'){
$('#show_all_asset').css('display', 'inline-block');
}
else{
$('#show_current_asset').css('display', 'inline-block');
}
})
.on('click', '.labels li', function () {
var val = $(this).text();
$("#asset_list_table_filter input").val(val);
asset_table.search(val).draw();
})
.on('click', '.btn_export', function () {
var $data_table = $('#asset_list_table').DataTable();
var rows = $data_table.rows('.selected').data();
var nodes = zTree.getSelectedNodes();
var current_node;
if (nodes && nodes.length === 1) {
current_node = nodes[0];
}
var assets = [];
$.each(rows, function (index, obj) {
assets.push(obj.id)
});
var _node_id = current_node ? current_node : null;
$.ajax({
url: "{% url "assets:asset-export" %}",
method: 'POST',
data: JSON.stringify({assets_id: assets, node_id: _node_id}),
dataType: "json",
success: function (data, textStatus) {
window.open(data.redirect)
},
error: function () {
toastr.error('Export failed');
}
$form.ajaxSubmit({success: success});
})
})
.on('click', '#btn_asset_import', function () {
var $form = $('#fm_asset_import');
var action = $form.attr("action");
var nodes = zTree.getSelectedNodes();
var current_node;
if (nodes && nodes.length ===1 ){
current_node = nodes[0];
action = setUrlParam(action, 'node_id', current_node.node_id);
{#action += "?node_id=" + current_node.node_id;#}
$form.attr("action", action)
}
$form.find('.help-block').remove();
function success (data) {
if (data.valid === false) {
$('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_assets'));
} else {
$('#id_created').html(data.created_info);
$('#id_created_detail').html(data.created.join(', '));
$('#id_updated').html(data.updated_info);
$('#id_updated_detail').html(data.updated.join(', '));
$('#id_failed').html(data.failed_info);
$('#id_failed_detail').html(data.failed.join(', '));
var $data_table = $('#asset_list_table').DataTable();
$data_table.ajax.reload();
}
}
$form.ajaxSubmit({success: success});
})
.on('click', '.btn-create-asset', function () {
var url = "{% url 'assets:asset-create' %}";
var nodes = zTree.getSelectedNodes();
var current_node;
if (nodes && nodes.length ===1 ){
current_node = nodes[0];
url += "?node_id=" + current_node.node_id;
}
window.open(url, '_self');
})
.on('click', '.btn-refresh-hardware', function () {
var url = "{% url 'api-assets:node-refresh-hardware-info' pk=DEFAULT_PK %}";
var nodes = zTree.getSelectedNodes();
var current_node;
if (nodes && nodes.length ===1 ){
current_node = nodes[0];
} else {
return null;
}
var the_url = url.replace("{{ DEFAULT_PK }}", current_node.node_id);
function success(data) {
rMenu.css({"visibility" : "hidden"});
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
}
APIUpdateAttr({
url: the_url,
method: "GET",
success: success,
flash_message: false
});
})
.on('click', '.btn-test-connective', function () {
var url = "{% url 'api-assets:node-test-connective' pk=DEFAULT_PK %}";
var nodes = zTree.getSelectedNodes();
var current_node;
if (nodes && nodes.length ===1 ){
current_node = nodes[0];
} else {
return null;
}
var the_url = url.replace("{{ DEFAULT_PK }}", current_node.node_id);
function success(data) {
rMenu.css({"visibility" : "hidden"});
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
}
APIUpdateAttr({
url: the_url,
method: "GET",
success: success,
flash_message: false
});
})
.on('click', '.btn-show-current-asset', function(){
hideRMenu();
$(this).css('display', 'none');
$('#show_all_asset').css('display', 'inline-block');
setCookie('show_current_asset', '1');
location.reload();
})
.on('click', '.btn-show-all-asset', function(){
hideRMenu();
$(this).css('display', 'none');
$('#show_current_asset').css('display', 'inline-block');
setCookie('show_current_asset', '');
location.reload();
})
.on('click', '.btn_asset_delete', function () {
var $this = $(this);
var $data_table = $("#asset_list_table").DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var the_url = '{% url "api-assets:asset-detail" pk=99991937 %}'.replace('99991937', uid);
console.log(the_url);
var the_url = '{% url "api-assets:asset-detail" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", uid);
objectDelete($this, name, the_url);
setTimeout( function () {
$data_table.ajax.reload();
@@ -187,26 +585,42 @@ $(document).ready(function(){
$data_table.rows({selected: true}).every(function(){
id_list.push(this.data().id);
});
if (id_list.length == 0) {
if (id_list.length === 0) {
return false;
}
var the_url = "{% url 'api-assets:asset-list' %}";
function doDeactive() {
var body = $.each(id_list, function(index, asset_object) {
asset_object['is_active'] = false;
var data = [];
$.each(id_list, function(index, object_id) {
var obj = {"pk": object_id, "is_active": false};
data.push(obj);
});
function success() {
asset_table.ajax.reload()
}
APIUpdateAttr({
url: the_url,
method: 'PATCH',
body: JSON.stringify(data),
success: success
});
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
$data_table.ajax.reload();
jumpserver.checked = false;
}
function doActive() {
var body = $.each(id_list, function(index, asset_object) {
asset_object['is_active'] = true;
var data = [];
$.each(id_list, function(index, object_id) {
var obj = {"pk": object_id, "is_active": true};
data.push(obj);
});
function success() {
asset_table.ajax.reload()
}
APIUpdateAttr({
url: the_url,
method: 'PATCH',
body: JSON.stringify(data),
success: success
});
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
$data_table.ajax.reload();
jumpserver.checked = false;
}
function doDelete() {
swal({
@@ -214,6 +628,7 @@ $(document).ready(function(){
text: "{% trans 'This will delete the selected assets !!!' %}",
type: "warning",
showCancelButton: true,
cancelButtonText: "{% trans 'Cancel' %}",
confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false
@@ -227,8 +642,13 @@ $(document).ready(function(){
var msg = "{% trans 'Asset Deleting failed.' %}";
swal("{% trans 'Asset Delete' %}", msg, "error");
};
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail});
var url_delete = the_url + '?id__in=' + JSON.stringify(id_list);
APIUpdateAttr({
url: url_delete,
method: 'DELETE',
success: success,
error: fail
});
$data_table.ajax.reload();
jumpserver.checked = false;
});
@@ -238,6 +658,31 @@ $(document).ready(function(){
var url = "{% url 'assets:asset-bulk-update' %}?assets_id=" + id_list_string;
location.href = url
}
function doRemove() {
var current_node;
var nodes = zTree.getSelectedNodes();
if (nodes && nodes.length === 1) {
current_node = nodes[0]
} else {
return
}
var data = {
'assets': id_list
};
var success = function () {
asset_table.ajax.reload()
};
APIUpdateAttr({
'url': '/api/assets/v1/nodes/' + current_node.node_id + '/assets/remove/',
'method': 'PUT',
'body': JSON.stringify(data),
'success': success
})
}
switch(action) {
case 'deactive':
doDeactive();
@@ -251,69 +696,50 @@ $(document).ready(function(){
case 'active':
doActive();
break;
case 'remove':
doRemove();
break;
default:
break;
}
});
{##}
{#.on('click', '#btn_asset_bulk_update', function () {#}
{# var json_data = $("#fm_asset_bulk_update").serializeObject();#}
{# var body = {};#}
{# body.enable_otp = (json_data.enable_otp === 'on')? true: false;#}
{# if (json_data.type != '') {#}
{# body.type = json_data.type;#}
{# }#}
{# if (json_data.groups != undefined) {#}
{# body.groups = json_data.groups;#}
{# }#}
{# if (typeof body.groups === 'string') {#}
{# body.groups = [parseInt(body.groups)]#}
{# } else if(typeof body.groups === 'array') {#}
{# var new_groups = body.groups.map(Number);#}
{# body.groups = new_groups;#}
{# }#}
{##}
{# if (json_data.system_users != undefined) {#}
{# body.system_users = json_data.system_users;#}
{# }#}
{# if (typeof body.system_users === 'string') {#}
{# body.system_users = [parseInt(body.system_users)]#}
{# } else if(typeof body.system_users === 'array') {#}
{# var new_users = body.system_users.map(Number);#}
{# body.system_users = new_users;#}
{# }#}
{##}
{# if (json_data.tags != undefined) {#}
{# body.tags = json_data.tags;#}
{# }#}
{# if (typeof body.tags == 'string') {#}
{# body.tags = [parseInt(body.tags)];#}
{# } else if (typeof body.tags === 'array') {#}
{# var new_tags = body.tags.map(Number);#}
{# body.tags = new_tags;#}
{# }#}
{##}
{# var $data_table = $('#asset_list_table').DataTable();#}
{# var post_list = [];#}
{# $data_table.rows({selected: true}).every(function(){#}
{# var content = Object.assign({id: this.data().id}, body);#}
{# post_list.push(content);#}
{# });#}
{# if (post_list === []) {#}
{# return false#}
{# }#}
{# var the_url = "{% url 'api-assets:asset-list' %}";#}
{# var success = function() {#}
{# var msg = "{% trans 'The selected assets has been updated successfully.' %}";#}
{# swal("{% trans 'Asset Updated' %}", msg, "success");#}
{# $('#asset_list_table').DataTable().ajax.reload();#}
{# jumpserver.checked = false;#}
{# };#}
{# console.log(JSON.stringify(post_list));#}
{# console.log(the_url);#}
{# APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success});#}
{# $('#asset_bulk_update_modal').modal('hide');#}
{#})#}
$(".ipt_check_all").prop("checked", false)
})
.on('click', '#btn_asset_modal_confirm', function () {
var assets_selected = asset_table2.selected;
var current_node;
var nodes = zTree.getSelectedNodes();
if (nodes && nodes.length === 1) {
current_node = nodes[0]
} else {
return
}
var data = {'assets': assets_selected};
var success = function () {
asset_table2.selected = [];
asset_table2.ajax.reload()
};
var url = '';
if (update_node_action === "move") {
url = "{% url 'api-assets:node-replace-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.node_id);
} else {
url = "{% url 'api-assets:node-add-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.node_id);
}
APIUpdateAttr({
'url': url,
'method': 'PUT',
'body': JSON.stringify(data),
'success': success
})
}).on('hidden.bs.modal', '#asset_list_modal', function () {
window.location.reload();
}).on('click', '#menu_asset_add', function () {
update_node_action = "add"
}).on('click', '#menu_asset_move', function () {
update_node_action = "move"
})
</script>
{% endblock %}
{% endblock %}

View File

@@ -1,127 +0,0 @@
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title" id="myModalLabel">分配/回收资产</h4>
</div>
<div class="modal-body" style="padding-bottom: 0px;">
<table aria-describedby="editable_info" role="grid" class="table table-striped table-bordered table-hover dataTable" id="editable">
<thead>
<tr>
<th class="text-center" style="background-color:white">
<input type="checkbox" id="check_all" onclick="checkAll()">
</th>
<th id="th_no">id</th>
<th>资产名称</th>
<th>IP</th>
<th>类型</th>
</tr>
</thead>
<tbody>
{% for asset in assets %}
{% if asset.id in all_assets %}
<tr name="oAssets" class="odd selected text-center">
<td class="text-center" ><input type="checkbox" name="checked" value="{{ asset.id }}" checked="checked"></td>
{% else %}
<tr name="oAssets">
<td class="text-center"><input type="checkbox" name="checked" value="{{ asset.id }}" ></td>
{% endif %}
<td class="text-center">{{ asset.id }}</td>
<td class="text-center">{{ asset.hostname }}</td>
<td class="text-center">{{ asset.ip }}</td>
<td class="text-center">{{ asset.env }}-{{ asset.type }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" id="close-btn">取消</button>
<button type="button" class="btn btn-primary" id="save-btn">保存</button>
</div>
<script type="text/javascript">
$(document).ready(function(){
var table = $('#editable').DataTable({
"aLengthMenu": [[10, 25, 50, -1], ["10", "25", "50", "all"]],
"iDisplayLength":25,
"aaSorting": [[2, "asc"]],
"aoColumnDefs": [ { "bSortable": false, "aTargets": [ 0 ] }],
"bAutoWidth": false,
"language": {
"url": "/static/js/plugins/dataTables/i18n/zh-hans.json"
},
columns: [
{data: "checkbox"},
{data: "id"},
{data: "hostname"},
{data: "ip"},
{data: "type"}
]
});
//将ID列隐藏
table.column('1').visible(false);
$('#editable tbody').on( 'click', 'tr', function () {
//alert($(this).hasClass('selected'));
if($(this).hasClass('selected')){
$(this).removeClass('selected');
this.children[0].children[0].checked=0;
}else{
$(this).addClass('selected');
this.children[0].children[0].checked=1;
}
});
$('#close-btn').on('click',function(){
$('#modal').modal('hide');
});
var size_name = document.getElementById('asset_on_count').innerText;
$('#save-btn').on('click',function(){
//alert( table.rows('.selected').data().length +' row(s) selected' );
var d = table.rows('.selected').data();
var size = d.length;
var re = /\d+/;
document.getElementById('add_asset').value = size;
var str= size_name;
var re=/\d+/g;
document.getElementById('asset_on_count').innerText = str.replace(re, size);
var column2 = table.rows('.selected').data();
$("#asset_sed").find("input[name='assets']").remove();
$("#asset_sed").find("button[name='asset_hostname']").remove();
for(var i=0;i<column2.length;i++){
column2[i].checkbox='<input name="checked" value="1" checked="" type="checkbox">';
var value = column2[i].id;
var ip = column2[i].ip;
var hostname = column2[i].hostname;
$("#asset_sed").append("<input type='hidden' name='assets' value='"+value+"'>");
$("#asset_on_p").append("<button name='asset_hostname' title='"+ip+"' type='button' class='btn btn-default btn-xs ss'>"+hostname+"</button> ");
}
$('#modal').modal('hide');
});
}); //$(document).ready
var bCheck = 1;
function checkAll(){
if(bCheck){
$("tr[name='oAssets']").each(function(){
oCheckbox = this.children[0].children[0];
$(this).toggleClass('selected',true);
oCheckbox.checked=1;
});
document.getElementById('check_all').checked=1;
bCheck = 0;
}else{
$("tr[name='oAssets']").each(function(){
oCheckbox = this.children[0].children[0];
$(this).toggleClass('selected',false);
oCheckbox.checked=0;
});
document.getElementById('check_all').checked=0;
bCheck = 1;
}
}
</script>

View File

@@ -2,6 +2,8 @@
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% load asset_tags %}
{% load common_tags %}
{% block custom_head_css_js_create %}
<link href="{% static "css/plugins/inputTags.css" %}" rel="stylesheet">
@@ -10,7 +12,7 @@
{% block form %}
<form action="" method="post" class="form-horizontal">
{% if form.no_field_errors %}
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
@@ -19,33 +21,47 @@
<h3>{% trans 'Basic' %}</h3>
{% bootstrap_field form.hostname layout="horizontal" %}
{% bootstrap_field form.ip layout="horizontal" %}
{% bootstrap_field form.public_ip layout="horizontal" %}
{% bootstrap_field form.protocol layout="horizontal" %}
{% bootstrap_field form.port layout="horizontal" %}
{% bootstrap_field form.type layout="horizontal" %}
{% bootstrap_field form.platform layout="horizontal" %}
{% bootstrap_field form.public_ip layout="horizontal" %}
{% bootstrap_field form.domain layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Group' %}</h3>
{% bootstrap_field form.idc layout="horizontal" %}
{% bootstrap_field form.groups layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Asset user' %}</h3>
<h3>{% trans 'Auth' %}</h3>
{% bootstrap_field form.admin_user layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Node' %}</h3>
{% bootstrap_field form.nodes layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Labels' %}</h3>
<div class="form-group">
<label for="{{ form.labels.id_for_label }}" class="col-md-2 control-label">{% trans 'Label' %}</label>
<div class="col-md-9">
<select name="labels" class="select2 labels" data-placeholder="{% trans 'Label' %}" style="width: 100%" multiple="" tabindex="4" id="{{ form.labels.id_for_label }}">
{% for name, labels in form.labels.field.queryset|group_labels %}
<optgroup label="{{ name }}">
{% for label in labels %}
{% if label in form.labels.initial %}
<option value="{{ label.id }}" selected>{{ label.value }}</option>
{% else %}
<option value="{{ label.id }}">{{ label.value }}</option>
{% endif %}
{% endfor %}
</optgroup>
{% endfor %}
</select>
</div>
</div>
<div class="hr-line-dashed"></div>
<h3>{% trans 'Configuration' %}</h3>
{% bootstrap_field form.number layout="horizontal" %}
{% bootstrap_field form.remote_card_ip layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Location' %}</h3>
{% bootstrap_field form.cabinet_no layout="horizontal" %}
{% bootstrap_field form.cabinet_pos layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Other' %}</h3>
{% bootstrap_field form.status layout="horizontal" %}
{% bootstrap_field form.env layout="horizontal" %}
{% bootstrap_field form.comment layout="horizontal" %}
{% bootstrap_field form.is_active layout="horizontal" %}
@@ -62,12 +78,18 @@
{% block custom_foot_js %}
<script>
$(document).ready(function () {
$('.select2').select2();
$("#tags").select2({
tags: true,
maximumSelectionLength: 8 //最多能够选择的个数
});
})
function format(item) {
var group = item.element.parentElement.label;
return group + ':' + item.text;
}
$(document).ready(function () {
$('.select2').select2({
allowClear: true
});
$(".labels").select2({
allowClear: true,
templateSelection: format
});
})
</script>
{% endblock %}

View File

@@ -0,0 +1,20 @@
{% extends '_base_create_update.html' %}
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% block form %}
<form id="groupForm" method="post" class="form-horizontal">
{% csrf_token %}
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.comment layout="horizontal" %}
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
{% endblock %}

View File

@@ -0,0 +1,168 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href='{% static "css/plugins/select2/select2.min.css" %}' rel="stylesheet">
<script src='{% static "js/plugins/select2/select2.full.min.js" %}'></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li class="active">
<a href="{% url 'assets:cmd-filter-detail' pk=object.id %}" class="text-center">
<i class="fa fa-laptop"></i> {% trans 'Detail' %}
</a>
</li>
<li>
<li>
<a href="{% url 'assets:cmd-filter-rule-list' pk=object.id %}" class="text-center">
<i class="fa fa-laptop"></i> {% trans 'Rules' %}
</a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'assets:cmd-filter-update' pk=object.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-danger btn-del">
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-8" style="padding-left: 0;">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label"><b>{{ object.name }}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table">
<tbody>
<tr class="no-borders-tr">
<td>{% trans 'Name' %}:</td>
<td><b>{{ object.name }}</b></td>
</tr>
<tr>
<td>{% trans 'Comment' %}:</td>
<td><b>{{ object.comment }}</b></td>
</tr>
<tr>
<td>{% trans 'Date created' %}:</td>
<td><b>{{ object.date_created }}</b></td>
</tr>
<tr>
<td>{% trans 'Date updated' %}:</td>
<td><b>{{ object.date_updated }}</b></td>
</tr>
<tr>
<td>{% trans 'Created by' %}:</td>
<td><b>{{ object.created_by }}</b></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-sm-4" style="padding-left: 0; padding-right: 0">
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'System users' %}
</div>
<div class="panel-body">
<table class="table group_edit" id="table-clusters">
<tbody>
<form>
<tr>
<td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Binding to system user' %}" id="system_users_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for system_user in system_users_remain %}
<option value="{{ system_user.id }}" id="opt_{{ system_user.id }}">{{ system_user }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td colspan="2" class="no-borders">
<button type="button" class="btn btn-primary btn-sm" id="btn-binding-system-users">{% trans 'Confirm' %}</button>
</td>
</tr>
</form>
{% for system_user in object.system_users.all %}
<tr>
<td><b class="bdg-system-users" data-gid={{ system_user.id }}>{{ system_user }}</b></td>
<td>
<button class="btn btn-danger pull-right btn-xs btn-unbound-system-user" data-gid={{ system_user.id }} type="button"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
function updateCMDFilterSystemUsers(system_users) {
var the_url = "{% url 'api-assets:cmd-filter-detail' pk=object.id %}";
var body = {
system_users: Object.assign([], system_users)
};
var success = function(data) {
location.reload();
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
method: 'PATCH',
success: success
});
}
$(document).ready(function () {
$(".select2").select2();
}).on('click', '#btn-binding-system-users', function () {
var origin_system_users = $.map($(".bdg-system-users"), function (s) {
return $(s).data('gid')
});
var new_selected_system_users_id = $.map($("#system_users_selected").select2('data'), function (s) {
return s.id;
});
var system_users = origin_system_users.concat(new_selected_system_users_id);
updateCMDFilterSystemUsers(system_users)
}).on('click', '.btn-unbound-system-user', function () {
var unbound_system_user = $(this).data('gid');
var origin_system_users = $.map($(".bdg-system-users"), function (s) {
return $(s).data('gid')
});
var system_users = $.grep(origin_system_users, function (n, i) {
return n !== unbound_system_user
});
updateCMDFilterSystemUsers(system_users)
})
</script>
{% endblock %}

View File

@@ -0,0 +1,89 @@
{% extends '_base_list.html' %}
{% load i18n static %}
{% block table_search %}{% endblock %}
{% block help_message %}
<div class="alert alert-info help-message">
{% trans 'System user bound some command filter, each command filter has some rules,'%}
{% trans 'When user login asset with this system user, then run a command,' %}
{% trans 'The command will be filter by rules, higher priority(lower number) rule run first,' %}
{% trans 'When a rule matched, if rule action is allow, then allow command execute,' %}
{% trans 'else if action is deny, then command with be deny,' %}
{% trans 'else match next rule, if none matched, allowed' %}
</div>
{% endblock %}
{% block table_container %}
<div class="uc pull-left m-r-5">
<a href="{% url 'assets:cmd-filter-create' %}" class="btn btn-sm btn-primary"> {% trans "Create command filter" %} </a>
</div>
<table class="table table-striped table-bordered table-hover " id="cmd_filter_list_table" >
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Rules' %}</th>
<th class="text-center">{% trans 'System users' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
<script>
function initTable() {
var options = {
ele: $('#cmd_filter_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url 'assets:cmd-filter-detail' pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 2, createdCell: function (td, cellData, rowData) {
var filters_list_btn = '<a href="{% url "assets:cmd-filter-rule-list" pk=DEFAULT_PK %}">' + cellData.length + '</a>';
filters_list_btn = filters_list_btn.replace("{{ DEFAULT_PK }}", rowData.id);
$(td).html(filters_list_btn);
}},
{targets: 3, createdCell: function (td, cellData, rowData) {
var system_users_list_btn = '<a href="{% url "assets:cmd-filter-detail" pk=DEFAULT_PK %}">' + cellData.length + '</a>';
system_users_list_btn = system_users_list_btn.replace("{{ DEFAULT_PK }}", rowData.id);
$(td).html(system_users_list_btn);
}},
{targets: 5, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:cmd-filter-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
}}
],
ajax_url: '{% url "api-assets:cmd-filter-list" %}',
columns: [
{data: "id"}, {data: "name" }, {data: "rules" },
{data: "system_users" }, {data: "comment"}, {data: "id"}
],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
}
$(document).ready(function(){
initTable();
})
.on('click', '.btn-delete', function () {
var $this = $(this);
var $data_table = $('#cmd_filter_list_table').DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var the_url = '{% url "api-assets:cmd-filter-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
objectDelete($this, name, the_url);
setTimeout( function () {
$data_table.ajax.reload();
}, 3000);
});
</script>
{% endblock %}

View File

@@ -0,0 +1,74 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% load bootstrap3 %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5>{{ action }}</h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<form enctype="multipart/form-data" method="post" class="form-horizontal" action="" >
{% csrf_token %}
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
{% bootstrap_form form layout="horizontal" %}
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-white" type="reset">{% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
var content_origin_placeholder = '';
var content_origin_help_text = '';
var content_ref = '';
var content_help_ref = '';
$(document).ready(function(){
content_ref = $('#id_content');
content_help_ref = content_ref.next();
content_origin_placeholder = content_ref.attr('placeholder');
content_origin_help_text = content_help_ref.html();
}).on('change', '#id_type', function () {
if ($('#id_type :selected').val() === 'regex') {
content_ref.attr('placeholder', 'rm.*|reboot|shutdown');
content_help_ref.html("");
} else {
content_ref.attr('placeholder', content_origin_placeholder);
content_help_ref.html(content_origin_help_text);
}
})
</script>
{% endblock %}

View File

@@ -0,0 +1,115 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href='{% static "css/plugins/select2/select2.min.css" %}' rel="stylesheet">
<script src='{% static "js/plugins/select2/select2.full.min.js" %}'></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li>
<a href="{% url 'assets:cmd-filter-detail' pk=object.id %}" class="text-center">
<i class="fa fa-laptop"></i> {% trans 'Detail' %}
</a>
</li>
<li class="active">
<a href="{% url 'assets:cmd-filter-rule-list' pk=object.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Rules' %} </a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-12" style="padding-left: 0;">
<div class="" id="content_start">
</div>
<div class="ibox float-e-margins">
<div class="ibox-title">
<span style="float: left"><b>{% trans 'Command filter rule list' %}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<div class="uc pull-left m-r-5">
<a href="{% url 'assets:cmd-filter-rule-create' filter_pk=object.id %}" class="btn btn-sm btn-primary"> {% trans "Create rule" %} </a>
</div>
<table class="table table-striped table-bordered table-hover " id="cmd_filter_rule_list_table" >
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th class="text-center">{% trans 'Type' %}</th>
<th class="text-center">{% trans 'Content' %}</th>
<th class="text-center">{% trans 'Priority' %}</th>
<th class="text-center">{% trans 'Strategy' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
<script>
function initTable() {
var options = {
ele: $('#cmd_filter_rule_list_table'),
columnDefs: [
{targets: 6, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:cmd-filter-rule-update" filter_pk=object.id pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
}}
],
ajax_url: '{% url "api-assets:cmd-filter-rule-list" filter_pk=object.id %}',
columns: [
{data: "id"}, {data: "type.display" }, {data: 'content'}, {data: 'priority'},
{data: 'action.display'}, {data: "comment" }, {data: "id"}
],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
}
$(document).ready(function(){
initTable();
})
.on('click', '.btn-delete', function () {
var $this = $(this);
var $data_table = $('#cmd_filter_rule_list_table').DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var the_url = '{% url "api-assets:cmd-filter-rule-detail" filter_pk=object.id pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
objectDelete($this, name, the_url);
setTimeout( function () {
$data_table.ajax.reload();
}, 3000);
})
</script>
{% endblock %}

View File

@@ -0,0 +1,42 @@
{% extends '_base_create_update.html' %}
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% block form %}
<form id="groupForm" method="post" class="form-horizontal">
{% csrf_token %}
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.assets layout="horizontal" %}
{% bootstrap_field form.comment layout="horizontal" %}
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
{% include 'assets/_asset_list_modal.html' %}
{% endblock %}
{% block custom_foot_js %}
<script type="text/javascript">
$(document).ready(function () {
console.log($.fn.select2.defaults);
$('.select2').select2().off("select2:open");
}).on('click', '.select2-selection__rendered', function (e) {
e.preventDefault();
$("#asset_list_modal").modal();
})
.on('click', '#btn_asset_modal_confirm', function () {
var assets = asset_table2.selected;
$.each(assets, function (id, data) {
$('.select2').val(assets).trigger('change');
});
$("#asset_list_modal").modal('hide');
})
</script>
{% endblock %}

View File

@@ -0,0 +1,132 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href='{% static "css/plugins/select2/select2.min.css" %}' rel="stylesheet">
<script src='{% static "js/plugins/select2/select2.full.min.js" %}'></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li class="active">
<a href="{% url 'assets:domain-detail' pk=object.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
</li>
<li>
<a href="{% url 'assets:domain-gateway-list' pk=object.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Gateway' %} </a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'assets:domain-update' pk=object.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-danger btn-del">
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-9" style="padding-left: 0;">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label"><b>{{ object.name }}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table">
<tbody>
<tr class="no-borders-tr">
<td>{% trans 'Name' %}:</td>
<td><b>{{ object.name }}</b></td>
</tr>
<tr>
<td>{% trans 'Asset' %}:</td>
<td><b>{{ object.assets.count }}</b></td>
</tr>
<tr>
<td>{% trans 'Gateway' %}:</td>
<td><b>{{ object.gateway_set.count }}</b></td>
</tr>
<tr>
<td>{% trans 'Date created' %}:</td>
<td><b>{{ object.date_created }}</b></td>
</tr>
<tr>
<td>{% trans 'Created by' %}:</td>
<td><b>{{ object.created_by }}</b></td>
</tr>
<tr>
<td>{% trans 'Comment' %}:</td>
<td><b>{{ object.comment }}</b></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
<script>
function initTable() {
var options = {
ele: $('#domain_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:domain-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 5, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:domain-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
}}
],
ajax_url: '{% url "api-assets:domain-list" %}',
columns: [
{data: "id"}, {data: "name" }, {data: "asset_count" },
{data: "gateway_count" }, {data: "comment" }, {data: "id"}
],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
}
$(document).ready(function(){
initTable();
})
.on('click', '.btn-delete', function () {
var $this = $(this);
var $data_table = $('#domain_list_table').DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var the_url = '{% url "api-assets:domain-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
objectDelete($this, name, the_url);
setTimeout( function () {
$data_table.ajax.reload();
}, 3000);
});
</script>
{% endblock %}

View File

@@ -0,0 +1,128 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href='{% static "css/plugins/select2/select2.min.css" %}' rel="stylesheet">
<script src='{% static "js/plugins/select2/select2.full.min.js" %}'></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li>
<a href="{% url 'assets:domain-detail' pk=object.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
</li>
<li class="active">
<a href="{% url 'assets:domain-detail' pk=object.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Gateway' %} </a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-12" style="padding-left: 0;">
<div class="" id="content_start">
</div>
<div class="ibox float-e-margins">
<div class="ibox-title">
<span style="float: left"><b>{% trans 'Gateway list' %}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<div class="uc pull-left m-r-5">
<a href="{% url 'assets:domain-gateway-create' pk=object.id %}" class="btn btn-sm btn-primary"> {% trans "Create gateway" %} </a>
</div>
<table class="table table-striped table-bordered table-hover " id="domain_list_table" >
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'Port' %}</th>
<th class="text-center">{% trans 'Protocol' %}</th>
<th class="text-center">{% trans 'Username' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
<script>
function initTable() {
var options = {
ele: $('#domain_list_table'),
columnDefs: [
{targets: 7, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:domain-gateway-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var test_btn = '<a class="btn btn-xs btn-warning m-l-xs btn-test" data-uid="{{ DEFAULT_PK }}">{% trans "Test connection" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
if(rowData.protocol === 'rdp'){
test_btn = '<a class="btn btn-xs btn-warning m-l-xs btn-test" disabled data-uid="{{ DEFAULT_PK }}">{% trans "Test connection" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
}
$(td).html(update_btn + test_btn + del_btn)
}}
],
ajax_url: '{% url "api-assets:gateway-list" %}?domain={{ object.id }}',
columns: [
{data: "id"}, {data: "name" }, {data: 'ip'}, {data: 'port'},
{data: "protocol"}, {data: "username" }, {data: "comment" }, {data: "id"}
],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
}
$(document).ready(function(){
initTable();
})
.on('click', '.btn-delete', function () {
var $this = $(this);
var $data_table = $('#domain_list_table').DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var the_url = '{% url "api-assets:gateway-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
objectDelete($this, name, the_url);
setTimeout( function () {
$data_table.ajax.reload();
}, 3000);
}).on('click', '.btn-test', function () {
var $this = $(this);
var uid = $this.data('uid');
var the_url = '{% url "api-assets:test-gateway-connective" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
APIUpdateAttr({
url: the_url,
method: "GET",
success_message: "{% trans 'Can be connected' %}",
fail_message: "{% trans 'The connection fails' %}"
})
});
</script>
{% endblock %}

View File

@@ -0,0 +1,82 @@
{% extends '_base_list.html' %}
{% load i18n static %}
{% block table_search %}{% endblock %}
{% block help_message %}
<div class="alert alert-info help-message">
{# 网域功能是为了解决部分环境(如:混合云)无法直接连接而新增的功能,原理是通过网关服务器进行跳转登录。<br>#}
{# JMS => 网域网关 => 目标资产#}
{% trans 'The domain function is added to address the fact that some environments (such as the hybrid cloud) cannot be connected directly by jumping on the gateway server.' %}
<br>
{% trans 'JMS => Domain gateway => Target assets' %}
</div>
{% endblock %}
{% block table_container %}
<div class="uc pull-left m-r-5">
<a href="{% url 'assets:domain-create' %}" class="btn btn-sm btn-primary"> {% trans "Create domain" %} </a>
</div>
<table class="table table-striped table-bordered table-hover " id="domain_list_table" >
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'Gateway' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
<script>
function initTable() {
var options = {
ele: $('#domain_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:domain-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 3, createdCell: function (td, cellData, rowData) {
var gateway_list_btn = '<a href="{% url "assets:domain-gateway-list" pk=DEFAULT_PK %}">' + cellData + '</a>';
gateway_list_btn = gateway_list_btn.replace("{{ DEFAULT_PK }}", rowData.id);
$(td).html(gateway_list_btn);
}},
{targets: 5, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:domain-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
}}
],
ajax_url: '{% url "api-assets:domain-list" %}',
columns: [
{data: "id"}, {data: "name" }, {data: "asset_count" },
{data: "gateway_count" }, {data: "comment" }, {data: "id"}
],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
}
$(document).ready(function(){
initTable();
})
.on('click', '.btn-delete', function () {
var $this = $(this);
var $data_table = $('#domain_list_table').DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var the_url = '{% url "api-assets:domain-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
objectDelete($this, name, the_url);
setTimeout( function () {
$data_table.ajax.reload();
}, 3000);
});
</script>
{% endblock %}

View File

@@ -0,0 +1,102 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% load bootstrap3 %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5>{{ action }}</h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<form enctype="multipart/form-data" method="post" class="form-horizontal" action="" >
{% csrf_token %}
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
<h3>{% trans 'Basic' %}</h3>
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.ip layout="horizontal" %}
{% bootstrap_field form.port layout="horizontal" %}
{% bootstrap_field form.protocol layout="horizontal" %}
{% bootstrap_field form.domain layout="horizontal" %}
{% block auth %}
<h3 id="auth_title">{% trans 'Auth' %}</h3>
<div class="auth-fields">
{% bootstrap_field form.username layout="horizontal" %}
{% bootstrap_field form.password layout="horizontal" %}
{% bootstrap_field form.private_key_file layout="horizontal" %}
</div>
{% endblock %}
<h3>{% trans 'Other' %}</h3>
{% bootstrap_field form.is_active layout="horizontal" %}
{% bootstrap_field form.comment layout="horizontal" %}
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-white" type="reset">{% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
var protocol_id = '#' + '{{ form.protocol.id_for_label }}';
var private_key_id = '#' + '{{ form.private_key_file.id_for_label }}';
var port = '#' + '{{ form.port.id_for_label }}';
var username = '#' + '{{ form.username.id_for_label }}';
var password = '#' + '{{ form.password.id_for_label }}';
var auth_title = '#auth_title';
function protocolChange() {
if ($(protocol_id + " option:selected").text() === 'rdp') {
{#$(port).val(3389);#}
$(private_key_id).closest('.form-group').addClass('hidden');
$(username).closest('.form-group').addClass('hidden');
$(password).closest('.form-group').addClass('hidden');
$(auth_title).addClass('hidden');
} else {
{#$(port).val(22);#}
$(private_key_id).closest('.form-group').removeClass('hidden');
$(username).closest('.form-group').removeClass('hidden');
$(password).closest('.form-group').removeClass('hidden');
$(auth_title).removeClass('hidden');
}
}
$(document).ready(function(){
protocolChange();
})
.on('change', protocol_id, function(){
protocolChange();
});
</script>
{% endblock %}

View File

@@ -1,364 +0,0 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
<style type="text/css">
</style>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li>
<a href="{% url 'assets:idc-detail' pk=idc.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
</li>
<li class="active"><a href="{% url 'assets:idc-assets' pk=idc.id %}" class="text-center">
<i class="fa fa-bar-chart-o"></i> {% trans 'IDC assets' %}</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-7" style="padding-left: 0;">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span style="float: left">{% trans 'IDC assets' %} <b>{{ idc.name }} </b><span class="badge"></span></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table table-striped table-bordered table-hover " id="idc_assets_table" >
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th>{% trans 'Hostname' %}</th>
<th>{% trans 'IP' %}</th>
<th>{% trans 'Port' %}</th>
<th>{% trans 'Type' %}</th>
<th>{% trans 'Valid' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div id="actions" class="hide">
<div class="input-group">
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
<option value="delete">{% trans 'Remove selected' %}</option>
</select>
<div class="input-group-btn pull-left" style="padding-left: 5px;">
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-warning">
{% trans 'Submit' %}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Add assets to' %} {{ idc.name }}
</div>
<div class="panel-body">
<table class="table">
<tbody>
<form>
<tr class="no-borders-tr">
<td colspan="2">
<select data-placeholder="{% trans 'Select asset' %}" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for asset in assets_remain %}
<option value="{{ asset.id }}" id="opt_{{ asset.id }}">{{ asset.ip}}:{{ asset.port }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr class="no-borders-tr">
<td colspan="2">
<button type="button" class="btn btn-primary btn-sm btn-asset-attach">{% trans 'Confirm' %}</button>
</td>
</tr>
</form>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script src="{% static 'js/jquery.form.min.js' %}"></script>
<script>
jumpserver.assets_selected = {};
Array.prototype.remove = function(val) {
var index = this.indexOf(val);
if (index > -1) {
this.splice(index, 1);
}
};
function updateIDCAssets(assets) {
var the_url = "{% url 'api-assets:idc-update-assets' pk=idc.id %}";
var body = {
assets: Object.assign([], assets)
};
var $data_table = $("#idc_assets_table").DataTable();
var success = function(data) {
$('.select2-selection__rendered').empty();
$.map(jumpserver.assets_selected, function(asset_ip, index) {
$('#opt_' + index).remove();
$data_table.ajax.reload();
});
jumpserver.groups_selected = {};
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
method: 'PUT',
success: success
});
}
function deleteIDCAssets(assets) {
var the_url = "{% url 'api-assets:idc-update-assets' pk=idc.id %}";
var body = {
assets: Object.assign([], assets)
};
var $data_table = $("#idc_assets_table").DataTable();
var success = function(data) {
$data_table.ajax.reload();
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
method: 'PUT',
success: success
});
}
$(document).ready(function () {
$('.select2').select2()
.on("select2:select", function (evt) {
var data = evt.params.data;
jumpserver.assets_selected[data.id] = data.text;
})
.on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.assets_selected[data.id];
});
var options = {
ele: $('#idc_assets_table'),
buttons: [],
order: [],
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:asset-detail" pk=99991937 %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
$(td).html(detail_btn.replace('99991937', rowData.id));
}},
{targets: 5, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}}],
ajax_url: '{% url "api-assets:asset-list" %}?idc_id={{ idc.id }}',
columns: [{data: function(){return ""}}, {data: "hostname" }, {data: "ip" }, {data: "port" },
{data: "type" }, {data: "is_active" }],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
})
.on('click', '.btn-asset-attach', function () {
if (Object.keys(jumpserver.assets_selected).length === 0) {
return false;
}
var assets=[];
var $data_table = $("#idc_assets_table").DataTable();
$.ajax({
url: '{% url "api-assets:asset-list" %}',
method: 'GET',
data: {"idc_id": {{ idc.id }}},
dataType: 'json',
success: function (result) {
for(var i in result){
if (!isNaN(parseInt(result[i]['id']))) {
assets.push(parseInt(result[i]['id']))
}
}
$.map(jumpserver.assets_selected, function(value, index) {
assets.push(parseInt(index));
});
updateIDCAssets(assets);
}
});
})
.on('click', '#btn_bulk_update', function () {
var action = $("#slct_bulk_update").val();
var $data_table = $("#idc_assets_table").DataTable();
var id_list = [];
var plain_id_list = [];
var assets = [];
$data_table.rows({selected: true}).every(function(){
id_list.push({id: this.data().id});
plain_id_list.push(this.data().id);
});
if (id_list === []) {
return false;
}
$.ajax({
url: '{% url "api-assets:asset-list" %}',
data: {"idc_id": {{ idc.id }}},
dataType: 'json',
method: 'GET',
success: function (result) {
for (var i in result) {
if (!isNaN(result[i]['id'])) {
assets.push(result[i]['id']);
}
}
for (var j in plain_id_list) {
assets.remove(plain_id_list[j])
}
function doDelete() {
swal({
title: "{% trans 'Are you sure?' %}",
text: "{% trans 'This will delete the selected assets !!!' %}",
type: "warning",
showCancelButton: true,
confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false
}, function() {
var success = function() {
var msg = "{% trans 'Asset Deleted.' %}";
swal("{% trans 'Asset Delete' %}", msg, "success");
$('#idc_assets_table').DataTable().ajax.reload();
};
var fail = function() {
var msg = "{% trans 'Asset Deleting failed.' %}";
swal("{% trans 'Asset Delete' %}", msg, "error");
};
var url_delete = "{% url 'api-assets:idc-update-assets' pk=idc.id %}";
var body = {
assets: Object.assign([], assets)
};
APIUpdateAttr({url: url_delete, body: JSON.stringify(body), method: 'PUT', success: success, error: fail});
jumpserver.checked = false;
});
}
function doUpdate() {
$('#asset_bulk_update_modal').modal('show');
}
switch (action) {
case 'delete':
doDelete();
break;
case 'update':
doUpdate();
break;
default:
break;
}
}
});
})
.on('click', '#btn_asset_bulk_update', function () {
var json_data = $("#fm_asset_bulk_update").serializeObject();
var body = {};
body.enable_otp = (json_data.enable_otp === 'on')? true: false;
if (json_data.type != '') {
body.type = json_data.type;
}
if (json_data.groups != undefined) {
body.groups = json_data.groups;
}
if (typeof body.groups === 'string') {
body.groups = [parseInt(body.groups)]
} else if(typeof body.groups === 'array') {
var new_groups = body.groups.map(Number);
body.groups = new_groups;
}
if (json_data.users != undefined) {
body.users = json_data.users;
}
if (typeof body.users === 'string') {
body.users = [parseInt(body.users)]
} else if(typeof body.users === 'array') {
var new_users = body.users.map(Number);
body.users = new_users;
}
if (json_data.tags != undefined) {
body.tags = json_data.tags;
}
if (typeof body.tags == 'string') {
body.tags = [parseInt(body.tags)];
} else if (typeof body.tags === 'array') {
var new_tags = body.tags.map(Number);
body.tags = new_tags;
}
var $data_table = $('#asset_list_table').DataTable();
var post_list = [];
$data_table.rows({selected: true}).every(function(){
var content = Object.assign({id: this.data().id}, body);
post_list.push(content);
});
if (post_list === []) {
return false
}
var the_url = "{% url 'api-assets:asset-list' %}";
var success = function() {
var msg = "{% trans 'The selected assets has been updated successfully.' %}";
swal("{% trans 'Asset Updated' %}", msg, "success");
$('#asset_list_table').DataTable().ajax.reload();
jumpserver.checked = false;
};
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success});
$('#asset_bulk_update_modal').modal('hide');
});
</script>
{% endblock %}

View File

@@ -1,73 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% load bootstrap3 %}
{% block custom_head_css_js %}
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-10">
<div class="ibox float-e-margins">
<div id="ibox-content" class="ibox-title">
<h5> {{ action }}</h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<div class="panel blank-panel">
<div class="panel-body">
<div class="tab-content">
<div id="tab-1" class="ibox float-e-margins tab-pane active"></div>
<form id="IDCForm" method="post" class="form-horizontal">
{% csrf_token %}
<h3 class="widget-head-color-box">基本信息</h3>
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.address layout="horizontal" %}
{% bootstrap_field form.contact layout="horizontal" %}
{% bootstrap_field form.phone layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3 class="widget-head-color-box">IP段</h3>
{% bootstrap_field form.operator layout="horizontal" %}
{% bootstrap_field form.intranet layout="horizontal" %}
{% bootstrap_field form.extranet layout="horizontal" %}
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function () {
$('.select2').select2();
})
</script>
{% endblock %}

View File

@@ -1,156 +0,0 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li class="active">
<a href="{% url 'assets:idc-detail' pk=idc.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
</li>
<li>
<a href="{% url 'assets:idc-assets' pk=idc.id %}" class="text-center">
<i class="fa fa-bar-chart-o"></i> {% trans 'IDC assets' %}
</a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'assets:idc-update' pk=idc.id %}"><i class="fa fa-edit"></i>Update</a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-danger btn-delete-idc">
<i class="fa fa-edit"></i>Delete
</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-7" style="padding-left: 0;">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label"><b>{{ idc.name }}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table">
<tbody>
<tr class="no-borders-tr">
<td width="20%">{% trans 'Name' %}:</td>
<td><b>{{ idc.name }}</b></td>
</tr>
<tr>
<td>{% trans 'Bandwidth' %}:</td>
<td><b>{{ idc.bandwidth }}</b></td>
</tr>
<tr>
<td>{% trans 'Contact' %}:</td>
<td><b>{{ idc.contact }}</b></td>
</tr>
<tr>
<td>{% trans 'Phone' %}:</td>
<td><b>{{ idc.phone }}</b></td>
</tr>
<tr>
<td>{% trans 'Address' %}:</td>
<td><b>{{ idc.address }}</b></td>
</tr>
<tr>
<td>{% trans 'Intranet' %}:</td>
<td><b>{{ idc.Intranet }}</b></td>
</tr>
<tr>
<td>{% trans 'Extranet' %}:</td>
<td><b>{{ idc.extranet }}</b></td>
</tr>
<tr>
<td>{% trans 'Operator' %}:</td>
<td><b>{{ idc.operator }}</b></td>
</tr>
<tr>
<td>{% trans 'Date created' %}:</td>
<td><b>{{ system_user.date_created }}</b></td>
</tr>
<tr>
<td>{% trans 'Created by' %}:</td>
<td><b>{{ asset_group.created_by }}</b></td>
</tr>
<tr>
<td>{% trans 'Comment' %}:</td>
<td><b>{{ system_user.comment }}</b></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
function idcDelete(name, url) {
function doDelete() {
var body = {};
var success = function() {
swal('Deleted!', "[ "+name+"]"+" has been deleted ", "success");
window.location.href="{% url 'assets:idc-list' %}";
};
var fail = function() {
swal("Failed", "Delete"+"[ "+name+" ]"+"failed", "error");
};
APIUpdateAttr({
url: url,
body: JSON.stringify(body),
method: 'DELETE',
success: success,
error: fail
});
}
swal({
title: 'Are you sure delete ?',
text: " [" + name + "] ",
type: "warning",
showCancelButton: true,
cancelButtonText: 'Cancel',
confirmButtonColor: "#DD6B55",
confirmButtonText: 'Confirm',
closeOnConfirm: false
}, function () {
doDelete()
});
}
$(document).ready(function () {
$('.select2').select2();
})
.on('click', '.btn-delete-idc', function () {
var name = $('.idc-details > tbody > tr').attr("data-name");
var id = {{ idc.id }};
var the_url = '{% url "api-assets:idc-detail" pk=99991937 %}'.replace(99991937, id);
idcDelete(name, the_url);
});
</script>
{% endblock %}

View File

@@ -1,129 +0,0 @@
{% extends '_base_list.html' %}
{% load i18n static %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
{% endblock %}
{% block table_search %}{% endblock %}
{% block table_container %}
<div class="uc pull-left m-l-5 m-r-5">
<a href="{% url "assets:idc-create" %}" class="btn btn-sm btn-primary"> {% trans "Create IDC" %} </a>
</div>
<table class="table table-striped table-bordered table-hover " id="idc_list_table" >
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th class="text-center"><a href="{% url 'assets:idc-list' %}?sort=name">{% trans 'Name' %}</a></th>
<th class="text-center">{% trans 'Asset num' %}</th>
<th class="text-center">{% trans 'Contact' %}</th>
<th class="text-center">{% trans 'Phone' %}</th>
<th class="text-center">{% trans 'Operator' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div id="actions" class="hide">
<div class="input-group">
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
<option value="delete">{% trans 'Delete selected' %}</option>
</select>
<div class="input-group-btn pull-left" style="padding-left: 5px;">
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
{% trans 'Submit' %}
</button>
</div>
</div>
</div>
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function(){
var options = {
ele: $('#idc_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:idc-detail" pk=99991937 %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('99991937', rowData.id));
}},
{targets: 6, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:idc-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_idc_delete" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData);
$(td).html(update_btn + del_btn)
}}],
ajax_url: '{% url "api-assets:idc-list" %}',
columns: [{data: function(){return ""}}, {data: "name" }, {data: "assets_amount" }, {data: "contact" }, {data: "phone" },
{data: "operator" }, {data: "id" }],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
})
.on('click', '.btn_idc_delete', function () {
var $this = $(this);
var $data_table = $('#idc_list_table').DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var the_url = '{% url "api-assets:idc-detail" pk=99991937 %}'.replace('99991937', uid);
objectDelete($this, name, the_url);
setTimeout( function () {
$data_table.ajax.reload();
}, 3000);
})
.on('click', '#btn_bulk_update', function () {
var action = $('#slct_bulk_update').val();
var $data_table = $('#idc_list_table').DataTable();
var id_list = [];
var plain_id_list = [];
$data_table.rows({selected: true}).every(function(){
id_list.push({id: this.data().id});
plain_id_list.push(this.data().id);
});
if (id_list === []) {
return false;
}
var the_url = "{% url 'api-assets:idc-list' %}";
function doDelete() {
swal({
title: "{% trans 'Are you sure?' %}",
text: "{% trans 'This will delete the selected idc' %}",
type: "warning",
showCancelButton: true,
confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false
}, function() {
var success = function() {
var msg = "{% trans 'IDC Deleted.' %}";
swal("{% trans 'IDC Delete' %}", msg, "success");
$('#idc_list_table').DataTable().ajax.reload();
};
var fail = function() {
var msg = "{% trans 'IDC Deleting failed.' %}";
swal("{% trans 'IDC Delete' %}", msg, "error");
};
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail});
$data_table.ajax.reload();
jumpserver.checked = false;
});
}
switch (action) {
case 'delete':
doDelete();
break;
default:
break;
}
});
</script>
{% endblock %}

View File

@@ -0,0 +1,47 @@
{% extends '_base_create_update.html' %}
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% block form %}
<form id="groupForm" method="post" class="form-horizontal">
{% csrf_token %}
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.value layout="horizontal" %}
{% bootstrap_field form.assets layout="horizontal" %}
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
{% include 'assets/_asset_list_modal.html' %}
{% endblock %}
{% block custom_foot_js %}
<script type="text/javascript">
$(document).ready(function () {
$('.select2').select2({
closeOnSelect: false
})
}).on('click', '.select2-selection__rendered', function (e) {
e.preventDefault();
$("#asset_list_modal").modal();
})
.on('click', '#btn_asset_modal_confirm', function () {
var assets = asset_table2.selected;
$('.select2 option:selected').each(function (i, data) {
assets.push($(data).attr('value'))
});
$.each(assets, function (id, data) {
$('.select2').val(assets).trigger('change');
});
$("#asset_list_modal").modal('hide');
})
</script>
{% endblock %}

View File

@@ -0,0 +1,70 @@
{% extends '_base_list.html' %}
{% load i18n static %}
{% block table_search %}{% endblock %}
{% block table_container %}
<div class="uc pull-left m-r-5">
<a href="{% url 'assets:label-create' %}" class="btn btn-sm btn-primary"> {% trans "Create label" %} </a>
</div>
<table class="table table-striped table-bordered table-hover " id="label_list_table" >
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Value' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
<script>
function initTable() {
var options = {
ele: $('#label_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
{# var detail_btn = '<a href="{% url "assets:label-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';#}
var detail_btn = '<a>' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 4, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:label-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
}}
],
ajax_url: '{% url "api-assets:label-list" %}?sort=name',
columns: [
{data: "id"}, {data: "name" }, {data: "value" },
{data: "asset_count" }, {data: "id"}
],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
}
$(document).ready(function(){
initTable();
})
.on('click', '.btn-delete', function () {
var $this = $(this);
var $data_table = $('#label_list_table').DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var the_url = '{% url "api-assets:label-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
objectDelete($this, name, the_url);
setTimeout( function () {
$data_table.ajax.reload();
}, 3000);
});
</script>
{% endblock %}

View File

@@ -1,10 +1,11 @@
{% extends 'base.html' %}
{% load common_tags %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
@@ -16,13 +17,15 @@
<li>
<a href="{% url 'assets:system-user-detail' pk=system_user.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
</li>
<li class="active"><a href="{% url 'assets:system-user-asset' pk=system_user.id %}" class="text-center">
<i class="fa fa-bar-chart-o"></i> {% trans 'Attached assets' %}</a>
<li class="active">
<a href="{% url 'assets:system-user-asset' pk=system_user.id %}" class="text-center">
<i class="fa fa-bar-chart-o"></i> {% trans 'Assets' %}
</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-7" style="padding-left: 0;">
<div class="col-sm-8" style="padding-left: 0;">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span style="float: left">{% trans 'Assets of ' %} <b>{{ system_user.name }} </b><span class="badge">{{ paginator.count }}</span></span>
@@ -52,85 +55,70 @@
</tr>
</thead>
<tbody>
{% for asset in page_obj %}
<tr>
<td>{{ asset.hostname }}</td>
<td>{{ asset.ip }}</td>
<td>{{ asset.port }}</td>
<td>
<i class="fa fa-check text-navy"></i>
</td>
<td>
<button class="btn btn-danger pull-right btn-xs {% if asset.is_inherit_from_asset_groups %} disabled {% endif %}" type="button"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{# <div class="row">#}
{# {% include '_pagination.html' %}#}
{# </div>#}
</div>
</div>
</div>
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Attach to assets ' %}
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %}
</div>
<div class="panel-body">
<table class="table">
<tbody>
<form>
<tr class="no-borders-tr">
<td colspan="2">
<select data-placeholder="{% trans 'Select asset' %}" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for asset in assets_remain %}
<option value="{{ asset.id }}">{{ asset.ip}}:{{ asset.port }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr class="no-borders-tr">
<td colspan="2">
<button type="button" class="btn btn-primary btn-sm btn-add-asset2system-user">{% trans 'Confirm' %}</button>
</td>
</tr>
</form>
<tr class="no-borders-tr">
<td width="50%">{% trans 'Test assets connective' %}:</td>
<td>
<span style="float: right">
<button type="button" class="btn btn-primary btn-xs btn-test-connective" style="width: 54px">{% trans 'Test' %}</button>
</span>
</td>
</tr>
{% if system_user.auto_push %}
<tr>
<td width="50%">{% trans 'Push system user now' %}:</td>
<td>
<span style="float: right">
<button type="button" class="btn btn-primary btn-xs btn-push" style="width: 54px">{% trans 'Push' %}</button>
</span>
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Attach to asset groups' %}
<i class="fa fa-info-circle"></i> {% trans 'Nodes' %}
</div>
<div class="panel-body">
<table class="table group_edit">
<table class="table node_edit" id="add-asset2group">
<tbody>
<form>
<tr>
<td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Add asset group' %}" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for asset_group in asset_groups_remain %}
<option value="{{ asset_group.id }}">{{ asset_group.name }}</option>
<select data-placeholder="{% trans 'Add to node' %}" id="node_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for node in nodes_remain %}
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td colspan="2" class="no-borders">
<button type="button" class="btn btn-info btn-sm" id="btn_add_user_group">{% trans 'Attach AssetGroup' %}</button>
<button type="button" class="btn btn-info btn-sm" id="btn-add-to-node">{% trans 'Confirm' %}</button>
</td>
</tr>
</form>
{% for asset_group in asset_groups %}
{% for node in system_user.nodes.all|sort %}
<tr>
<td ><b class="bdg_asset_groups" data-gid={{ asset_group.id }}>{{ asset_group.name }}</b></td>
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node }}</b></td>
<td>
<button class="btn btn-danger pull-right btn-xs btn-leave-system_user" type="button"><i class="fa fa-minus"></i></button>
<button class="btn btn-danger pull-right btn-xs btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
@@ -144,79 +132,65 @@
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
jumpserver.assets_selected = {};
jumpserver.asset_groups_selected = {};
Array.prototype.remove = function(val) {
var index = this.indexOf(val);
if (index > -1) {
this.splice(index, 1);
}
};
Array.prototype.unique = function(){
var res = [];
var json = {};
for(var i = 0; i < this.length; i++){
if(!json[this[i]]){
res.push(this[i]);
json[this[i]] = 1;
}
}
return res;
};
function objectDelete(obj, name, url, data) {
function doDelete() {
var body = data;
var success = function() {
swal('Deleted!', "[ "+name+"]"+" has been deleted ", "success");
$(obj).parent().parent().remove();
};
var fail = function() {
swal("Failed", "Delete"+"[ "+name+" ]"+"failed", "error");
};
APIUpdateAttr({
url: url,
body: JSON.stringify(body),
method: 'PATCH',
success: success,
error: fail
});
}
swal({
title: 'Are you sure delete ?',
text: " [" + name + "] ",
type: "warning",
showCancelButton: true,
cancelButtonText: 'Cancel',
confirmButtonColor: "#DD6B55",
confirmButtonText: 'Confirm',
closeOnConfirm: false
}, function () {
doDelete()
});
function initAssetsTable() {
var unreachable = {{ system_user.unreachable_assets|safe }};
var options = {
ele: $('#system_user_list'),
buttons: [],
order: [],
columnDefs: [
{targets: 0, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:asset-detail" pk=DEFAULT_PK %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 3, createdCell: function (td, cellData) {
if (unreachable.indexOf(cellData) >= 0) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 4, createdCell: function (td, cellData) {
var push_btn = '';
{% if system_user.auto_push %}
push_btn = '<a class="btn btn-xs btn-primary btn-push-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Push" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
{% endif %}
var test_btn = ' <a class="btn btn-xs btn-info btn-test-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Test" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
{#var unbound_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-asset-unbound" data-uid="{{ DEFAULT_PK }}">{% trans "Unbound" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);#}
$(td).html(push_btn + test_btn);
}}
],
ajax_url: '{% url "api-assets:system-user-assets" pk=system_user.id %}',
columns: [{data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "hostname" }, {data: "id"}],
op_html: $('#actions').html()
};
jumpserver.initServerSideDataTable(options);
}
function updateSystemUserAssetGroup(asset_groups) {
var the_url = "{% url 'api-assets:systemuser-update-assetgroups' pk=system_user.id %}";
function updateSystemUserNode(nodes) {
var the_url = "{% url 'api-assets:system-user-detail' pk=system_user.id %}";
var body = {
asset_groups: Object.assign([], asset_groups)
nodes: Object.assign([], nodes)
};
var success = function(data) {
// remove all the selected groups from select > option and rendered ul element;
$('.select2-selection__rendered').empty();
$('#groups_selected').val('');
$.map(jumpserver.asset_groups_selected, function(asset_groups, index) {
$('#node_selected').val('');
$.map(jumpserver.nodes_selected, function(node_name, index) {
$('#opt_' + index).remove();
$('.system-user-table tbody').append(
// change tr html of user groups.
$('.node_edit tbody').append(
'<tr>' +
'<td><b class="bdg_asset_groups" data-sid="' + index + '">' + asset_groups + '</b></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn-leave-system_user" type="button"><i class="fa fa-minus"></i></button></td>' +
'<td><b class="bdg_node" data-gid="' + index + '">' + node_name + '</b></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button></td>' +
'</tr>'
)
});
jumpserver.assets_selected = {};
// clear jumpserver.nodes_selected
jumpserver.nodes_selected = {};
};
APIUpdateAttr({
url: the_url,
@@ -224,148 +198,121 @@ function updateSystemUserAssetGroup(asset_groups) {
success: success
});
}
jumpserver.nodes_selected = {};
$(document).ready(function () {
$('.select2').select2()
.on("select2:select", function (evt) {
var data = evt.params.data;
jumpserver.assets_selected[data.id] = data.text;
jumpserver.asset_groups_selected[data.id] = data.text;
})
.on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.assets_selected[data.id];
delete jumpserver.asset_groups_selected[data.id];
});
var options = {
ele: $('#system_user_list'),
buttons: [],
order: [],
columnDefs: [
{targets: 0, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:asset-detail" pk=99991937 %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
$(td).html(detail_btn.replace('99991937', rowData.id));
}},
{targets: 3, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 4, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:asset-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', rowData.id);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-aid="99991937">{% trans "Delete" %}</a>'.replace('99991937', rowData.id);
$(td).html(update_btn + del_btn)
}}
],
ajax_url: '{% url "api-assets:asset-list" %}?system_user_id={{ system_user.id }}',
columns: [{data: "hostname" }, {data: "ip" }, {data: "port" }, {data: function () { return ""; } }, {data: "id"}],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
.on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.nodes_selected[data.id] = data.text;
})
.on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.nodes_selected[data.id];
});
initAssetsTable();
})
.on('click', '.btn-add-asset2system-user', function () {
if (Object.keys(jumpserver.assets_selected).length === 0) {
return false;
}
var $data_table = $("#system_user_list").DataTable();
var assets = [];
$.ajax({
url: '{% url "api-assets:asset-list" %}?system_user_id={{ system_user.id }}',
method: 'GET',
dataType: 'json',
success: function (result) {
for(var i in result){
if (!isNaN(parseInt(result[i]['id']))) {
assets.push(parseInt(result[i]['id']))
}
}
$.map(jumpserver.assets_selected, function(value, index) {
assets.push(parseInt(index));
});
assets.unique();
var the_url = "{% url 'api-assets:systemuser-update-assets' pk=system_user.id %}";
var body = {"assets": assets};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
method: 'PATCH'
});
$data_table.ajax.reload();
}
});
.on('click', '.btn-push', function () {
var the_url = "{% url 'api-assets:system-user-push' pk=system_user.id %}";
var error = function (data) {
alert(data)
};
var success = function (data) {
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({
url: the_url,
error: error,
method: 'GET',
success: success
});
})
.on('click', '.btn_asset_delete', function () {
var $this = $(this);
var the_url = "{% url 'api-assets:systemuser-update-assets' pk=system_user.id %}";
var name = $(this).closest("tr").find(":nth-child(1) > a").html();
var $data_table = $("#system_user_list").DataTable();
var assets = [];
$('#system_user_list > tbody > tr').map(function () {
assets.push(parseInt($(this).closest("tr").find(":nth-child(1) > a").attr("data-aid")))
});
var delete_asset_id = $(this).data('aid');
assets.remove(delete_asset_id);
assets.unique();
var data = {"assets": assets};
objectDelete($this, name, the_url, data);
$data_table.ajax.reload();
.on('click', '.btn-test-connective', function () {
var the_url = "{% url 'api-assets:system-user-connective' pk=system_user.id %}";
var error = function (data) {
alert(data)
};
var success = function (data) {
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({
url: the_url,
error: error,
method: 'GET',
success: success
});
})
.on('click', '#btn_add_user_group', function () {
jumpserver.assets_selected = {};
if (Object.keys(jumpserver.asset_groups_selected).length === 0) {
return false;
}
asset_groups = [];
$.ajax({
url: '{% url "api-assets:systemuser-update-assetgroups" pk=system_user.id %}',
method: 'GET',
dataType: 'json',
success: function (result) {
for (var i in result['asset_groups']) {
if (!isNaN(result['asset_groups'][i])) {
asset_groups.push(parseInt(result['asset_groups'][i]));
}
}
$.map(jumpserver.asset_groups_selected, function(value, index) {
asset_groups.push(parseInt(index));
});
asset_groups.unique();
console.log(asset_groups);
var the_url = '{% url "api-assets:systemuser-update-assetgroups" pk=system_user.id %}';
var body = {"asset_groups": asset_groups};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
method: 'PATCH'
});
{# TODO: reload the table #}
{# window.location.href="{% url 'assets:system-user-asset' pk=system_user.id %}"#}
}
});
.on('click', '.btn-remove-from-node', function() {
var $this = $(this);
var $tr = $this.closest('tr');
var $badge = $tr.find('.bdg_node');
var gid = $badge.data('gid');
var node_name = $badge.html() || $badge.text();
$('#groups_selected').append(
'<option value="' + gid + '" id="opt_' + gid + '">' + node_name + '</option>'
);
$tr.remove();
var nodes = $('.bdg_node').map(function () {
return $(this).data('gid');
}).get();
updateSystemUserNode(nodes);
})
.on('click', '#btn-add-to-node', function() {
if (Object.keys(jumpserver.nodes_selected).length === 0) {
return false;
}
var nodes = $('.bdg_node').map(function() {
return $(this).data('gid');
}).get();
$.map(jumpserver.nodes_selected, function(value, index) {
nodes.push(index);
});
updateSystemUserNode(nodes);
})
.on('click', '.btn-push-asset', function () {
var $this = $(this);
var asset_id = $this.data('uid');
var the_url = "{% url 'api-assets:system-user-push-to-asset' pk=object.id aid=DEFAULT_PK %}";
the_url = the_url.replace("{{ DEFAULT_PK }}", asset_id);
var success = function (data) {
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
var error = function (data) {
alert(data)
};
APIUpdateAttr({
url: the_url,
method: 'GET',
success: success,
error: error
})
})
.on('click', '.btn-test-asset', function () {
var $this = $(this);
var asset_id = $this.data('uid');
var the_url = "{% url 'api-assets:system-user-test-to-asset' pk=object.id aid=DEFAULT_PK %}";
the_url = the_url.replace("{{ DEFAULT_PK }}", asset_id);
var success = function (data) {
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
var error = function (data) {
alert(data)
};
APIUpdateAttr({
url: the_url,
method: 'GET',
success: success,
error: error
})
})
.on('click', '.btn-leave-system_user', function () {
var $this = $(this);
var $tr = $this.closest('tr');
var $badge = $tr.find('.bdg_asset_groups');
var sid = $badge.data('gid');
var name = $badge.html() || $badge.text();
$('system-user-table').append(
'<option value="' + sid + '" id="opt_' + sid + '">' + name + '</option>'
);
$tr.remove();
var asset_groups = $('.bdg_asset_groups').map(function () {
return $(this).data('gid');
}).get();
updateSystemUserAssetGroup(asset_groups);
});
</script>
{% endblock %}

View File

@@ -19,16 +19,21 @@
</li>
<li>
<a href="{% url 'assets:system-user-asset' pk=system_user.id %}" class="text-center">
<i class="fa fa-bar-chart-o"></i> {% trans 'Attached assets' %}
<i class="fa fa-bar-chart-o"></i> {% trans 'Assets' %}
</a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'assets:system-user-update' pk=system_user.id %}"><i class="fa fa-edit"></i>Update</a>
<a class="btn btn-outline btn-default" href="{% url 'assets:system-user-update' pk=system_user.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-danger btn-del">
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-7" style="padding-left: 0;">
<div class="col-sm-8" style="padding-left: 0;">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label"><b>{{ system_user.name }}</b></span>
@@ -57,20 +62,20 @@
<td>{% trans 'Username' %}:</td>
<td><b>{{ system_user.username }}</b></td>
</tr>
<tr>
<td>{% trans 'Login mode' %}:</td>
<td><b>{{ system_user.get_login_mode_display }}</b></td>
</tr>
<tr>
<td>{% trans 'Protocol' %}:</td>
<td><b>{{ system_user.protocol }}</b></td>
<td><b id="id_protocol_type">{{ system_user.protocol }}</b></td>
</tr>
<tr>
<td>{% trans 'Auto push' %}:</td>
<td><b>{{ system_user.auto_push|yesno:"Yes,No,Unknown" }}</b></td>
</tr>
<tr>
<tr class="only-ssh">
<td>{% trans 'Sudo' %}:</td>
<td><b>{{ system_user.sudo }}</b></td>
</tr>
{% if system_user.shell %}
<tr>
<tr class="only-ssh">
<td>{% trans 'Shell' %}:</td>
<td><b>{{ system_user.shell }}</b></td>
</tr>
@@ -105,37 +110,45 @@
</div>
</div>
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary">
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary ">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %}
</div>
<div class="panel-body">
<table class="table">
<tbody>
<tr class="no-borders-tr">
<td width="50%">{% trans 'Get manual install script' %}:</td>
<tr class="only-ssh">
<td width="50%">{% trans 'Auto push' %}:</td>
<td>
<span style="float: right">
<button type="button" class="btn btn-primary btn-xs" style="width: 54px">{% trans 'Get' %}</button>
<span class="pull-right">
<div class="switch">
<div class="onoffswitch">
<input type="checkbox" {% if system_user.auto_push %} checked {% endif %} class="onoffswitch-checkbox" id="btn-auto-push">
<label class="onoffswitch-label" for="btn-auto-push">
<span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
</label>
</div>
</div>
</span>
</td>
</tr>
<tr>
<td width="50%">{% trans 'Retest asset connectivity' %}:</td>
{% if system_user.auto_push %}
<tr class="only-ssh">
<td width="50%">{% trans 'Push system user now' %}:</td>
<td>
<span style="float: right">
<button type="button" class="btn btn-primary btn-xs" style="width: 54px">{% trans 'Start' %}</button>
<button type="button" class="btn btn-primary btn-xs btn-push" style="width: 54px">{% trans 'Push' %}</button>
</span>
</td>
</tr>
<tr>
<td width="50%">{% trans 'Reset private key' %}:</td>
{% endif %}
<tr class="only-ssh">
<td width="50%">{% trans 'Test assets connective' %}:</td>
<td>
<span style="float: right">
<button type="button" class="btn btn-primary btn-xs" style="width: 54px">{% trans 'Reset' %}</button>
<button type="button" class="btn btn-primary btn-xs btn-test-connective" style="width: 54px">{% trans 'Test' %}</button>
</span>
</td>
</tr>
@@ -145,34 +158,136 @@
</div>
</div>
</div>
{% if system_user.protocol != 'rdp' %}
<div class="col-sm-4" style="padding-left: 0; padding-right: 0">
<div class="panel panel-info">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Command filter' %}
</div>
<div class="panel-body">
<table class="table">
<tbody>
<form>
<tr>
<td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Binding command filters' %}" id="command_filters_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for cf in cmd_filters_remain %}
<option value="{{ cf.id }}" id="opt_{{ cf.id }}" >{{ cf }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td colspan="2" class="no-borders">
<button type="button" class="btn btn-info btn-sm" id="btn-binding-command-filters">{% trans 'Confirm' %}</button>
</td>
</tr>
</form>
{% for cf in object.cmd_filters.all %}
<tr>
<td><b class="bdg-command-filters" data-gid={{ cf.id }}>{{ cf }}</b></td>
<td>
<button class="btn btn-danger pull-right btn-xs btn-unbound-command-filter" data-gid={{ cf.id }} type="button"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
{# function switch_user_status(obj) {#}
{# var status = $(obj).prop('checked');#}
{##}
{# $.ajax({#}
{# url: "{% url 'users:user-active-api' pk=user.id %}",#}
{# type: "PUT",#}
{# data: {#}
{# 'is_active': status#}
{# },#}
{# success: function (data, status) {#}
{# console.log(data)#}
{# },#}
{# error: function () {#}
{# console.log('error')#}
{# }#}
{# })#}
{# }#}
$(document).ready(function () {
$('.select2').select2();
});
</script>
<script>
function updateCommandFilters(command_filters) {
var the_url = "{% url 'api-assets:system-user-detail' pk=system_user.id %}";
var body = {
cmd_filters: Object.assign([], command_filters)
};
var success = function(data) {
location.reload();
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
success: success
});
}
$(document).ready(function () {
if($('#id_protocol_type').text() === 'rdp'){
$('.only-ssh').addClass('hidden')
}
$(".panel-body .table tr:visible:first").addClass('no-borders-tr');
$('.select2').select2()
})
.on('click', '#btn-auto-push', function () {
var checked = $(this).prop('checked');
var the_url = "{% url 'api-assets:system-user-detail' pk=system_user.id %}";
var body = {
'auto_push': checked
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body)
});
}).on('click', '.btn-del', function () {
var $this = $(this);
var name = "{{ system_user.name}}";
var uid = "{{ system_user.id }}";
var the_url = '{% url "api-assets:system-user-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
var redirect_url = "{% url 'assets:system-user-list' %}";
objectDelete($this, name, the_url, redirect_url);
})
.on('click', '.btn-push', function () {
var the_url = "{% url 'api-assets:system-user-push' pk=system_user.id %}";
var success = function (data) {
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({
url: the_url,
method: 'GET',
success: success,
flash_message: false
});
})
.on('click', '.btn-test-connective', function () {
var the_url = "{% url 'api-assets:system-user-connective' pk=system_user.id %}";
var success = function (data) {
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
};
APIUpdateAttr({
url: the_url,
method: 'GET',
success: success,
flash_message: false
});
}).on('click', '#btn-binding-command-filters', function () {
var new_selected_cmd_filters = $.map($('#command_filters_selected').select2('data'), function (i) {
return i.id;
});
var origin_cmd_filters = $.map($(".bdg-command-filters"), function (s) {
return $(s).data('gid')
});
var command_filters = new_selected_cmd_filters.concat(origin_cmd_filters);
updateCommandFilters(command_filters)
}).on('click', '.btn-unbound-command-filter', function () {
var unbound_command_filter = $(this).data('gid');
var origin_command_filters = $.map($(".bdg-command-filters"), function (s) {
return $(s).data('gid')
});
var command_filters = $.grep(origin_command_filters, function (n, i) {
return n !== unbound_command_filter
});
updateCommandFilters(command_filters)
})
</script>
{% endblock %}

View File

@@ -1,11 +1,23 @@
{% extends '_base_list.html' %}
{% load i18n %}
{% block help_message %}
<div class="alert alert-info help-message">
{# 系统用户是 Jumpserver跳转登录资产时使用的用户可以理解为登录资产用户如 web, sa, dba(`ssh web@some-host`), 而不是使用某个用户的用户名跳转登录服务器(`ssh xiaoming@some-host`);#}
{# 简单来说是 用户使用自己的用户名登录Jumpserver, Jumpserver使用系统用户登录资产。#}
{# 系统用户创建时,如果选择了自动推送 Jumpserver会使用ansible自动推送系统用户到资产中如果资产(交换机、windows)不支持ansible, 请手动填写账号密码。#}
{# 目前还不支持Windows的自动推送#}
{% trans 'System user is Jumpserver jump login assets used by the users, can be understood as the user login assets, such as web, sa, the dba (` ssh web@some-host `), rather than using a user the username login server jump (` ssh xiaoming@some-host `); '%}
{% trans 'In simple terms, users log into Jumpserver using their own username, and Jumpserver uses system users to log into assets. '%}
{% trans 'When system users are created, if you choose auto push Jumpserver to use ansible push system users into the asset, if the asset (Switch, Windows) does not support ansible, please manually fill in the account password. Automatic push for Windows is not currently supported.' %}
</div>
{% endblock %}
{% block table_search %}
{% endblock %}
{% block table_container %}
<div class="uc pull-left m-l-5 m-r-5">
<div class="uc pull-left m-r-5">
<a href="{% url 'assets:system-user-create' %}" class="btn btn-sm btn-primary "> {% trans "Create system user" %} </a>
</div>
<table class="table table-striped table-bordered table-hover " id="system_user_list_table" >
@@ -16,8 +28,12 @@
</th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Username' %}</th>
<th class="text-center">{% trans 'Protocol' %}</th>
<th class="text-center">{% trans 'Login mode' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'Reachable' %}</th>
<th class="text-center">{% trans 'Unreachable' %}</th>
<th class="text-center">{% trans 'Ratio' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
@@ -25,54 +41,78 @@
<tbody>
</tbody>
</table>
<div id="actions" class="hide">
<div class="input-group">
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
<option value="delete">{% trans 'Delete selected' %}</option>
<option value="update">{% trans 'Update selected' %}</option>
</select>
<div class="input-group-btn pull-left" style="padding-left: 5px;">
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
{% trans 'Submit' %}
</button>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function(){
function initTable() {
var options = {
ele: $('#system_user_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:system-user-detail" pk=99991937 %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('99991937', rowData.id));
var detail_btn = '<a href="{% url "assets:system-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 5, createdCell: function (td, cellData) {
var innerHtml = cellData.length > 30 ? cellData.substring(0, 30) + '...': cellData;
{targets: 6, createdCell: function (td, cellData) {
var innerHtml = "";
if (cellData !== 0) {
innerHtml = "<span class='text-navy'>" + cellData + "</span>";
} else {
innerHtml = "<span>" + cellData + "</span>";
}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData +'">' + innerHtml + '</span>');
}},
{targets: 7, createdCell: function (td, cellData) {
var innerHtml = "";
if (cellData !== 0) {
innerHtml = "<span class='text-danger'>" + cellData + "</span>";
} else {
innerHtml = "<span>" + cellData + "</span>";
}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}},
{targets: 6, createdCell: function (td, cellData, rowData) {
{# var script_btn = '<a href="{% url "assets:system-user-update" pk=99991937 %}" class="btn btn-xs btn-primary">{% trans "Script" %}</a>'.replace('99991937', cellData);#}
var update_btn = '<a href="{% url "assets:system-user-update" pk=99991937 %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData);
{targets: 8, createdCell: function (td, cellData, rowData) {
var val = 0;
var innerHtml = "";
var total = rowData.assets_amount;
var reachable = rowData.reachable_amount;
if (total !== 0) {
val = reachable/total * 100;
}
if (val === 100) {
innerHtml = "<span class='text-navy'>" + val + "% </span>";
} else {
var num = new Number(val);
innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>";
}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}},
{targets: 10, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:system-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
}}],
ajax_url: '{% url "api-assets:system-user-list" %}',
columns: [{data: "id" }, {data: "name" }, {data: "username" }, {data: "assets_amount" }, {data: function () { return "3"}},
{data: "comment" }, {data: "id" }],
columns: [
{data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "get_login_mode_display"}, {data: "assets_amount" },
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" }
],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
}
$(document).ready(function(){
initTable();
})
.on('click', '.btn_admin_user_delete', function () {
var $this = $(this);
var $data_table = $('#idc_list_table').DataTable();
var $data_table = $('#cluster_list_table').DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var the_url = '{% url "api-assets:system-user-detail" pk=99991937 %}'.replace('99991937', uid);
var the_url = '{% url "api-assets:system-user-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
objectDelete($this, name, the_url);
setTimeout( function () {
$data_table.ajax.reload();
@@ -98,6 +138,7 @@ $(document).ready(function(){
text: "{% trans 'This will delete the selected System Users !!!' %}",
type: "warning",
showCancelButton: true,
cancelButtonText: "{% trans 'Cancel' %}",
confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false

View File

@@ -4,50 +4,13 @@
{% load bootstrap3 %}
{% block auth %}
<div class="password-auth hidden">
{% bootstrap_field form.password layout="horizontal" %}
</div>
<div class="public-key-auth">
<div>
{% bootstrap_field form.private_key_file layout="horizontal" %}
{% bootstrap_field form.private_key_file layout="horizontal" %}
<div class="form-group">
<label for="{{ form.as_push.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto push' %}</label>
<div class="col-sm-8">
{{ form.auto_push}}
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
var auth_method = '#'+'{{ form.auth_method.id_for_label }}';
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
function authMethodDisplay() {
if ($(auth_method).val() == 'P') {
$('.password-auth').removeClass('hidden');
$('.public-key-auth').addClass('hidden');
$('#'+'{{ form.password.id_for_label }}').removeAttr('disabled');
} else if ($(auth_method).val() == 'K') {
$('.password-auth').addClass('hidden');
$('.public-key-auth').removeClass('hidden');
$('#'+'{{ form.password.id_for_label }}').removeAttr('required');
$('#'+'{{ form.password.id_for_label }}').attr('disabled', 'disabled');
if ($(auto_generate_key).prop('checked')){
$('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group').addClass('hidden');
} else {
$('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group').removeClass('hidden');
}
}
}
$(document).ready(function () {
$('.select2').select2();
authMethodDisplay();
$(auth_method).change(function () {
authMethodDisplay();
});
$(auto_generate_key).change(function () {
authMethodDisplay();
});
})
</script>
{% endblock %}

View File

@@ -1,264 +1,188 @@
{% extends '_base_list.html' %}
{% load i18n %}
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<style>
.custom{
margin-right:5px;
}
#modal .modal-body { max-height: 200px; }
</style>
{% endblock %}
{% block content_left_head %}{% endblock %}
{% block table_search %}
{# <div class="html5buttons">#}
{# <div class="dt-buttons btn-group">#}
{# <a class="btn btn-default btn_export" tabindex="0">#}
{# <span>{% trans "Export" %}</span>#}
{# </a>#}
{# </div>#}
{# </div>#}
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
<script src="{% static 'js/jquery.form.min.js' %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content">
<div class="row">
<div class="col-lg-3" id="split-left" style="padding-left: 3px">
<div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
<div class="file-manager ">
<div id="assetTree" class="ztree">
</div>
{% block table_container %}
<table class="table table-striped table-bordered table-hover " id="asset_list_table" >
<thead>
<tr>
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
<th class="text-center">{% trans 'Hostname' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'Port' %}</th>
<th class="text-center">{% trans 'Type' %}</th>
<th class="text-center">{% trans 'Env' %}</th>
<th class="text-center">{% trans 'Hardware' %}</th>
<th class="text-center">{% trans 'Valid' %}</th>
<th class="text-center">{% trans 'Alive' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div class="col-lg-9 animated fadeInRight" id="split-right">
<div class="tree-toggle">
<div class="btn btn-sm btn-primary tree-toggle-btn" onclick="toggle()">
<i class="fa fa-angle-left fa-x" id="toggle-icon"></i>
</div>
</div>
<div class="mail-box-header">
<div class="btn-group" style="float: right">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>
<ul class="dropdown-menu labels">
{% for label in labels %}
<li><a style="font-weight: bolder">{{ label.name }}:{{ label.value }}</a></li>
{% endfor %}
</ul>
</div>
<table class="table table-striped table-bordered table-hover " id="user_assets_table" style="width: 100%">
<thead>
<tr>
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
<th class="text-center">{% trans 'Hostname' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'Active' %}</th>
<th class="text-center">{% trans 'System users' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
{% include 'assets/_user_asset_detail_modal.html' %}
{% endblock %}
{% block custom_foot_js %}
<script src="{% static 'js/jquery.form.min.js' %}"></script>
<script type="text/javascript">
$(document).ready(function(){
var options = {
ele: $('#asset_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:asset-detail" pk=99991937 %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('99991937', rowData.id));
}},
{targets: 7, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 8, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-circle text-danger"></i>')
} else {
$(td).html('<i class="fa fa-circle text-navy"></i>')
}
}}
],
ajax_url: '{% url "api-assets:asset-list" %}',
columns: [{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
{data: "get_type_display" }, {data: "get_env_display"}, {data: "hardware"},
{data: "is_active" }, {data: "is_active"}],
};
var table = jumpserver.initDataTable(options);
$('.btn_export').click(function () {
var assets = [];
var rows = table.rows('.selected').data();
$.each(rows, function (index, obj) {
assets.push(obj.id)
});
console.log(assets);
$.ajax({
url: "{% url "assets:asset-export" %}",
method: 'POST',
data: JSON.stringify({assets_id: assets}),
dataType: "json",
success: function (data, textStatus) {
window.open(data.redirect)
},
error: function () {
toastr.error('Export failed');
}
})
});
$('#btn_asset_import').click(function() {
var $form = $('#fm_asset_import');
$form.find('.help-block').remove();
function success (data) {
if (data.valid === false) {
$('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_assets'));
} else {
$('#id_created').html(data.created_info);
$('#id_created_detail').html(data.created.join(', '));
$('#id_updated').html(data.updated_info);
$('#id_updated_detail').html(data.updated.join(', '));
$('#id_failed').html(data.failed_info);
$('#id_failed_detail').html(data.failed.join(', '));
var $data_table = $('#asset_list_table').DataTable();
$data_table.ajax.reload();
}
}
$form.ajaxSubmit({success: success});
})
})
.on('click', '.btn_asset_delete', function () {
var $this = $(this);
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var the_url = '{% url "api-assets:asset-detail" pk=99991937 %}'.replace('99991937', uid);
objectDelete($this, name, the_url);
})
.on('click', '#btn_bulk_update', function () {
var action = $('#slct_bulk_update').val();
var $data_table = $('#asset_list_table').DataTable();
var id_list = [];
var plain_id_list = [];
$data_table.rows({selected: true}).every(function(){
id_list.push({id: this.data().id});
plain_id_list.push(this.data().id);
});
if (plain_id_list.length == 0) {
return false;
<script>
var zTree, asset_table;
var inited = false;
var url;
function initTable() {
if (inited){
return
} else {
inited = true;
}
var the_url = "{% url 'api-assets:asset-list' %}";
function doDeactive() {
var body = $.each(id_list, function(index, asset_object) {
asset_object['is_active'] = false;
});
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
$data_table.ajax.reload();
jumpserver.checked = false;
}
function doActive() {
var body = $.each(id_list, function(index, asset_object) {
asset_object['is_active'] = true;
});
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
$data_table.ajax.reload();
jumpserver.checked = false;
}
function doDelete() {
swal({
title: "{% trans 'Are you sure?' %}",
text: "{% trans 'This will delete the selected assets !!!' %}",
type: "warning",
showCancelButton: true,
confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false
}, function() {
var success = function() {
var msg = "{% trans 'Asset Deleted.' %}";
swal("{% trans 'Asset Delete' %}", msg, "success");
$('#asset_list_table').DataTable().ajax.reload();
};
var fail = function() {
var msg = "{% trans 'Asset Deleting failed.' %}";
swal("{% trans 'Asset Delete' %}", msg, "error");
};
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail});
$data_table.ajax.reload();
jumpserver.checked = false;
});
}
function doUpdate() {
$('#asset_bulk_update_modal').modal('show');
}
switch(action) {
case 'deactive':
doDeactive();
break;
case 'delete':
doDelete();
break;
case 'update':
doUpdate();
break;
case 'active':
doActive();
break;
default:
break;
}
})
.on('click', '#btn_asset_bulk_update', function () {
var json_data = $("#fm_asset_bulk_update").serializeObject();
var body = {};
body.enable_otp = (json_data.enable_otp === 'on')? true: false;
if (json_data.type != '') {
body.type = json_data.type;
}
if (json_data.groups != undefined) {
body.groups = json_data.groups;
}
if (typeof body.groups === 'string') {
body.groups = [parseInt(body.groups)]
} else if(typeof body.groups === 'array') {
var new_groups = body.groups.map(Number);
body.groups = new_groups;
}
if (json_data.system_users != undefined) {
body.system_users = json_data.system_users;
}
if (typeof body.system_users === 'string') {
body.system_users = [parseInt(body.system_users)]
} else if(typeof body.system_users === 'array') {
var new_users = body.system_users.map(Number);
body.system_users = new_users;
}
if (json_data.tags != undefined) {
body.tags = json_data.tags;
}
if (typeof body.tags == 'string') {
body.tags = [parseInt(body.tags)];
} else if (typeof body.tags === 'array') {
var new_tags = body.tags.map(Number);
body.tags = new_tags;
}
var $data_table = $('#asset_list_table').DataTable();
var post_list = [];
$data_table.rows({selected: true}).every(function(){
var content = Object.assign({id: this.data().id}, body);
post_list.push(content);
});
if (post_list === []) {
return false
}
var the_url = "{% url 'api-assets:asset-list' %}";
var success = function() {
var msg = "{% trans 'The selected assets has been updated successfully.' %}";
swal("{% trans 'Asset Updated' %}", msg, "success");
$('#asset_list_table').DataTable().ajax.reload();
jumpserver.checked = false;
console.log("init table")
url = "{% url 'api-perms:my-assets' %}";
var options = {
ele: $('#user_assets_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a class="asset_detail" asset-id="rowData_id" data-toggle="modal" data-target="#user_asset_detail_modal" tabindex="0">'+ cellData +'</a>'
$(td).html(detail_btn.replace("rowData_id", rowData.id));
}},
{targets: 3, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 4, createdCell: function (td, cellData) {
var users = [];
$.each(cellData, function (id, data) {
users.push(data.name);
});
$(td).html(users.join(', '))
}}
],
ajax_url: url,
columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" },
{data: "is_active", orderable: false },
{data: "system_users_granted", orderable: false}
]
};
console.log(JSON.stringify(post_list));
console.log(the_url);
{# APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success});#}
$('#asset_bulk_update_modal').modal('hide');
asset_table = jumpserver.initDataTable(options);
return asset_table
}
function onSelected(event, treeNode) {
url = '{% url "api-perms:my-node-assets" node_id=DEFAULT_PK %}';
url = url.replace("{{ DEFAULT_PK }}", treeNode.node_id);
setCookie('node_selected', treeNode.id);
asset_table.ajax.url(url);
asset_table.ajax.reload();
}
function initTree() {
var setting = {
view: {
dblClickExpand: false,
showLine: true
},
data: {
simpleData: {
enable: true
}
},
callback: {
onSelected: onSelected
}
};
var zNodes = [];
$.get("{% url 'api-perms:my-nodes' %}", function(data, status){
$.each(data, function (index, value) {
value["node_id"] = value["id"];
value["id"] = value["tree_id"];
if (value["tree_id"] !== value["tree_parent"]) {
value["pId"] = value["tree_parent"];
}
value["isParent"] = value["is_node"];
value['name'] = value['value'];
});
zNodes = data;
$.fn.zTree.init($("#assetTree"), setting, zNodes);
zTree = $.fn.zTree.getZTreeObj("assetTree");
var root = zTree.getNodes()[0];
zTree.expandNode(root);
});
}
$(document).ready(function () {
initTree();
initTable();
})
.on('click', '.asset_detail', function() {
var data = asset_table.ajax.json();
var asset_id = this.getAttribute("asset-id");
var trs = '';
var desc = {
'hostname': "{% trans 'Hostname' %}",
'ip': "{% trans 'IP' %}",
'port': "{% trans 'Port' %}",
'protocol': "{% trans 'Protocol' %}",
'platform': "{% trans 'Platform' %}",
'os': "{% trans 'OS' %}",
'system_users_join': "{% trans 'System user' %}",
'domain': "{% trans 'Domain' %}",
'is_active': "{% trans 'Is active' %}",
'comment': "{% trans 'Comment' %}"
{#'date_joined': "{% trans 'Date joined' %}",#}
};
$.each(data, function(index, value){
if(value.id === asset_id){
for(var i in desc){
trs += "<tr class='no-borders-tr'>\n" +
"<td>"+ desc[i] + ":</td>"+
"<td><b>"+ (value[i] === null?'':value[i]) + "</b></td>\n" +
"</tr>";
}
}
});
$('#asset_detail_tbody').html(trs)
});
</script>
{% endblock %}
{% endblock %}

View File

@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
#

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