Compare commits

..

872 Commits

Author SHA1 Message Date
老广
a025930957 Merge pull request #2642 from jumpserver/dev
Dev
2019-04-30 10:46:28 +08:00
老广
990c78e7cc Merge branch 'master' into dev 2019-04-30 10:46:10 +08:00
BaiJiangJie
0ef12906d3 [Update] 修改翻译信息 (#2643)
* [Update] 更新翻译信息

* [Update] 更新翻译信息
2019-04-30 10:30:58 +08:00
ibuler
61a37731ec Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-04-30 10:20:57 +08:00
ibuler
d3217b6a67 [Update] 修改版本号 2019-04-30 10:20:48 +08:00
BaiJiangJie
04266cc20b [Update] 修改Copyright (#2649) 2019-04-30 10:07:57 +08:00
ibuler
4f36cf7dd1 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-04-29 10:39:51 +08:00
ibuler
490041587b [Update] 修改copyright 2019-04-29 10:39:31 +08:00
BaiJiangJie
3a3da94468 [Update] 修改预留/auth/login/页面方式(admin=1);解决luna页面刷新不跳转openid认证的问题 (#2634) 2019-04-25 18:55:48 +08:00
BaiJiangJie
b7ad6cfe62 [Update] 防止 XSS (#2633)
* [Bugfix] 修改管理用户列表显示bug

* [Bugfix] 修复刷新批量命令页面的bug

* [Update] 防止 XSS
2019-04-25 18:16:41 +08:00
ibuler
4463e7545d [Update] 去掉多余的 2019-04-25 14:34:47 +08:00
ibuler
d0eafc8b8e [Update] Merge 2019-04-25 14:32:53 +08:00
ibuler
8b98c20d68 [Update] xss 2019-04-25 14:31:34 +08:00
BaiJiangJie
caa5060ecd [Update] 控制组织管理员不允许更新、删除超级用户;修复ViewSet API批量更新的bug (#2629)
* [Update] 控制组织管理员不允许编辑(更新、删除)超级用户 - 待续(控制批量更新API)

* [Update] 修改方法名称

* [Update] 控制组织管理员不允许批量更新包含超级用户的用户列表

* [Bugfix] 修复所有ViewSet API进行批量更新时rest_framework_bulk库内部的bug

* [Update] 修改 OpenID Middleware 日志输出模式 info => debug
2019-04-25 10:11:50 +08:00
BaiJiangJie
aabcf7f31c [Update] 授权列表下拉信息添加action字段 (#2618) 2019-04-22 12:31:40 +08:00
ibuler
40d48cdfe4 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-04-22 12:07:59 +08:00
ibuler
8196537878 [Update] 去掉logger.info openid msg 2019-04-22 12:07:48 +08:00
BaiJiangJie
33a00f043b [Bugfix] 修复创建azure类型的录像存储时前端的bug (#2617) 2019-04-22 11:55:14 +08:00
BaiJiangJie
f235e20153 [Feature] 授权规则添加 actions 选项,控制用户对资产的操作行为 (#2610)
* [Feature] 1. perms actions - 添加 Action Model

* [Feature] 2. perms actions - 添加 Action API

* [Feature] 3. perms actions - 授权规则: 添加actions字段

* [Feature] 4. perms actions - 授权规则创建页面: 设置 actions 默认 all

* [Feature] 5. perms actions - 资产授权工具: 动态给system_user设置actions属性; 修改授权相关的API-serializer类: 添加actions字段值

* [Feature] 6. perms actions - 更新API(用户使用系统用户连接资产时权限校验): 添加actions校验

* [Feature] 7. perms actions - 迁移文件中为已经存在的perms添加默认的action

* [Feature] 8. perms actions - 创建授权规则时设置默认action(如果actions字段值为空)

* [Feature] 9. check actions - 修改校验用户资产权限API逻辑(添加actions校验)

* [Feature] 10. check actions - 修改注释

* [Feature] 11. check actions - 添加API: 获取用户指定资产和系统用户被授权的actions

* [Feature] 12. check actions - 添加翻译信息
2019-04-22 11:42:20 +08:00
BaiJiangJie
cf2455c084 [Bugfix] 修复创建azure类型的录像存储时前端的bug (#2608) 2019-04-18 11:50:30 +08:00
BaiJiangJie
fc1068a9dc [Update] 更新翻译 (#2599) 2019-04-15 16:34:56 +08:00
zhnxin
35a0ca1875 Update node.py (#2527)
bugfix: node get_all_children逻辑
2019-04-15 14:43:49 +08:00
八千流
56519354b6 Email reset password (#2547)
* [Update]更改英文登录界面标题,可两行

* [Update] 更改用户通过邮箱修改密码后,该链接就失效

* [Update]更改页面左上角logo_text图片

* [Update] 优化发送邮箱改密连接失效代码

* [Update] 优化发送邮箱改密连接失效代码(2)

* [Update] 优化发送邮箱改密连接失效代码(3)

* [Update] 更新interface一键恢复默认的翻译

* [Update] 更改登录失败 用户名密码错误信息的翻译

* [Update] 优化生成token并设置缓存的代码
2019-04-15 14:37:45 +08:00
jokimina
78e4e13fb9 [Bugfix] IsSuperUserOrAppUser deny app user (#2558) 2019-04-15 14:33:40 +08:00
八千流
699b8d9980 [Update] 修改批量更新资产url过长导致的错误 (#2571) 2019-04-15 14:33:16 +08:00
zbhlove100
ba9581801c Update README.md 2019-04-09 16:57:01 +08:00
zbhlove100
0a5fdf4ea1 功能转为表格 2019-04-09 16:53:54 +08:00
zhangbohan
3849fa2b15 update readme 2019-04-09 15:27:37 +08:00
zhangbohan
0952cbc7c6 英文readme 2019-04-09 12:21:13 +08:00
zhangbohan
bb06c39dd4 更新readme 2019-04-09 12:09:11 +08:00
zhangbohan
d60dc31443 更新readme 2019-04-08 18:30:39 +08:00
老广
76b3cd8edd Merge pull request #2583 from jumpserver/dev
Dev
2019-04-04 12:33:16 +08:00
老广
638ba31694 Merge pull request #2582 from jumpserver/dev_bugfix_assets
[Bugfix] 修复资产对象获取所有节点时的bug
2019-04-04 12:21:42 +08:00
BaiJiangJie
c31b169cae [Bugfix] 修复资产对象获取所有节点时的bug 2019-04-04 12:17:53 +08:00
老广
fc167526ae Merge pull request #2575 from jumpserver/dev
[Bugfix] 修复OpenID判断退出登录的中间件逻辑的bug (#2574)
2019-04-03 12:19:23 +08:00
BaiJiangJie
55eff5eab9 [Bugfix] 修复OpenID判断退出登录的中间件逻辑的bug (#2574) 2019-04-03 12:18:58 +08:00
老广
f5a7f4e086 Merge pull request #2570 from jumpserver/dev
Bugfix (#2569)
2019-04-02 16:26:28 +08:00
老广
f7b0932cdd Bugfix (#2569)
* [Update] 修改Logo 的位置

* [Update] 修改镜像build
2019-04-02 16:25:39 +08:00
老广
ba89ce8fb9 Merge pull request #2560 from jumpserver/dev
[Update] 修复小问题 (#2557)
2019-03-29 19:34:28 +08:00
BaiJiangJie
9d62deeabe [Update] 修复小问题 (#2557)
* [Update] 修复小问题

* [Bugfix] 修复操作日志动作、资源类型过滤不成功的bug
2019-03-29 19:14:18 +08:00
老广
459b41f327 Merge pull request #2559 from jumpserver/dev
[Update] 修复重定向的问题
2019-03-29 15:54:20 +08:00
ibuler
3062e3f64a [Update] 修复重定向的问题 2019-03-29 15:53:31 +08:00
老广
c1362ca4e2 Merge pull request #2552 from jumpserver/dev
Dev
2019-03-28 13:13:36 +08:00
ibuler
9d24912ad9 [Update] Merge 2019-03-28 13:12:19 +08:00
老广
db290609a8 Merge pull request #2551 from jumpserver/dev
Dev
2019-03-28 13:07:34 +08:00
ibuler
4bc5eced6c Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-03-28 13:07:04 +08:00
BaiJiangJie
b82a66c83d [Update] 优化LDAP用户导入功能 (#2550) 2019-03-28 12:50:04 +08:00
ibuler
bf7079df9e [Update] 修改logo 2019-03-28 12:35:16 +08:00
ibuler
f137c5740e [Update] 添加openldap client 2019-03-28 11:53:52 +08:00
ibuler
ee47905966 [Update] 修改超时时间 2019-03-27 20:39:31 +08:00
fit2cloudrd
f6cd193f9e Update README.md (#2548)
* Dev (#2544)

* [Update] 更新翻译 (#2541)

* [Update] 更新缓存机制

* [Update] 增加task最大允许事件,并设置命令最大运行时间为60s

* Update README.md
2019-03-27 18:47:38 +08:00
ibuler
a31775dd23 [Update] 整理 copyright 和 版本 2019-03-27 17:46:35 +08:00
老广
30ba1e5886 Merge pull request #2545 from jumpserver/dev
Dev
2019-03-27 13:04:58 +08:00
ibuler
f97bfa7bf1 [Update] 修复s3创建时端点不含协议的提示 2019-03-27 12:49:46 +08:00
ibuler
ace028fa7f [Update] 统一可更改的interface 2019-03-27 12:20:43 +08:00
老广
69f6401e87 Dev (#2544)
* [Update] 更新翻译 (#2541)

* [Update] 更新缓存机制

* [Update] 增加task最大允许事件,并设置命令最大运行时间为60s
2019-03-27 11:21:21 +08:00
ibuler
bd4d974df1 [Update] merge 2019-03-27 11:13:35 +08:00
ibuler
6e7446f530 [Update] 增加task最大允许事件,并设置命令最大运行时间为60s 2019-03-27 11:12:34 +08:00
ibuler
afe9471aa2 [Update] 更新缓存机制 2019-03-26 19:46:04 +08:00
BaiJiangJie
4d56b84861 [Update] 更新翻译 (#2541) 2019-03-26 15:47:24 +08:00
老广
8fede58c64 Merge pull request #2456 from jumpserver/dev
Dev
2019-03-26 11:39:43 +08:00
ibuler
370904212f [Update] 修改翻译 2019-03-25 20:36:20 +08:00
ibuler
ae03a5aeb7 [Update] 翻译问题 2019-03-25 19:57:18 +08:00
ibuler
24a38841dd [Update] 修改Dockerfile 2019-03-22 18:26:25 +08:00
ibuler
bbc6156bd7 [Update] 数据库支持CA 2019-03-22 18:06:46 +08:00
ibuler
f387df41d7 [Update] 修改版本 2019-03-22 17:23:02 +08:00
ibuler
ceb8b2f5b3 [Update] 修改翻译 2019-03-22 17:16:02 +08:00
ibuler
877781a6ca [Update] 删掉一个migrations 2019-03-22 16:56:46 +08:00
BaiJiangJie
612d5efd1b [Update] 抽象Inventory,更新翻译 (#2530)
* [Update] 抽象Inventory, 支持自定义用户名密码构建Inventory

* [Update] 更新翻译
2019-03-22 16:52:26 +08:00
ibuler
7ea03801d0 [Update] 修改翻译 2019-03-22 15:57:37 +08:00
八千流
ce8f4b4a48 Ldap synchronization (#2512)
* [Add]初步实现ldap一键导入用户到jumpserver用户表里

* [update]增加定时延迟一秒刷新页面

* [Update]更改前端以表格形式显示用户信息,优化代码结构

* [Update]增加用户显示表格

* [Update]settings配置文件取消注释

* [Update]优化ldap同步用功能代码

* [Update]删除ldap同步用户旧html模版

* [Update]修改登录页面图片拉伸问题

* [Update]增加 是否已经导入,在前端提示用户

* [Update]优化ldap同步用户的代码,以及翻译

* [Update] 更新翻译(改密计划) (#2525)

* [Update] 更新翻译(添加改密计划)

* [Update] 更新翻译(改密计划)

* [Update] 更新翻译

* [Update] 更新翻译

* Export login log (#2511)

* [Add]增加登录日志导出功能

* [Update]优化导出登录日志代码

* [Update]优化导出登录日志代码

* [Update]更改导出登录日志按钮
2019-03-22 15:55:46 +08:00
ibuler
15179d2450 [Update] 修改批量命令的翻译 2019-03-22 15:55:20 +08:00
ibuler
9aae106970 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-03-22 15:19:24 +08:00
ibuler
c82044f6bc [Update] 修改一些bug 2019-03-22 15:18:47 +08:00
八千流
e4e6f59589 Export login log (#2511)
* [Add]增加登录日志导出功能

* [Update]优化导出登录日志代码

* [Update]优化导出登录日志代码

* [Update]更改导出登录日志按钮
2019-03-22 15:17:22 +08:00
BaiJiangJie
c8aa9d006f [Update] 更新翻译(改密计划) (#2525)
* [Update] 更新翻译(添加改密计划)

* [Update] 更新翻译(改密计划)

* [Update] 更新翻译

* [Update] 更新翻译
2019-03-22 09:42:39 +08:00
老广
311538dcf8 Bugfix (#2513)
* [Update] 用户页面添加跳转

* [Update] 网关测试支持nat, 修复创建node等id不能指定的问题, 修复settings频繁redis, 没有has_replay录像不可以播放
2019-03-19 19:09:09 +08:00
老广
324cf2469f Bugfix (#2506)
* [Update] 增加清理celery日志

* [Update] 修复两周前会话命令数量系那是问题

* [Update] 修复两周前会话命令数量系那是问题

* [Update] 修改结构

* [Update] 添加datatable失败的日志

* [Update] 转换配置文件格式

* [Update] 添加traceback
2019-03-18 11:34:13 +08:00
ibuler
01745ead1f [Update] 添加migrations 2019-03-18 10:26:40 +08:00
BaiJiangJie
4e705a52eb [Feature] 添加资产用户管理器 (#2489)
* [Feature] 1. 资产用户管理器

* [Feature] 2. 资产用户管理器: 更新AuthBook

* [Feature] 3. 资产用户管理器: 添加 AssetUser API

* [Feature] 4. AssetUser Model: 添加方法 load_related_asset_auth

* [Feature] 5. AdminUser: 更新管理用户获取认证信息时,先加载相关资产的认证

* [Feature] 6. SystemUser: 更新系统用户获取认证信息时,先加载相关资产的认证

* [Feature] 前端页面: 添加资产用户列表页面

* [Feature] 前端页面: 管理用户的资产管理页面添加按钮: 修改资产用户认证信息

* [Feature] 前端页面: 系统用户的资产管理页面添加按钮: 修改资产用户认证信息

* [Feature] 优化: 从管理用户和系统用户的backend中获取相关资产用户的逻辑

* [Update] Fix 1

* [Feature] 优化: SystemUserBackend之filter功能

* [Feature] 优化: AdminUserBackend之filter功能

* [Feature] 优化: AdminUserBackend和SystemUserBackend功能

* [Feature] 更新翻译: 资产用户管理器

* [Update] 更新资产用户列表页名称为: asset_asset_user_list.html

* [Bugfix] 修改bug: SystemUserBackend 根据用户名过滤系统用户

* [Feature] 添加: 资产用户列表中可测试资产用户的连接性

* [Update] 修改: AdHoc model的run_as字段从SystemUser外键修改为username字符串

* [Feature] 添加: 获取系统用户认证信息(对应某个资产)API

* [Update] 更新: API获取asset user时进行排序

* [Bugfix] 修改: 资产用户可连接性CACHE_KEY

* [Update] 更新翻译信息

* [Update] 修改获取资产用户认证信息API的返回响应(200/400)

* [Update] 修改BaseUser获取特定资产的方法名

* [Update] 修改logger输出,AuthBook set_version_and_latest

* [Update] 修改日志输出添加exc_info参数

* [Update] 移除AuthBook迁移文件0026

* [Bugfix] 修复AdminUserBackend获取instances为空的bug
2019-03-18 10:15:33 +08:00
ibuler
9bb58afee1 [Update] bugfix 2019-03-11 10:27:40 +08:00
老广
b45b33380c Monitor (#2485)
* [Update]

* [Update] 修改fields
2019-03-11 10:06:45 +08:00
BaiJiangJie
c86a036ac6 [Update] OpenID认证流程添加详细日志 (#2462)
* [Update] OpenID认证流程添加详细日志

* [Update] 优化日志格式
2019-03-07 18:41:42 +08:00
ibuler
8694511d86 [Update] 完成 2019-03-06 14:58:25 +08:00
ibuler
58c4a46f6e [Update] 用户权限增加cache 2019-03-05 19:47:14 +08:00
ibuler
dfd26d88d4 [Update] 用户资产添加缓存 2019-03-04 20:45:57 +08:00
ibuler
dcf6959cff [Update] 每次启动失效设置的cache 2019-03-04 18:24:02 +08:00
ibuler
924affd978 [Update] 更新移动的model 2019-03-04 17:07:51 +08:00
ibuler
ad6d233c11 [Update] 修改表名称 2019-03-04 15:51:17 +08:00
ibuler
d84ab1d215 [Update] 修改迁移 2019-03-04 15:38:59 +08:00
ibuler
a5fc04e0ce [Update] merge 2019-03-04 11:36:10 +08:00
ibuler
7f71513085 [Update] 去掉fixtures 2019-03-04 11:32:28 +08:00
ibuler
6004ef3f0d [Update] 修改settings 2019-03-04 10:53:49 +08:00
ibuler
e76392a169 Merge branch 'stable' into dev 2019-03-04 10:47:31 +08:00
ibuler
53f0b2e9b0 [Update] 增加清理登陆日志的任务 2019-03-04 10:39:44 +08:00
ibuler
de79e36251 [Update] 增加配置 2019-03-04 10:17:35 +08:00
ibuler
c84e984eae [Update] 任务列表去掉日期 2019-03-01 18:26:13 +08:00
ibuler
6d65c967b1 [Update] config example增加radius 2019-03-01 18:21:14 +08:00
ibuler
8199ea84f4 [Bugfix] 修复有时导致的token解析失败 2019-03-01 13:50:57 +08:00
ibuler
ce1b0da09d [Update] Merge 2019-02-28 18:24:52 +08:00
ibuler
cd6bb848e9 [Update] Squash table 2019-02-28 18:23:39 +08:00
ibuler
34040fcd59 [Update] 移动model 2019-02-28 17:58:53 +08:00
ibuler
1969fb79fe [Update] 修改authentication backends 2019-02-28 15:12:45 +08:00
BaiJiangJie
904f64604b [Bugfix] 修复authentication模块之间引用的bug 2019-02-28 14:41:33 +08:00
ibuler
9b3509208d [Update] 修改authentication目录结构 2019-02-28 11:58:48 +08:00
BaiJiangJie
6700dc969f [Update] users认证逻辑迁移到authentication中 2019-02-27 20:55:28 +08:00
ibuler
21714cc411 [Update] Stash 2019-02-27 08:45:00 +08:00
ibuler
1a247d60e7 [Update] sudo命令添加help text 2019-02-26 16:49:55 +08:00
jokimina
adf8b1f7aa [Bugfix] operation on closed file. (#2438) 2019-02-26 12:42:34 +08:00
老广
69f640daa4 Api (#2439)
* [Update] 迁移settings到独立app

* [Update] 修改settings migrations

* [Update] 修改docs说明
2019-02-26 12:38:20 +08:00
老广
a43314f5be Merge pull request #2432 from jokimina/patch-5
[Bugfix] celery beat crontab task timezone issue
2019-02-25 10:43:52 +08:00
jokimina
599e8a7e37 [Bugfix] crontab task timezone issue 2019-02-24 21:34:45 +08:00
老广
0e00451e1f Merge pull request #2425 from jumpserver/dev
[Update] 修改ldap test密码为空
2019-02-22 19:21:21 +08:00
ibuler
866e5d2011 [Update] 修改ldap test密码为空 2019-02-22 19:14:07 +08:00
老广
232674b1c1 Merge pull request #2423 from jumpserver/dev
Dev
2019-02-22 17:10:05 +08:00
ibuler
ddf60d2512 [Update] 添加jms日志和修改entrypoint 2019-02-22 16:33:27 +08:00
ibuler
3e6e0153cf [Update] 修改entrypoint并增加debug日志 2019-02-22 16:14:50 +08:00
ibuler
6b984aac53 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-02-22 12:49:03 +08:00
ibuler
6d3ee8116e [Update] 修改版本号 2019-02-22 12:48:43 +08:00
老广
904a0f67dd Merge pull request #2422 from jumpserver/dev
Dev
2019-02-22 10:16:08 +08:00
BaiJiangJie
81e1ce2688 [Update] Interface - 1. settings 添加TERMINAL_HEADER_TITLE (#2421) 2019-02-21 19:36:15 +08:00
ibuler
7c422d2ed6 [Update] Merge 2019-02-21 19:24:52 +08:00
ibuler
2a5129c481 [Update] 修改支持记录cmd filter日志 2019-02-21 19:22:23 +08:00
BaiJiangJie
28cdfca14b [Update] 优化 License (#2420)
* [Update] License - 1.添加标签过滤器

* [Update] License - 2.添加翻译信息
2019-02-21 18:49:40 +08:00
ibuler
202aba048b [Update] 修改用户页面授权资产 2019-02-21 17:35:28 +08:00
ibuler
3c2a4703bc [Update] ldap允许空密码 2019-02-21 16:37:36 +08:00
ibuler
4904aac2df [Update] 更新依赖版本 2019-02-21 16:16:08 +08:00
ibuler
303f88d6ee [Update] 更新replay storage 配置 2019-02-21 16:13:08 +08:00
老广
cccc74279d Dev license (#2409)
* [Update] 更新用户组

* [Update] license - 更新翻译

* [Feature] interface - 自定义配置logo,修改前端(待续)

* [Update] interface更新前端页面,两个登陆首页的模版设

* [Update]修改新登陆模版全局变量,国际化翻译,登陆逻辑页面切换

* [Update] 优化Interface前端逻辑

* [Update] Interface 修改前端一些小问题

* [Update] License/Interface 生成翻译信息
2019-02-20 19:20:13 +08:00
老广
16db2abca5 Merge pull request #2419 from jumpserver/dev
Dev
2019-02-20 19:02:40 +08:00
ibuler
859f2d9795 [Update] 支持telnet自定义正则 2019-02-20 19:00:01 +08:00
ibuler
4fd9957bec [Update] 修改celery日志显示 2019-02-20 17:51:53 +08:00
老广
0ac6e6ba2c Merge pull request #2418 from jumpserver/dev
[Update] 修改gunicorn日志
2019-02-20 16:16:44 +08:00
ibuler
785cc04126 [Update] 修改gunicorn日志 2019-02-20 16:11:58 +08:00
老广
f269eae774 Merge pull request #2415 from jumpserver/dev
[Update] 修改导入
2019-02-20 10:41:59 +08:00
ibuler
6f19fcb702 [Update] 修改tasks 2019-02-20 10:40:43 +08:00
老广
b7b6218306 Merge pull request #2414 from jumpserver/dev
[Update] gunicorn的日志
2019-02-19 20:06:37 +08:00
ibuler
5cd809b48a [Update] 修改导入 2019-02-19 20:03:02 +08:00
ibuler
5a1b894138 [Update] gunicorn的日志 2019-02-19 16:59:00 +08:00
老广
4d402617b6 Merge pull request #2410 from jumpserver/dev
Dev
2019-02-19 13:04:00 +08:00
ibuler
666ef366e7 [Bugfix] 修复session不显示在线的问题 2019-02-19 12:50:33 +08:00
ibuler
28d029a553 [Update] 修改settings 2019-02-19 12:18:39 +08:00
老广
00763e986a Merge pull request #2408 from jumpserver/dev
[Update] 修改token有效期
2019-02-19 12:00:04 +08:00
ibuler
f9a7cca478 [Update] 修改token有效期 2019-02-19 10:29:25 +08:00
老广
d09b34e232 Merge pull request #2404 from jumpserver/dev
[Update] 修改配置文件判断
2019-02-18 15:10:36 +08:00
ibuler
2737675c36 [Update] 修改配置文件判断 2019-02-18 15:09:02 +08:00
老广
7591f40b2c Merge pull request #2402 from jumpserver/dev
[Update] 创建子节点支持id
2019-02-18 13:02:15 +08:00
ibuler
c4af6fa72d [Update] 创建子节点支持id 2019-02-18 12:59:36 +08:00
老广
19be7ac580 Merge pull request #2394 from jumpserver/dev
Dev
2019-02-15 11:16:54 +08:00
ibuler
49404f763d [Update] 修改terminal字体 2019-02-15 11:15:46 +08:00
ibuler
87f2a67789 [Update] 修改录像获取 2019-02-15 11:14:37 +08:00
老广
041edb6177 Merge pull request #2392 from jumpserver/dev
Dev
2019-02-15 10:44:32 +08:00
ibuler
df2fad76c7 [Update] 修改Dockerfile 优先使用阿里云镜像 2019-02-14 16:23:56 +08:00
ibuler
94020a8fbb [Update] 修改日志存储 2019-02-14 16:15:28 +08:00
ibuler
cb1e19d28f [Update] 修改录像路径使用utc 2019-02-14 15:47:55 +08:00
ibuler
0980dffb47 [Update] 修复一下新建两个节点的bug 2019-02-13 19:38:51 +08:00
ibuler
4051225ecb [Bugfix] 修复命令执行bug,修复修改日志级别后无法查看日志 2019-02-13 15:55:11 +08:00
老广
507518da04 Merge pull request #2369 from jumpserver/dev
[Update] 修改版本号
2019-01-29 19:33:06 +08:00
ibuler
d3bdbc0b81 [Update] 修改版本号 2019-01-29 19:24:28 +08:00
老广
3b56027edc Merge pull request #2364 from jumpserver/dev
Dev
2019-01-29 17:45:31 +08:00
ibuler
8285610097 [Update] 创建Host Key 2019-01-29 14:07:46 +08:00
ibuler
6acac9cb3d Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-01-29 12:28:14 +08:00
ibuler
9d78f26807 [Update] 创建Host Key 2019-01-29 12:27:51 +08:00
BaiJiangJie
9d53ba22e1 [Update] 取消ceph录像存储,添加命令执行翻译 (#2365) 2019-01-28 14:59:06 +08:00
ibuler
0d0cf04543 [Update] 修改日志查看 2019-01-28 12:18:53 +08:00
ibuler
56a47b6ba3 [Update] 存储添加说明 2019-01-25 14:55:56 +08:00
ibuler
1dbcf4e3ab [Update] 数据库make migration移动到gunicorn 2019-01-24 11:25:13 +08:00
ibuler
95fcd60f64 [Update] 兼容之前的node api 2019-01-22 12:44:01 +08:00
ibuler
178055eb57 [Update] 修改任务小写 2019-01-21 17:43:08 +08:00
老广
01a101a710 [Update] 修改一些terminal storage (#2357) 2019-01-21 17:05:31 +08:00
ibuler
eee6dd1436 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-01-21 12:27:28 +08:00
ibuler
27693c6288 [Update] 修改配置文件 2019-01-21 12:27:20 +08:00
ibuler
55b55f6162 [Update] 修改测试连接节点时,资产为下面的所有资产 2019-01-21 11:58:51 +08:00
ibuler
384cdfbc19 [Update] 启动脚本 2019-01-19 19:29:38 +08:00
ibuler
c8d007f9d7 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-01-19 16:52:59 +08:00
ibuler
d8069f47f2 [Update] 修改配置文件加载 2019-01-17 20:38:56 +08:00
ibuler
d664018bd7 [Update] 修改config.py 2019-01-17 20:32:10 +08:00
ibuler
0f32e78891 [Update] 修改dockerfile 2019-01-17 20:26:47 +08:00
ibuler
6e061d2da5 [Bugfix] 修改小bug 2019-01-17 18:39:38 +08:00
ibuler
b2717133ee [Update] 修改日志目录 2019-01-17 18:06:47 +08:00
老广
2a0e68c58f Bugfix (#2350)
* [Update] 权限页面增加过滤规则

* [Update] 修改terminal注册,更新以后使用api完成

* [Update] 修改terminal注册,更新以后使用api完成

* [Update] 修改更新注册逻辑
2019-01-16 18:13:16 +08:00
老广
176052e8e9 [Update] 权限页面增加过滤规则 (#2349) 2019-01-15 19:01:33 +08:00
老广
d026b31c9f Bugfix (#2346)
* [Update] 修改command Post导致的output错误和定时任务创建问题

* [Update] 修改celery 日志

* [Update] 修改task日志方式

* [Update] 修改Docker file
2019-01-15 10:23:30 +08:00
老广
50c1b3ed4a [Update] 统一coco host key (#2336) 2019-01-10 11:50:08 +08:00
mago960806
131e588d82 is_running()里的打开pid文件的操作已经在get_pid()中执行过了,不需要再次执行 (#2335) 2019-01-10 10:21:05 +08:00
xiaomao
4bf0dfcf7b [bugfix] 解决日志中文报错以及各种utf8问题 (#2328) 2019-01-08 15:27:15 +08:00
老广
49a166552e Bugfix (#2327)
* [Bugfix] 修复两个配置文件冲突问题

* [Update] Docker中不再提供配置文件
2019-01-08 11:15:09 +08:00
老广
0e1d3f93ff [Update] 支持radius认证 (#2323)
* [Update] 支持radius认证

* [Update] 支持radius

* [Update] 增加requirements

* [Update] 修改copyright

* [Update] 修改migrations
2019-01-07 19:20:39 +08:00
老广
35403086ab Config (#2322)
* [Update] 修改配置文件

* [Update] 修改配置文件形式
2019-01-07 18:55:02 +08:00
ibuler
2fde6cfe24 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-01-03 12:41:21 +08:00
老广
ed1dc7a984 Merge pull request #2300 from jumpserver/dev
Dev
2019-01-03 11:52:13 +08:00
老广
9ab3f0441f Merge pull request #2298 from jumpserver/dev_bugfix
[Bugfix] 修复-用户页面资产详情显示bug
2019-01-03 10:41:48 +08:00
BaiJiangJie
d6567f0e57 [Bugfix] 修复-用户页面资产详情显示bug 2019-01-03 10:39:09 +08:00
老广
84bd465b30 Merge pull request #2296 from jumpserver/dev
Dev
2019-01-02 15:39:05 +08:00
ibuler
d0af8eba32 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-01-02 15:35:06 +08:00
ibuler
b2a8415f77 [Update] 修改创建子节点api 2019-01-02 15:33:35 +08:00
老广
545d4fa691 Merge pull request #2287 from jumpserver/dev
[Update] 禁用其他认证方式修改密码 (#2286)
2018-12-27 18:58:52 +08:00
老广
560df5027a [Update] 禁用其他认证方式修改密码 (#2286)
* [Update] 禁用其他认证方式修改密码

* [Update] 禁用其他认证方式修改密码

* [Update] 禁用其他认证方式修改密码
2018-12-27 16:47:40 +08:00
老广
e3db7462f7 Merge pull request #2284 from jumpserver/dev
Dev
2018-12-27 11:45:38 +08:00
老广
b55d137e7f Merge pull request #2283 from jumpserver/dev_bai
[Update] 修改在线会话终端按钮vnc协议disable
2018-12-27 11:44:58 +08:00
BaiJiangJie
7c10f8743f [Update] 修改在线会话终端按钮vnc协议disable 2018-12-27 11:39:11 +08:00
ibuler
c5d1ed126e [Update] 修改Jms脚本 2018-12-26 19:20:32 +08:00
老广
6b02cdfc37 Merge pull request #2279 from jumpserver/dev
Dev
2018-12-26 14:56:25 +08:00
BaiJiangJie
340c615efe [Bugfix] session model 添加 vnc 协议 (#2278)
* [Bugfix] session model 添加 vnc 协议

* [Update] 修改表结构
2018-12-26 14:43:43 +08:00
BaiJiangJie
8e51f97dc7 [Update] 添加资产树右击菜单: 刷新所有节点资产数量 (#2274)
* [Update] 添加资产树右击菜单: 刷新所有节点资产数量

* [Update] 修改右击菜单样式
2018-12-26 13:54:42 +08:00
BaiJiangJie
76a08c9039 [Bugfix] 修复创建/更新 Perms 时,名称重复不提示错误信息的问题 (#2272) 2018-12-26 12:59:09 +08:00
BaiJiangJie
b9b8c35a81 [Update] 修改创建资产选择vnc协议,默认端口5901 (#2276)
* [Update] 修改创建资产选择vnc协议,默认端口5901

* [update] 修改创建命令/录像存储时账户无效的提示信息
2018-12-26 12:57:59 +08:00
老广
e8fba2ec44 Merge pull request #2270 from jumpserver/dev
Dev
2018-12-25 16:56:02 +08:00
老广
61df6f55b9 Merge pull request #2269 from jumpserver/bugfix
[Update] 修改bug
2018-12-25 16:55:13 +08:00
ibuler
ef02b1f83a [Update] 修改bug 2018-12-25 16:53:52 +08:00
老广
0798e3c466 Merge pull request #2266 from jumpserver/dev
Dev
2018-12-25 14:32:47 +08:00
老广
a990098744 [Update] 优化资产选择 (#2267)
* [Update] 优化资产选择

* [Update] 优化资产任务
2018-12-25 13:33:37 +08:00
BaiJiangJie
dab692c0eb [Update] 限制终端设置中心跳间隔和会话保留时长的form最小值 (#2262)
* [Update] 限制终端设置中心跳间隔和会话保留时长的form最小值

* [Update] 删除terminal forms表单的初始化值

* [Update] 取消安全设置中forms的初始化值,并采用默认值;添加密码过期时间的最大值限制
2018-12-25 09:58:01 +08:00
BaiJiangJie
8e93bfecb0 [Bugfix] 修复DEFAULT组织下,批量删除(某组织下)一部分用户失败的bug (#2261) 2018-12-24 15:50:58 +08:00
ibuler
e5953e1932 Merge branch 'dev' of bitbucket.org:jumpserver/core into dev 2018-12-23 22:59:30 +08:00
老广
67b21f0489 Merge pull request #2254 from jumpserver/dev
Dev
2018-12-21 15:58:56 +08:00
ibuler
e15c9e6588 [Update] 修改小bug 2018-12-21 15:58:19 +08:00
ibuler
94d6525548 [Bugfix] 修复小bug 2018-12-21 15:57:23 +08:00
老广
a14d6b298d Merge pull request #2253 from jumpserver/dev
Bugfix (#2252)
2018-12-21 15:48:29 +08:00
老广
e7725e6910 Bugfix (#2252)
* [Update] 修改心跳偶人时间

* [Update] 修改Node比较

* [Bugfix] 修复bug
2018-12-21 15:47:52 +08:00
老广
23bf2b0f8e Merge pull request #2250 from jumpserver/dev
Dev
2018-12-21 15:10:37 +08:00
BaiJiangJie
ac9178cb93 [Update] 更新deb_requirements依赖 (#2239) 2018-12-21 14:59:00 +08:00
老广
7ff39259af Required opt (#2246)
* [Update] 修改心跳偶人时间

* [Update] 去掉required的label

* [Update] 修改默认心跳时间

* [Update] 去掉默认的placeholder

* [Update] 修改utils
2018-12-21 14:57:42 +08:00
ibuler
b2aef87fdd [Update] 修改心跳偶人时间 2018-12-21 10:35:17 +08:00
老广
e1f1bed9c9 Merge pull request #2231 from jumpserver/dev
Dev
2018-12-20 11:44:34 +08:00
老广
08945f0a19 [Bugfix] 修复adhoc 日志查看的bug, 修改config example (#2230) 2018-12-20 11:42:00 +08:00
ibuler
a1b80f5f0b [Update] 清理task adhoc和history 2018-12-20 11:05:36 +08:00
ibuler
7773c30240 [Update] 增加索引 2018-12-20 10:40:52 +08:00
jokimina
231c907c64 [Update] ops adhoc date_created index (#2223) 2018-12-20 10:34:42 +08:00
BaiJiangJie
db8882a2b9 [Bugfix] 修复创建网域时,选择资产的input和弹出table显示不一致的bug (#2224) 2018-12-20 10:32:41 +08:00
ibuler
e5285f312b [Bugfix] 修复vnc录像的bug 2018-12-19 19:55:41 +08:00
老广
332be54b46 Vnc (#2226)
* [Update] 优化授权协议, 支持vnc

* [Update] 添加协议vnc

* [Update] 修改系统用户添加

* [Update] 修改vnc信息
2018-12-19 19:40:58 +08:00
老广
fe7c3c29ad [Update] 优化授权协议, 支持vnc (#2220)
* [Update] 优化授权协议, 支持vnc
2018-12-19 17:03:10 +08:00
老广
0e9ebed19d Merge pull request #2214 from jumpserver/dev
Dev
2018-12-19 13:57:15 +08:00
老广
4a3327bc4b Merge pull request #2213 from jumpserver/bugfix
[Bugfix] 修复录像bug
2018-12-19 13:56:30 +08:00
ibuler
5d47bebb6b [Bugfix] 修复录像bug 2018-12-19 13:55:49 +08:00
老广
2ece3545ed Merge pull request #2210 from jumpserver/dev
Dev
2018-12-19 12:33:34 +08:00
ibuler
a9a1bae805 [Update] 修改列表页,显示可连接状态 2018-12-19 12:32:53 +08:00
ibuler
f35c02b346 [Update] 修改列表页,显示可连接状态 2018-12-19 12:30:51 +08:00
老广
32df515f4b Merge pull request #2209 from jumpserver/dev
Dev
2018-12-19 12:02:16 +08:00
ibuler
71750970b2 [Update] 暂时去掉刷新资产树 2018-12-19 12:01:18 +08:00
ibuler
a85099ee60 [Update] 修改版本号 2018-12-19 11:43:13 +08:00
老广
a5b9b4e1d2 Merge pull request #2206 from jumpserver/dev
Dev
2018-12-19 10:53:00 +08:00
老广
b3079a4a9b [Update] Add index (#2208) 2018-12-19 10:49:30 +08:00
ibuler
9a22874305 [Update] 去掉多余的任务 2018-12-18 20:14:44 +08:00
ibuler
179018bf67 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2018-12-18 20:07:16 +08:00
ibuler
583214e91a [Update] 优化获取授权资产api 2018-12-18 20:01:59 +08:00
BaiJiangJie
fb44ef0986 [Bugfix] 修复 资产授权/标签 选择资产时,input框内的资产和弹出资产tabl显示不一致的bug (#2205)
* [Bugfix] 修复授权规则,选择资产时,输入框资产和弹出表格中资产显示不一致

* [Bugfix] 修复创建/更新资产标签,选择资产时,输入框资产和弹出表格中资产显示不一致的bug

* [Update] 修复标签/授权选择资产时, 2次初始化table的bug;资产input和弹出table统一的代码逻辑移到js中;
2018-12-18 18:50:02 +08:00
ibuler
90b77fdb08 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2018-12-18 17:30:59 +08:00
老广
a609f17078 [Update] Stash it (#2197)
* [Update] Stash it

* [Bugfix] 修复错误

* [Update] 修改jms
2018-12-18 17:28:45 +08:00
ibuler
068a280350 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2018-12-18 11:38:42 +08:00
老广
1293d72189 Session task (#2196)
* [Bugfix] 修复错误

* [Update] 增加会话定期清理
2018-12-18 11:29:21 +08:00
ibuler
164c5ebabd Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2018-12-18 09:52:25 +08:00
老广
b56d73ba9e Node asset amount (#2192)
* [Bugfix] 修复错误

* [Update] 优化用户api
2018-12-17 20:09:25 +08:00
ibuler
dbdcdb722d Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2018-12-17 18:21:51 +08:00
老广
517a27ea33 Node asset amount (#2191)
* [Bugfix] 修复错误

* [Update] 修改树结构,统一api
2018-12-17 18:20:44 +08:00
vkill
ab6c88823d Support for TOTP valid_window configuration (#2187) 2018-12-17 14:26:00 +08:00
ibuler
1ff9f0eaa6 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2018-12-17 11:50:18 +08:00
老广
b95f8a7d6b [Update] 修复获取节点数量比较慢的问题 (#2184) 2018-12-17 11:49:57 +08:00
ibuler
29ff0efdc1 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2018-12-17 11:45:37 +08:00
老广
985bd6fc82 Bugfix2 (#2183)
* [Bugfix] 修复错误

* [Bugfix] 修复一些bug
2018-12-17 11:44:43 +08:00
BaiJiangJie
374039d287 [Update] 终端设置,添加coco端资产列表页面大小配置项 (#2182)
* [Update] 终端设置,添加coco端资产列表页面大小配置项

* [Update] 添加页面大小选项
2018-12-17 11:18:55 +08:00
ibuler
c7ac93fcc1 [Bugfix] 修复错误 2018-12-17 10:21:16 +08:00
老广
ac7e3e7f97 Merge pull request #2161 from jumpserver/dev
[Update] 更改readme
2018-12-13 10:16:08 +08:00
ibuler
4e0b25ae0f [Update] 更改readme 2018-12-13 10:15:31 +08:00
老广
559f4d2f5c Merge pull request #2160 from jumpserver/dev
[Bugfix] 修复access key的错误
2018-12-13 10:08:16 +08:00
ibuler
f40f6bc61e [Bugfix] 修复accekt的错误 2018-12-13 10:06:51 +08:00
老广
0f61b36bff Merge pull request #2155 from jumpserver/dev
Dev
2018-12-12 18:04:27 +08:00
ibuler
55ff82545a [Bugfix] 修复任务执行args为空的bug 2018-12-12 18:03:28 +08:00
ibuler
a99d5609fa [Bugfix] 修复运行测试硬件等报错的bug 2018-12-12 18:00:21 +08:00
老广
dbc2779b34 Merge pull request #2151 from jumpserver/dev
Dev
2018-12-12 15:58:57 +08:00
ibuler
33b1de0d85 [Update] 优化一下 2018-12-12 15:56:36 +08:00
ibuler
bcfe82f162 [Update] 优化错误颜色 2018-12-12 12:40:45 +08:00
ibuler
82af5f8f16 [Update] 资产详情页面增加网域 2018-12-12 12:28:13 +08:00
ibuler
84f52eb337 [Update] 修改api 2018-12-12 12:11:12 +08:00
老广
f00a650366 Merge pull request #2145 from jumpserver/dev
Dev
2018-12-12 10:54:41 +08:00
ibuler
2fedeb9834 [Update] 修改命令错误输出 2018-12-12 10:41:28 +08:00
ibuler
daadcedc21 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2018-12-12 09:57:26 +08:00
ibuler
951e8261ad [Update] 修改版本 2018-12-12 09:57:02 +08:00
老广
9c0f00f625 [Update] 修改命令执行 (#2148)
* [Update] 修改命令执行

* Update forms.py
2018-12-11 20:32:55 +08:00
老广
6a23983331 Command (#2142)
* [Update] 修改节点

* [Update] 优化命令运行失败日志
2018-12-11 12:51:22 +08:00
BaiJiangJie
18e590effd [Bugfix] 修复创建授权规则,选择资产导致其他输入框清空的bug - select2 (#2141) 2018-12-11 12:04:19 +08:00
vkill
9d1f5d3184 Add alpine_requirements.txt (#2139)
* Add alpine_requirements.txt

* Update alpine_requirements.txt
2018-12-11 11:21:07 +08:00
ibuler
b54d389c7c [Update] 修改翻译 2018-12-11 11:12:47 +08:00
老广
31356e825f [Update] 修改运行命令名称 (#2140) 2018-12-11 11:09:46 +08:00
ibuler
76aadad6fe [Bugfix] 修复菜单选中bug 2018-12-10 19:54:50 +08:00
ibuler
c7510bcf19 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2018-12-10 19:51:57 +08:00
ibuler
52e5487e7d [Update] 修改执行命令 2018-12-10 19:51:35 +08:00
BaiJiangJie
4a0d6842de [Update] 修改select2 css (#2125)
* [Update] 修改select2 css

* [Update] 创建资产选择节点时,关闭自动收起下拉列表
2018-12-10 15:19:08 +08:00
老广
d6b22e9ff8 [Update] 修改gateway test connection (#2135) 2018-12-10 13:03:02 +08:00
Titan
2833f343b2 otp issuer配置项 (#2133)
多机房环境中otp issuer配置
2018-12-10 12:03:42 +08:00
老广
3d13f3a17d Command (#2134)
* [Update] 任务区分org

* [Update] 修改翻译

* [Update] 使用id而不是hostname

* [Update] 执行命令

* [Update] 修改一些东西

* [Update] 暂存

* [Update] 用户执行命令

* [Update] 添加资产授权模块-tree

* [Update] 暂时这样

* [Update] 批量命令执行

* [Update] 修改表结构

* [Update] 更新翻译

* [Update] 删除cloud模块无效中文翻译
2018-12-10 10:11:54 +08:00
BaiJiangJie
d91599ffab [Update] 更新依赖 (#2117)
* [Update] 更新依赖

* [Update] 修改翻译小细节
2018-12-04 10:01:40 +08:00
老广
e22e832d49 [Bugfix] 修复批量更新时 节点和标签是增加而不是覆盖 (#2115) 2018-11-30 15:39:13 +08:00
BaiJiangJie
8f479e364b [Update] 修复小细节 (#2111)
* [Update] 命令/录像存储,禁用default删除按钮

* [Update] 修复 _asset_list_modal 取消资产全选时不能映射到资产选择框的bug
2018-11-29 16:01:03 +08:00
老广
0b0fdbfc82 [Update] 日志审计中改密日志权限 (#2108)
* [Update] 日志审计中改密日志权限

* [Update] 日志审计中改密日志权限
2018-11-28 18:15:02 +08:00
老广
24fe3ade9c [Update] 默认中可以看到所有用户 (#2106) 2018-11-28 10:02:58 +08:00
老广
9499a16a8b [Update] 默认中可以看到所有用户 (#2102) 2018-11-27 11:00:39 +08:00
BaiJiangJie
f380d82b55 [Update] 修改小问题 (#2100)
* [Update] 不修改admin用户的用户来源

* [Bugfix] 修复Default组织下用户列表会显示所有用户,并查看详情会报错

* [Update] 关闭telnet资产测试可连接性

* [Update] 只有ssh协议资产可测试连接性
2018-11-27 10:21:47 +08:00
老广
060248d1ca Req (#2098)
* [Update] 修改requirements

* [Update] 修改requirements

* [Update] 编译语言
2018-11-26 18:50:30 +08:00
ibuler
da8fec77bb [Update] 更新配置文件 2018-11-26 10:38:56 +08:00
ibuler
67f52888f6 [Update] 浏览器会话火气时间 2018-11-26 10:30:40 +08:00
ibuler
c4d6f32528 [Update] 增加启动超时时间 2018-11-26 10:22:17 +08:00
ibuler
2661bbb70a [Update] 修改config example 2018-11-23 18:30:41 +08:00
ibuler
3e3ab556d3 [Update] 修改设置 2018-11-23 18:28:02 +08:00
BaiJiangJie
646a29108c [Update] 修改安全设置模块排版,翻译 (#2083) 2018-11-23 17:00:35 +08:00
ibuler
c7f86cdde9 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2018-11-23 16:36:49 +08:00
BaiJiangJie
79208a95c1 [Update] authentication 添加自定义 LDAPBackends (#2081) 2018-11-23 16:36:28 +08:00
ibuler
091cf39e4e [Bugfix] ldap可以修改密码问题,platform大小写问题 2018-11-23 16:24:30 +08:00
ibuler
0df7c6909e [Update] 修改config_example 2018-11-23 11:30:31 +08:00
ibuler
f6def0b43f [Update] 修改表结构 2018-11-23 11:15:11 +08:00
老广
9cfcadc2f6 服务账号注册机制更改 (#2079)
* [Update] 服务账号注册

* [Update] 修改settings配置

* [Update] 修改settings

* [Update] 整理terminal api

* [Update] 修改terminal api

* [Update] 修改terminal注册机制
2018-11-23 10:25:35 +08:00
BaiJiangJie
363985ee7a [Feature] 添加功能,密码过期间隔时间配置,检测用户密码过期 (#2043)
* [Update] user model 添加date_password_last_updated字段, 并在用户详情/个人信息页进行展示、重置密码/修改密码时进行更新;安全设置添加密码过期时间配置项;

* [Update] 修改依赖,deb_requirements 删除 gcc automake

* [Update] 添加定时任务: 检测用户密码是否过期

* [Update] 登录页面添加检测用户密码是否过期,并给出过期提示,拒绝登录

* [update] 用户密码过期时间5天以内,每天发送重置密码邮件

* [Update] api 登录认证,添加密码过期检测

* [Update] 添加提示用户密码即将过期信息

* [Update] 修改小细节

* [Update] User model 添加密码即将过期/已过期 property属性

* [Update] 修改用户api auth检测用户密码过期逻辑

* [Update] 添加翻译,用户密码过期

* [Update] 用户密码即将过期,发送密码过期提醒邮件

* [Update] 修改检测用户密码过期任务,修改interval为crontab

* [Update] 修改翻译小细节

* [Update] 修改翻译小细节

* [Bugfix] 修复在用户更新页面修改密码时, 不更新最后密码修改时间的bug

* [Update] 修复小细节

* [Update] 修改系统设置成功提示翻译信息
2018-11-22 18:02:12 +08:00
老广
16cc4a0f4e [Update] 修改settings配置 (#2067)
* [Update] 修改settings配置

* [Update] 修改settings

* [Update] 修改密码校验规则前后端逻辑

* [Update] 修改用户config机制

* [Update] 修改配置

* [Update] 修改config example增加翻译
2018-11-22 12:27:27 +08:00
ibuler
5931c5a032 [Update] terminal配置修改 2018-11-19 12:45:33 +08:00
ibuler
742200e462 Merge remote-tracking branch 'github/dev' into dev 2018-11-19 12:27:57 +08:00
ibuler
9d7b82085e [Update] 修改common settings 2018-11-19 12:27:29 +08:00
ibuler
dda367a956 [Update] 修改common settings配置 2018-11-19 12:18:52 +08:00
老广
c0d51e22d7 Merge pull request #2035 from jumpserver/origin_dev
Origin dev
2018-11-14 12:49:47 +08:00
老广
2348c8c335 Merge pull request #2034 from jumpserver/origin_dev
[Bugfix] 修复luna不显示多组织资产的问题
2018-11-14 12:49:21 +08:00
ibuler
87abe63a20 [Bugfix] 修复luna不显示多组织资产的问题 2018-11-14 12:48:59 +08:00
ibuler
4d26fd8b56 [Bugfix] 修复luna不显示多组织资产的问题 2018-11-14 12:47:43 +08:00
ibuler
c9aab608a9 [Bugfix] 修复luna不显示多组织资产的问题 2018-11-14 12:42:26 +08:00
ibuler
641567be10 [Update] 修改docker file依赖 2018-11-13 16:18:25 +08:00
ibuler
5f68f6cb69 [Update] 增加脚本导入migration sql 2018-11-13 16:09:26 +08:00
ibuler
d50ad66b78 [Update] 维护统一的migrations 2018-11-13 15:57:44 +08:00
老广
69ab3e3542 Merge pull request #2028 from jumpserver/dev
Dev
2018-11-13 15:17:47 +08:00
ibuler
ff1b902b2e Merge remote-tracking branch 'github/dev' into dev 2018-11-13 15:01:07 +08:00
BaiJiangJie
52647da79b [Update] 修改jumpserver功能图片 (#2020) 2018-11-13 14:59:50 +08:00
BaiJiangJie
28b6144189 [Bugfix] 修复创建用户common_settings.SITE_URL 为None的bug (#2027) 2018-11-13 14:59:29 +08:00
ibuler
f7daf26a03 [Update] 修改celery运行,最大20个进程 2018-11-12 17:47:41 +08:00
ibuler
e1673334af Merge remote-tracking branch 'github/dev' into dev 2018-11-12 16:46:21 +08:00
BaiJiangJie
fd6e561d4b [Bugfix] 资产/用户等升序排序问题,显示创建oss录像存储bucket输入框 (#2017)
* [Bugfix] 修复资产列表,用户列表排序只有降序的问题

* [update] 系统设置创建oss录像存储, 添加bucket输入框
2018-11-12 16:45:57 +08:00
ibuler
4cdddaa493 [Bugfix] 修复网域网关列表的bug 2018-11-12 16:43:35 +08:00
老广
9491827e01 Merge pull request #2014 from jumpserver/dev
Dev
2018-11-11 12:04:00 +08:00
老广
5459d1114f Merge branch 'master' into dev 2018-11-11 11:39:02 +08:00
ibuler
6acda27d67 [Update] 更新版本 2018-11-11 10:38:15 +08:00
BaiJiangJie
0f9326bd8f [Update] 更新OpenID依赖 (#2013) 2018-11-09 15:00:37 +08:00
BaiJiangJie
e09f3ca4fd [Update] 支持 OpenID 认证 (#2008)
* [Update] core支持openid登录,coco还不支持

* [Update] coco支持openid登录

* [Update] 修改注释

* [Update] 修改 OpenID Auth Code Backend 用户认证失败返回None, 不是Anonymoususer

* [Update] 修改OpenID Code用户认证异常捕获

* [Update] 修改OpenID Auth Middleware, check用户是否单点退出的异常捕获

* [Update] 修改config_example Auth OpenID 配置

* [Update] 登录页面添加 更多登录方式

* [Update] 重构OpenID认证架构

* [Update] 修改小细节

* [Update] OpenID用户认证成功后,更新用户来源

* [update] 添加OpenID用户登录成功日志
2018-11-09 14:54:38 +08:00
ibuler
1fcb272ddc [Bugfix] 修复coco tree失败的问题,不应该过滤没有asset的node 2018-11-09 12:33:45 +08:00
ibuler
b577c626f7 [Bugfix] 修复luna取到协议不同的系统用户 2018-11-09 11:23:41 +08:00
ibuler
2e4e5503cc [Bugfix] 修复获取common settings时数据库问题 2018-11-07 11:26:39 +08:00
liuzheng712
4212cb3600 feat: add tencentcloud-sdk-python==3.0.32 2018-11-05 12:48:32 +08:00
ibuler
b8874e1855 [Update] 修改版本号 2018-11-05 11:52:44 +08:00
ibuler
9bb498f7b3 [Update] 修改sdk版本 2018-11-05 11:25:22 +08:00
ibuler
e38d089056 Merge remote-tracking branch 'github/dev' into dev 2018-11-05 11:11:33 +08:00
BaiJiangJie
f9e9bf0b2d [Update] 更新用户/授权规则过期时间精确到min (#1993) 2018-11-05 11:11:20 +08:00
BaiJiangJie
1e5387ef47 [Update] 更新组织管理api (#1986)
* [Update] 更新组织管理api

* [Update] 重写-组织管理员/用户API,采用through类

* [Update] 修改OrgMembershipSerializerMixin目录

* [Update] 修改组织管理API,限制http method

* [Update] 修改rpm依赖
2018-11-02 14:38:44 +08:00
BaiJiangJie
f87e08efff [Update] 添加deb依赖,完善用户登录失败日志,修复资产标签bug (#1983)
* [Update] 修改deb依赖

* [Update] 修改记录用户登录失败日志,用户不存在的情况,但是显示登录日志列表还不全..

* [Update] 用户登录日志,记录用户名不存在的情况(Default组织显示所有用户的登录日志)

* [Bugfix] 修复标签名为search, limit时资产列表不显示的bug
2018-11-01 16:28:19 +08:00
BaiJiangJie
82d866db7d [Update] 添加用户授权资产列表页的分页,搜索,排序 (#1963)
* [Update] 分页获取用户授权资产

* [Update] 修改前端-用户授权资产分页

* [Update] 用户授权资产支持搜索

* [Update] 用户授权资产支持排序

* [Update] 用户授权的节点with资产Api,对资产进行排序

* [Update] 获取用户授权的节点下的资产的api,进行分页、排序、查询

* [Update] 抽象用户授权资产列表的查询,排序

* [Update] 优化小细节

* [Update] 删除无用导入

* [Update] 修改AssetFilterMixins目录从common到perms

* [Update] 资产授权规则列表: 添加分页、搜索

* [Update] 添加管理用户,系统用户列表分页、搜索

* [Update] 用户组列表添加分页,搜索

* [Update] 资产标签列表添加分页、搜索

* [Update] 网域网关列表添加分页、搜索

* [Update] 命令过滤列表添加分页、搜索,修改翻译小细节

* [Update] 删除前端注释initDataTable

* [Update] 修改文案,资产组-节点

* [Update] 普通用户资产列表添加分页、搜索
2018-10-31 15:31:09 +08:00
老广
ba0d822734 Merge pull request #1971 from waiwaiku/master
修复子用户资产列表点击无法收起
2018-10-31 15:30:14 +08:00
fangjian
c8568eb244 修复子用户资产列表点击无法收起 2018-10-31 11:26:24 +08:00
ibuler
6e19b9d5bc [Update] Luna支持异步加载 2018-10-30 12:06:39 +08:00
老广
354b728f75 Merge pull request #1945 from jumpserver/dev_storage
[Update] 更新命令/录像存储设置
2018-10-29 10:07:10 +08:00
老广
ce553710ba Merge pull request #1958 from jumpserver/dev_bugfix_ops
[Bugfix] 修复组织管理员刷新硬件信息,获取资产为None(使用fullname)和查看celery log 403问题
2018-10-29 10:04:57 +08:00
BaiJiangJie
4f806f11f2 [Update] 修改命令/录像存储,创建/删除API的权限为SuperUser 2018-10-28 02:17:46 +08:00
ibuler
e9247dd578 [Update] default组织显示所有用户 2018-10-27 11:34:10 +08:00
老广
0a94a346a0 Merge pull request #1946 from jumpserver/dev_bugfix_perms
[Bugfix] 修复授权规则下Default节点下的资产不显示
2018-10-27 11:14:23 +08:00
老广
d8afe72d4c Merge pull request #1947 from jumpserver/dev_user_paging
[Update] 用户列表页,添加分页,搜索
2018-10-27 11:14:03 +08:00
BaiJiangJie
e2072a1e02 [Update] 修复组织管理员刷新硬件信息,获取资产为None(使用fullname)和查看celery log 403问题 2018-10-26 21:03:09 +08:00
BaiJiangJie
cc387bf511 [Update] 用户列表页,添加分页,搜索 2018-10-24 14:55:04 +08:00
BaiJiangJie
5c002e91ee [Update] 修改获取资产distinct 2018-10-24 13:05:32 +08:00
BaiJiangJie
41a8831034 [Bugfix] 修复授权规则下Default节点下的资产不显示 2018-10-24 12:13:47 +08:00
BaiJiangJie
ebd92c79c7 [Update] 修改翻译小细节 2018-10-24 11:05:39 +08:00
BaiJiangJie
6278900201 [Update] 创建es命令存储,添加提示信息 2018-10-24 10:57:55 +08:00
ibuler
4f580e0df8 [Update] 修改启动脚本 2018-10-24 10:53:00 +08:00
BaiJiangJie
1f502e02c7 [Update] 修改settings为common_settings 2018-10-24 10:48:03 +08:00
BaiJiangJie
cdf8398169 [Update] 修复创建es命令存储校验有效性异常 2018-10-24 10:29:40 +08:00
BaiJiangJie
1bfef829f3 [Update] 修改terminal表单获取storage, 删除无用代码 2018-10-24 10:11:38 +08:00
BaiJiangJie
cc0cf8ed1c Merge branch 'dev' into dev_replay 2018-10-24 09:52:43 +08:00
BaiJiangJie
2791213844 [Update] 更新storage翻译 2018-10-23 20:41:01 +08:00
BaiJiangJie
284e8be45c [Update] 修改系统设置-命令/录像存储页面(添加,删除) 2018-10-23 19:22:18 +08:00
老广
76109f1808 Merge pull request #1938 from jumpserver/dev
Dev
2018-10-23 10:10:53 +08:00
ibuler
54b6e06d1f [Update] 暂时不支持上传目录 2018-10-19 16:30:52 +08:00
ibuler
5c30c76ea3 [Update] 修改jms-storage版本 2018-10-19 10:49:13 +08:00
老广
94b5eb8685 Merge pull request #1929 from jumpserver/dev
Dev
2018-10-19 09:56:52 +08:00
ibuler
c9f4b104c7 Merge remote-tracking branch 'github/dev' into dev 2018-10-19 09:38:18 +08:00
ibuler
3bf1c036c5 [Bugfix] 修改sshpubkey库版本 2018-10-19 09:27:33 +08:00
老广
09fbd3a5ab Merge pull request #1922 from jumpserver/dev
Dev
2018-10-16 17:18:31 +08:00
ibuler
ebecd00581 [Update] 修改优先级逻辑 2018-10-16 16:47:47 +08:00
ibuler
143fa060d1 [Update] 修改节点信息 2018-10-16 13:44:26 +08:00
ibuler
2c18a27e3a [Update] 修改节点创建规则 2018-10-16 12:37:42 +08:00
ibuler
910dd4e593 [Update] 修改节点生成规则 2018-10-16 12:03:11 +08:00
ibuler
11aefa479b [Bugfix] 修复common设置问题 2018-10-16 10:56:25 +08:00
ibuler
abc56016f2 Merge remote-tracking branch 'github/dev' into dev 2018-10-16 10:19:00 +08:00
老广
f44db2a25b Merge pull request #1916 from jumpserver/dev_tag_filter
[Update] 添加(减法)标签过滤器
2018-10-16 10:16:25 +08:00
BaiJiangJie
3fa6807837 [Update] 添加(减法)标签过滤器 2018-10-16 09:53:30 +08:00
ibuler
2c4195d619 Merge remote-tracking branch 'github/dev' into dev 2018-10-15 17:47:32 +08:00
老广
265ef0c8ac Merge pull request #1913 from jumpserver/dev_requirements
[Update] 更新requirements
2018-10-15 17:47:10 +08:00
BaiJiangJie
0b0b06a5c2 [Update] 更新requirements-aliyun 2018-10-15 17:06:01 +08:00
ibuler
d77ba1d5ea Merge remote-tracking branch 'github/dev' into dev 2018-10-15 16:30:08 +08:00
老广
a3bd7cee80 Merge pull request #1912 from jumpserver/dev_cloud_manager
[Update] 修改文案
2018-10-15 16:29:50 +08:00
BaiJiangJie
8d73cd43e1 Merge branch 'dev' into dev_cloud_manager 2018-10-15 16:09:55 +08:00
BaiJiangJie
7e3fd73ae5 [Update] 修改文案 2018-10-15 16:05:53 +08:00
ibuler
70960d2ae4 Merge remote-tracking branch 'github/dev' into dev 2018-10-15 11:34:20 +08:00
老广
d2c574fe9d Merge pull request #1910 from jumpserver/dev_cloud_manager
[Feature] 添加xpack-云管中心模块
2018-10-15 11:30:38 +08:00
BaiJiangJie
a70fcf057b [Update] 修改js,添加3级菜单active属性 2018-10-15 10:48:24 +08:00
ibuler
f37582ec53 [Update] 用户导航加上文件管理 2018-10-15 10:14:20 +08:00
BaiJiangJie
29b87c40fe [Update] 修改云管理模块-文案 2018-10-14 00:03:04 +08:00
BaiJiangJie
1ec77c5bb9 [Update] 添加3级导航菜单-云管理模块 2018-10-12 20:29:04 +08:00
BaiJiangJie
21c71aba93 [Update] Merge branch dev_cloud_manager (baijiangjie_local) to dev 2018-10-12 18:41:01 +08:00
BaiJiangJie
f8db9f480e [Update] 添加翻译-云管理模块 2018-10-12 18:26:56 +08:00
老广
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
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
chnliyong
6035d1f130 python-gssapi libkrb5-dev for gssapi/gssapi.h 2018-03-10 10:46:28 +08:00
684 changed files with 111929 additions and 10643 deletions

View File

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

4
.gitignore vendored
View File

@@ -17,7 +17,7 @@ dump.rdb
.idea/
db.sqlite3
config.py
migrations/
config.yml
*.log
host_rsa_key
*.bat
@@ -32,3 +32,5 @@ django.db
celerybeat-schedule.db
data/static
docs/_build/
xpack
logs/*

24
Dockerfile Normal file
View File

@@ -0,0 +1,24 @@
FROM registry.fit2cloud.com/public/python:v3
MAINTAINER Jumpserver Team <ibuler@qq.com>
WORKDIR /opt/jumpserver
RUN useradd jumpserver
COPY ./requirements /tmp/requirements
RUN yum -y install epel-release && rpm -ivh https://repo.mysql.com/mysql57-community-release-el6.rpm
RUN cd /tmp/requirements && yum -y install $(cat rpm_requirements.txt)
RUN cd /tmp/requirements && pip install --upgrade pip setuptools && \
pip install -i https://mirrors.aliyun.com/pypi/simple/ -r requirements.txt || pip install -r requirements.txt
RUN mkdir -p /root/.ssh/ && echo -e "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config
COPY . /opt/jumpserver
RUN echo > config.yml
VOLUME /opt/jumpserver/data
VOLUME /opt/jumpserver/logs
ENV LANG=zh_CN.UTF-8
ENV LC_ALL=zh_CN.UTF-8
EXPOSE 8080
ENTRYPOINT ["./entrypoint.sh"]

205
README.md
View File

@@ -1,58 +1,207 @@
## Jumpserver
## 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/)
[![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/)
----
Jumpserver是全球首款完全开源的堡垒机使用GNU GPL v2.0开源协议,是符合 4A 的专业运维审计系统。
Jumpserver 是全球首款完全开源的堡垒机,使用 GNU GPL v2.0 开源协议,是符合 4A 的专业运维审计系统。
Jumpserver使用Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 解决方案,交互界面美观、用户体验好。
Jumpserver 使用 Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 解决方案,交互界面美观、用户体验好。
Jumpserver采纳分布式架构支持多机房跨区域部署中心节点提供 API各机房部署登录节点可横向扩展、无并发限制。
Jumpserver 采纳分布式架构,支持多机房跨区域部署,中心节点提供 API各机房部署登录节点可横向扩展、无并发限制。
改变世界,从一点点开始。
----
- [English Version](https://github.com/jumpserver/jumpserver/blob/master/README_EN.md)
### 功能
- 统一认证
- 资产管理
- 统一授权
- 审计
- 支持LDAP认证
- Web terminal
- SSH Server
- 支持Windows RDP
----
<table class="subscription-level-table">
<tr class="subscription-level-tr-border">
<th style="background-color: #1ab394;color: #ffffff;" colspan="3">Jumpserver提供的堡垒机必备功能</th>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-first-td-background-style" rowspan="4">身份验证 Authentication</td>
<td class="features-second-td-background-style" rowspan="3" >登录认证
</td>
<td class="features-third-td-background-style">资源统一登录和认证
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-third-td-background-style">LDAP认证
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-third-td-background-style">支持OpenID实现单点登录
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-second-td-background-style">多因子认证
</td>
<td class="features-third-td-background-style">MFAGoogle Authenticator
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-first-td-background-style" rowspan="9">账号管理 Account</td>
<td class="features-second-td-background-style" rowspan="2">集中账号管理
</td>
<td class="features-third-td-background-style">管理用户管理
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-third-td-background-style">系统用户管理
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-second-td-background-style" rowspan="4">统一密码管理
</td>
<td class="features-third-td-background-style">资产密码托管
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-third-td-background-style">自动生成密码
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-third-td-background-style">密码自动推送
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-third-td-background-style">密码过期设置
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-outline-td-background-style" rowspan="2">批量密码变更(X-PACK)
</td>
<td class="features-outline-td-background-style">定期批量修改密码
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-outline-td-background-style">生成随机密码
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-outline-td-background-style">多云环境的资产纳管(X-PACK)
</td>
<td class="features-outline-td-background-style">对私有云、公有云资产统一纳管
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-first-td-background-style" rowspan="8">授权控制 Authorization</td>
<td class="features-second-td-background-style" rowspan="3">资产授权管理
</td>
<td class="features-third-td-background-style">资产树
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-third-td-background-style">资产或资产组灵活授权
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-third-td-background-style">节点内资产自动继承授权
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-outline-td-background-style">组织管理(X-PACK)
</td>
<td class="features-outline-td-background-style">实现多租户管理,权限隔离
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-second-td-background-style">多维度授权
</td>
<td class="features-third-td-background-style">可对用户、用户组或系统角色授权
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-second-td-background-style">指令限制
</td>
<td class="features-third-td-background-style">限制特权指令使用,支持黑白名单
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-second-td-background-style">统一文件传输
</td>
<td class="features-third-td-background-style">SFTP 文件上传/下载
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-second-td-background-style">文件管理
</td>
<td class="features-third-td-background-style">Web SFTP 文件管理
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-first-td-background-style" rowspan="6">安全审计 Audit</td>
<td class="features-second-td-background-style" rowspan="2">会话管理
</td>
<td class="features-third-td-background-style">在线会话管理
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-third-td-background-style">历史会话管理
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-second-td-background-style" rowspan="2">录像管理
</td>
<td class="features-third-td-background-style">Linux 录像支持
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-third-td-background-style">Windows 录像支持
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-second-td-background-style">指令审计
</td>
<td class="features-third-td-background-style">指令记录
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-second-td-background-style">文件传输审计
</td>
<td class="features-third-td-background-style">上传/下载记录审计
</td>
</tr>
</table>
### 开始使用
----
快速开始文档 [Docker安装](http://docs.jumpserver.org/zh/latest/quickstart.html)
- 快速开始文档 [Docker 安装](http://docs.jumpserver.org/zh/docs/dockerinstall.html)
一步一步安装文档 [详细部署](http://docs.jumpserver.org/zh/latest/step_by_step.html)
- Step by Step 安装文档 [详细部署](http://docs.jumpserver.org/zh/docs/step_by_step.html)
也可以查看我们完整文档包括了使用和开发 [文档](http://docs.jumpserver.org)
- 也可以查看我们完整文档 [文档](http://docs.jumpserver.org)
### Demo 和 截图
### Demo、视频 和 截图
----
我们提供了DEMO和截图可以让你快速了解Jumpserver
我们提供了 Demo 、演示视频和截图可以让你快速了解 Jumpserver
[DEMO](http://demo.jumpserver.org)
[截图](http://docs.jumpserver.org/zh/docs/snapshot.html)
- [Demo](https://demo.jumpserver.org/auth/login/?next=/)
- [视频](https://fit2cloud2-offline-installer.oss-cn-beijing.aliyuncs.com/tools/Jumpserver%20%E4%BB%8B%E7%BB%8Dv1.4.mp4)
- [截图](http://docs.jumpserver.org/zh/docs/snapshot.html)
### SDK
### SDK
----
我们还编写了一些SDK供你其它系统快速和Jumpserver APi交互
我们还编写了一些SDK供你其它系统快速和 Jumpserver API 交互
- [python](https://github.com/jumpserver/jumpserver-python-sdk) Jumpserver其它组件使用这个SDK完成交互
- [java](https://github.com/KaiJunYan/jumpserver-java-sdk.git) 恺珺同学提供的Java版本的SDK
- [Python](https://github.com/jumpserver/jumpserver-python-sdk) Jumpserver其它组件使用这个SDK完成交互
- [Java](https://github.com/KaiJunYan/jumpserver-java-sdk.git) 恺珺同学提供的Java版本的SDK
### License & Copyright
Copyright (c) 2014-2018 Beijing Duizhan Tech, Inc., All rights reserved.
Copyright (c) 2014-2019 飞致云 FIT2CLOUD, All rights reserved.
Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

58
README_EN.md Normal file
View File

@@ -0,0 +1,58 @@
## 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-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/)
----
- [中文版](https://github.com/jumpserver/jumpserver/blob/master/README_EN.md)
Jumpserver is the first fully open source bastion in the world, based on the GNU GPL v2.0 open source protocol. Jumpserver is a professional operation and maintenance audit system conforms to 4A specifications.
Jumpserver is developed using Python / Django, conforms to the Web 2.0 specification, and is equipped with the industry-leading Web Terminal solution which have beautiful interface and great user experience.
Jumpserver adopts a distributed architecture to support multi-branch deployment across multiple areas. The central node provides APIs, and login nodes are deployed in each branch. It can be scaled horizontally without concurrency restrictions.
Change the world, starting from little things.
----
### Features
![Jumpserver 功能](https://jumpserver-release.oss-cn-hangzhou.aliyuncs.com/Jumpserver148.jpeg "Jumpserver 功能")
### Start
Quick start [Docker Install](http://docs.jumpserver.org/zh/docs/dockerinstall.html)
Step by Step deployment. [Docs](http://docs.jumpserver.org/zh/docs/step_by_step.html)
Full documentation [Docs](http://docs.jumpserver.org)
### Demo、Video 和 Snapshot
We provide online demo, demo video and screenshots to get you started quickly.
[Demo](https://demo.jumpserver.org/auth/login/?next=/)
[Video](https://fit2cloud2-offline-installer.oss-cn-beijing.aliyuncs.com/tools/Jumpserver%20%E4%BB%8B%E7%BB%8Dv1.4.mp4)
[Snapshot](http://docs.jumpserver.org/zh/docs/snapshot.html)
### SDK
We provide the SDK for your other systems to quickly interact with the Jumpserver API.
- [Python](https://github.com/jumpserver/jumpserver-python-sdk) Jumpserver other components use this SDK to complete the interaction.
- [Java](https://github.com/KaiJunYan/jumpserver-java-sdk.git) 恺珺同学提供的Java版本的SDK thanks to 恺珺 for provide Java SDK
### License & Copyright
Copyright (c) 2014-2019 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
https://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.

View File

@@ -1,5 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
__version__ = "1.0.0"
#

View File

@@ -4,3 +4,5 @@ from .label import *
from .system_user import *
from .node import *
from .domain import *
from .cmd_filter import *
from .asset_user import *

View File

@@ -14,22 +14,25 @@
# limitations under the License.
from django.db import transaction
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.mixins import IDInFilterMixin
from common.utils import get_logger
from ..hands import IsSuperUser
from ..hands import IsOrgAdmin
from ..models import AdminUser, Asset
from .. import serializers
from ..tasks import test_admin_user_connectability_manual
from ..tasks import test_admin_user_connectivity_manual
logger = get_logger(__file__)
__all__ = [
'AdminUserViewSet', 'ReplaceNodesAdminUserApi',
'AdminUserTestConnectiveApi', 'AdminUserAuthApi',
'AdminUserAssetsListView',
]
@@ -37,21 +40,29 @@ class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet):
"""
Admin user api set, for add,delete,update,list,retrieve resource
"""
filter_fields = ("name", "username")
search_fields = filter_fields
queryset = AdminUser.objects.all()
serializer_class = serializers.AdminUserSerializer
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
pagination_class = LimitOffsetPagination
def get_queryset(self):
queryset = super().get_queryset().all()
return queryset
class AdminUserAuthApi(generics.UpdateAPIView):
queryset = AdminUser.objects.all()
serializer_class = serializers.AdminUserAuthSerializer
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
class ReplaceNodesAdminUserApi(generics.UpdateAPIView):
queryset = AdminUser.objects.all()
serializer_class = serializers.ReplaceNodeAdminUserSerializer
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
def update(self, request, *args, **kwargs):
admin_user = self.get_object()
@@ -72,12 +83,30 @@ class ReplaceNodesAdminUserApi(generics.UpdateAPIView):
class AdminUserTestConnectiveApi(generics.RetrieveAPIView):
"""
Test asset admin user connectivity
Test asset admin user assets_connectivity
"""
queryset = AdminUser.objects.all()
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.TaskIDSerializer
def retrieve(self, request, *args, **kwargs):
admin_user = self.get_object()
task = test_admin_user_connectability_manual.delay(admin_user)
task = test_admin_user_connectivity_manual.delay(admin_user)
return Response({"task": task.id})
class AdminUserAssetsListView(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetSimpleSerializer
pagination_class = LimitOffsetPagination
filter_fields = ("hostname", "ip")
http_method_names = ['get']
search_fields = filter_fields
def get_object(self):
pk = self.kwargs.get('pk')
return get_object_or_404(AdminUser, pk=pk)
def get_queryset(self):
admin_user = self.get_object()
return admin_user.get_related_assets()

View File

@@ -1,29 +1,37 @@
# -*- coding: utf-8 -*-
#
import uuid
import random
from rest_framework import generics
from rest_framework.views import APIView
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.utils.translation import ugettext_lazy as _
from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy
from django.core.cache import cache
from django.db.models import Q
from common.mixins import IDInFilterMixin
from common.utils import get_logger
from ..hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser, \
NodePermissionUtil
from ..models import Asset, SystemUser, AdminUser, Node
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX
from ..models import Asset, AdminUser, Node
from .. import serializers
from ..tasks import update_asset_hardware_info_manual, \
test_asset_connectability_manual
test_asset_connectivity_manual
from ..utils import LabelFilter
logger = get_logger(__file__)
__all__ = [
'AssetViewSet', 'UserAssetListView', 'AssetListUpdateApi',
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi'
'AssetViewSet', 'AssetListUpdateApi',
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi',
'AssetGatewayApi', 'AssetBulkUpdateSelectAPI'
]
@@ -37,33 +45,47 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer
pagination_class = LimitOffsetPagination
permission_classes = (IsSuperUserOrAppUser,)
permission_classes = (IsOrgAdminOrAppUser,)
def get_queryset(self):
queryset = super().get_queryset()
admin_user_id = self.request.query_params.get('admin_user_id')
def filter_node(self, queryset):
node_id = self.request.query_params.get("node_id")
if not node_id:
return queryset
if admin_user_id:
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
queryset = queryset.filter(admin_user=admin_user)
if node_id:
node = get_object_or_404(Node, id=node_id)
if not node.is_root():
queryset = queryset.filter(nodes__key__startswith=node.key).distinct()
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() and show_current_asset:
queryset = queryset.filter(
Q(nodes=node_id) | Q(nodes__isnull=True)
)
elif node.is_root() and not show_current_asset:
pass
elif not node.is_root() and show_current_asset:
queryset = queryset.filter(nodes=node)
else:
queryset = queryset.filter(
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
)
return queryset
def filter_admin_user_id(self, queryset):
admin_user_id = self.request.query_params.get('admin_user_id')
if not admin_user_id:
return queryset
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
queryset = queryset.filter(admin_user=admin_user)
return queryset
class UserAssetListView(generics.ListAPIView):
queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer
permission_classes = (IsValidUser,)
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
queryset = self.filter_node(queryset)
queryset = self.filter_admin_user_id(queryset)
return queryset
def get_queryset(self):
assets_granted = NodePermissionUtil.get_user_assets(self.request.user).keys()
queryset = self.queryset.filter(
id__in=[asset.id for asset in assets_granted]
)
queryset = super().get_queryset().distinct()
queryset = self.get_serializer_class().setup_eager_loading(queryset)
return queryset
@@ -73,7 +95,22 @@ class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
"""
queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
class AssetBulkUpdateSelectAPI(APIView):
permission_classes = (IsOrgAdmin,)
def post(self, request, *args, **kwargs):
assets_id = request.data.get('assets_id', '')
if assets_id:
spm = uuid.uuid4().hex
key = CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX.format(spm)
cache.set(key, assets_id, 300)
url = reverse_lazy('assets:asset-bulk-update') + '?spm=%s' % spm
return Response({'url': url})
error = _('Please select assets that need to be updated')
return Response({'error': error}, status=400)
class AssetRefreshHardwareApi(generics.RetrieveAPIView):
@@ -82,7 +119,7 @@ class AssetRefreshHardwareApi(generics.RetrieveAPIView):
"""
queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
asset_id = kwargs.get('pk')
@@ -93,13 +130,32 @@ class AssetRefreshHardwareApi(generics.RetrieveAPIView):
class AssetAdminUserTestApi(generics.RetrieveAPIView):
"""
Test asset admin user connectivity
Test asset admin user assets_connectivity
"""
queryset = Asset.objects.all()
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.TaskIDSerializer
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)
task = test_asset_connectivity_manual.delay(asset)
return Response({"task": task.id})
class AssetGatewayApi(generics.RetrieveAPIView):
queryset = Asset.objects.all()
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.GatewayWithAuthSerializer
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,101 @@
# -*- coding: utf-8 -*-
#
from rest_framework.response import Response
from rest_framework import viewsets, status, generics
from rest_framework.pagination import LimitOffsetPagination
from common.permissions import IsOrgAdminOrAppUser
from common.utils import get_object_or_none, get_logger
from ..backends.multi import AssetUserManager
from ..models import Asset
from .. import serializers
from ..tasks import test_asset_users_connectivity_manual
__all__ = [
'AssetUserViewSet', 'AssetUserAuthInfoApi', 'AssetUserTestConnectiveApi',
]
logger = get_logger(__name__)
class AssetUserViewSet(viewsets.GenericViewSet):
pagination_class = LimitOffsetPagination
serializer_class = serializers.AssetUserSerializer
permission_classes = (IsOrgAdminOrAppUser, )
http_method_names = ['get', 'post']
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
def get_queryset(self):
username = self.request.GET.get('username')
asset_id = self.request.GET.get('asset_id')
asset = get_object_or_none(Asset, pk=asset_id)
queryset = AssetUserManager.filter(username=username, asset=asset)
return queryset
def filter_queryset(self, queryset):
queryset = sorted(
queryset,
key=lambda q: (q.asset.hostname, q.connectivity, q.username)
)
return queryset
class AssetUserAuthInfoApi(generics.RetrieveAPIView):
serializer_class = serializers.AssetUserAuthInfoSerializer
permission_classes = (IsOrgAdminOrAppUser,)
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
status_code = status.HTTP_200_OK
if not instance:
status_code = status.HTTP_400_BAD_REQUEST
return Response(serializer.data, status=status_code)
def get_object(self):
username = self.request.GET.get('username')
asset_id = self.request.GET.get('asset_id')
asset = get_object_or_none(Asset, pk=asset_id)
try:
instance = AssetUserManager.get(username, asset)
except Exception as e:
logger.error(e, exc_info=True)
return None
else:
return instance
class AssetUserTestConnectiveApi(generics.RetrieveAPIView):
"""
Test asset users connective
"""
def get_asset_users(self):
username = self.request.GET.get('username')
asset_id = self.request.GET.get('asset_id')
asset = get_object_or_none(Asset, pk=asset_id)
asset_users = AssetUserManager.filter(username=username, asset=asset)
return asset_users
def retrieve(self, request, *args, **kwargs):
asset_users = self.get_asset_users()
task = test_asset_users_connectivity_manual.delay(asset_users)
return Response({"task": task.id})

View File

@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
#
from rest_framework_bulk import BulkModelViewSet
from rest_framework.pagination import LimitOffsetPagination
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):
filter_fields = ("name",)
search_fields = filter_fields
permission_classes = (IsOrgAdmin,)
queryset = CommandFilter.objects.all()
serializer_class = serializers.CommandFilterSerializer
pagination_class = LimitOffsetPagination
class CommandFilterRuleViewSet(BulkModelViewSet):
filter_fields = ("content",)
search_fields = filter_fields
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.CommandFilterRuleSerializer
pagination_class = LimitOffsetPagination
def get_queryset(self):
fpk = self.kwargs.get('filter_pk')
if not fpk:
return CommandFilterRule.objects.none()
cmd_filter = get_object_or_404(CommandFilter, pk=fpk)
return cmd_filter.rules.all()

View File

@@ -2,14 +2,13 @@
from rest_framework_bulk import BulkModelViewSet
from rest_framework.views import APIView, Response
from rest_framework.generics import RetrieveAPIView
from rest_framework.pagination import LimitOffsetPagination
from django.views.generic.detail import SingleObjectMixin
from common.utils import get_logger
from ..hands import IsSuperUser, IsSuperUserOrAppUser
from common.permissions import IsOrgAdmin, IsAppUser, IsOrgAdminOrAppUser
from ..models import Domain, Gateway
from ..utils import test_gateway_connectability
from .. import serializers
@@ -19,8 +18,13 @@ __all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"]
class DomainViewSet(BulkModelViewSet):
queryset = Domain.objects.all()
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.DomainSerializer
pagination_class = LimitOffsetPagination
def get_queryset(self):
queryset = super().get_queryset().all()
return queryset
def get_serializer_class(self):
if self.request.query_params.get('gateway'):
@@ -29,26 +33,28 @@ class DomainViewSet(BulkModelViewSet):
def get_permissions(self):
if self.request.query_params.get('gateway'):
self.permission_classes = (IsSuperUserOrAppUser,)
self.permission_classes = (IsOrgAdminOrAppUser,)
return super().get_permissions()
class GatewayViewSet(BulkModelViewSet):
filter_fields = ("domain",)
filter_fields = ("domain__name", "name", "username", "ip", "domain")
search_fields = filter_fields
queryset = Gateway.objects.all()
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.GatewaySerializer
pagination_class = LimitOffsetPagination
class GatewayTestConnectionApi(SingleObjectMixin, APIView):
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
model = Gateway
object = None
def get(self, request, *args, **kwargs):
def post(self, request, *args, **kwargs):
self.object = self.get_object(Gateway.objects.all())
ok, e = test_gateway_connectability(self.object)
local_port = self.request.data.get('port') or self.object.port
ok, e = self.object.test_connective(local_port=local_port)
if ok:
return Response("ok")
else:

View File

@@ -14,10 +14,11 @@
# limitations under the License.
from rest_framework_bulk import BulkModelViewSet
from rest_framework.pagination import LimitOffsetPagination
from django.db.models import Count
from common.utils import get_logger
from ..hands import IsSuperUser
from ..hands import IsOrgAdmin
from ..models import Label
from .. import serializers
@@ -27,12 +28,18 @@ __all__ = ['LabelViewSet']
class LabelViewSet(BulkModelViewSet):
queryset = Label.objects.annotate(asset_count=Count("assets"))
permission_classes = (IsSuperUser,)
filter_fields = ("name", "value")
search_fields = filter_fields
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.LabelSerializer
pagination_class = LimitOffsetPagination
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

View File

@@ -13,32 +13,34 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from rest_framework import generics, mixins
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 common.utils import get_logger, get_object_or_none
from ..hands import IsSuperUser
from common.tree import TreeNodeSerializer
from ..hands import IsOrgAdmin
from ..models import Node
from ..tasks import update_assets_hardware_info_util, test_asset_connectability_util
from ..tasks import update_assets_hardware_info_util, test_asset_connectivity_util
from .. import serializers
logger = get_logger(__file__)
__all__ = [
'NodeViewSet', 'NodeChildrenApi',
'NodeAddAssetsApi', 'NodeRemoveAssetsApi',
'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi',
'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'NodeReplaceAssetsApi',
'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi',
'TestNodeConnectiveApi'
'TestNodeConnectiveApi', 'NodeListAsTreeApi',
'NodeChildrenAsTreeApi', 'RefreshAssetsAmount',
]
class NodeViewSet(BulkModelViewSet):
class NodeViewSet(viewsets.ModelViewSet):
queryset = Node.objects.all()
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeSerializer
def perform_create(self, serializer):
@@ -46,42 +48,173 @@ class NodeViewSet(BulkModelViewSet):
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 NodeListAsTreeApi(generics.ListAPIView):
"""
获取节点列表树
[
{
"id": "",
"name": "",
"pId": "",
"meta": ""
}
]
"""
permission_classes = (IsOrgAdmin,)
serializer_class = TreeNodeSerializer
def get_queryset(self):
queryset = [node.as_tree_node() for node in Node.objects.all()]
return queryset
def filter_queryset(self, queryset):
if self.request.query_params.get('refresh', '0') == '1':
queryset = self.refresh_nodes(queryset)
return queryset
@staticmethod
def refresh_nodes(queryset):
Node.expire_nodes_assets_amount()
Node.expire_nodes_full_value()
return queryset
class NodeChildrenAsTreeApi(generics.ListAPIView):
"""
节点子节点作为树返回,
[
{
"id": "",
"name": "",
"pId": "",
"meta": ""
}
]
"""
permission_classes = (IsOrgAdmin,)
serializer_class = TreeNodeSerializer
node = None
is_root = False
def get_queryset(self):
node_key = self.request.query_params.get('key')
if node_key:
self.node = Node.objects.get(key=node_key)
queryset = self.node.get_children(with_self=False)
else:
self.is_root = True
self.node = Node.root()
queryset = list(self.node.get_children(with_self=True))
nodes_invalid = Node.objects.exclude(key__startswith=self.node.key)
queryset.extend(list(nodes_invalid))
queryset = [node.as_tree_node() for node in queryset]
return queryset
def filter_assets(self, queryset):
include_assets = self.request.query_params.get('assets', '0') == '1'
if not include_assets:
return queryset
assets = self.node.get_assets()
for asset in assets:
queryset.append(asset.as_tree_node(self.node))
return queryset
def filter_queryset(self, queryset):
queryset = self.filter_assets(queryset)
queryset = self.filter_refresh_nodes(queryset)
return queryset
def filter_refresh_nodes(self, queryset):
if self.request.query_params.get('refresh', '0') == '1':
Node.expire_nodes_assets_amount()
Node.expire_nodes_full_value()
return queryset
class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
queryset = Node.objects.all()
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeSerializer
instance = None
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
instance = self.get_object()
if not request.data.get("value"):
request.data["value"] = _("New node {}").format(
Node.root().get_next_child_key().split(":")[-1]
)
request.data["value"] = instance.get_next_child_preset_name()
return super().post(request, *args, **kwargs)
def create(self, request, *args, **kwargs):
instance = self.get_object()
value = request.data.get("value")
node = instance.create_child(value=value)
return Response(
{"id": node.id, "key": node.key, "value": node.value},
status=201,
)
_id = request.data.get('id') or None
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, _id=_id)
return Response(self.serializer_class(instance=node).data, status=201)
def get(self, request, *args, **kwargs):
instance = self.get_object()
if self.request.query_params.get("all"):
children = instance.get_all_children()
def get_object(self):
pk = self.kwargs.get('pk') or self.request.query_params.get('id')
if not pk:
node = Node.root()
else:
children = instance.get_children()
response = [{"id": node.id, "key": node.key, "value": node.value} for node in children]
return Response(response, status=200)
node = get_object_or_404(Node, pk=pk)
return node
def get_queryset(self):
queryset = []
query_all = self.request.query_params.get("all")
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))
return queryset
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 = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeAddChildrenSerializer
instance = None
@@ -93,14 +226,13 @@ class NodeAddChildrenApi(generics.UpdateAPIView):
if not node:
continue
node.parent = instance
node.save()
return Response("OK")
class NodeAddAssetsApi(generics.UpdateAPIView):
serializer_class = serializers.NodeAssetsSerializer
queryset = Node.objects.all()
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
instance = None
def perform_update(self, serializer):
@@ -112,7 +244,7 @@ class NodeAddAssetsApi(generics.UpdateAPIView):
class NodeRemoveAssetsApi(generics.UpdateAPIView):
serializer_class = serializers.NodeAssetsSerializer
queryset = Node.objects.all()
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
instance = None
def perform_update(self, serializer):
@@ -120,30 +252,56 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView):
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 = (IsSuperUser,)
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))
assets = node.get_all_assets()
# 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 = (IsSuperUser,)
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 = test_asset_connectability_util.delay(assets, task_name=task_name)
assets = node.get_all_assets()
# task_name = _("测试节点下资产是否可连接: {}".format(node.name))
task_name = _("Test if the assets under the node are connectable: {}".format(node.name))
task = test_asset_connectivity_util.delay(assets, task_name=task_name)
return Response({"task": task.id})
class RefreshAssetsAmount(APIView):
permission_classes = (IsOrgAdmin,)
model = Node
def get(self, request, *args, **kwargs):
self.model.expire_nodes_assets_amount()
return Response("Ok")

View File

@@ -13,21 +13,28 @@
# 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 ..hands import IsSuperUser, IsSuperUserOrAppUser
from ..models import SystemUser
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
test_system_user_connectivity_manual, push_system_user_a_asset_manual, \
test_system_user_connectivity_a_asset
logger = get_logger(__file__)
__all__ = [
'SystemUserViewSet', 'SystemUserAuthInfoApi',
'SystemUserPushApi', 'SystemUserTestConnectiveApi'
'SystemUserViewSet', 'SystemUserAuthInfoApi', 'SystemUserAssetAuthInfoApi',
'SystemUserPushApi', 'SystemUserTestConnectiveApi',
'SystemUserAssetsListView', 'SystemUserPushToAssetApi',
'SystemUserTestAssetConnectivityApi', 'SystemUserCommandFilterRuleListApi',
]
@@ -35,29 +42,60 @@ class SystemUserViewSet(BulkModelViewSet):
"""
System user api set, for add,delete,update,list,retrieve resource
"""
filter_fields = ("name", "username")
search_fields = filter_fields
queryset = SystemUser.objects.all()
serializer_class = serializers.SystemUserSerializer
permission_classes = (IsSuperUserOrAppUser,)
permission_classes = (IsOrgAdminOrAppUser,)
pagination_class = LimitOffsetPagination
def get_queryset(self):
queryset = super().get_queryset().all()
return queryset
class SystemUserAuthInfoApi(generics.RetrieveUpdateAPIView):
class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
"""
Get system user auth info
"""
queryset = SystemUser.objects.all()
permission_classes = (IsSuperUserOrAppUser,)
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 SystemUserAssetAuthInfoApi(generics.RetrieveAPIView):
"""
Get system user with asset auth info
"""
queryset = SystemUser.objects.all()
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.SystemUserAuthSerializer
def get_object(self):
instance = super().get_object()
aid = self.kwargs.get('aid')
asset = get_object_or_404(Asset, pk=aid)
instance.load_specific_asset_auth(asset)
return instance
class SystemUserPushApi(generics.RetrieveAPIView):
"""
Push system user to cluster assets api
"""
queryset = SystemUser.objects.all()
permission_classes = (IsSuperUser,)
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})
@@ -67,9 +105,65 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
Push system user to cluster assets api
"""
queryset = SystemUser.objects.all()
permission_classes = (IsSuperUser,)
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
system_user = self.get_object()
task = test_system_user_connectability_manual.delay(system_user)
task = test_system_user_connectivity_manual.delay(system_user)
return Response({"task": task.id})
class SystemUserAssetsListView(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetSimpleSerializer
pagination_class = LimitOffsetPagination
filter_fields = ("hostname", "ip")
http_method_names = ['get']
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,)
serializer_class = serializers.TaskIDSerializer
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 SystemUserTestAssetConnectivityApi(generics.RetrieveAPIView):
queryset = SystemUser.objects.all()
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.TaskIDSerializer
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_connectivity_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

View File

@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
#
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
from abc import abstractmethod
class NotSupportError(Exception):
pass
class BaseBackend:
ObjectDoesNotExist = ObjectDoesNotExist
MultipleObjectsReturned = MultipleObjectsReturned
NotSupportError = NotSupportError
MSG_NOT_EXIST = '{} Object matching query does not exist'
MSG_MULTIPLE = '{} get() returned more than one object ' \
'-- it returned {}!'
@classmethod
def get(cls, username, asset):
instances = cls.filter(username, asset)
if len(instances) == 1:
return instances[0]
elif len(instances) == 0:
cls.raise_does_not_exist(cls.__name__)
else:
cls.raise_multiple_return(cls.__name__, len(instances))
@classmethod
@abstractmethod
def filter(cls, username=None, asset=None, latest=True):
"""
:param username: 用户名
:param asset: <Asset>对象
:param latest: 是否是最新记录
:return: 元素为<AuthBook>的可迭代对象(<list> or <QuerySet>)
"""
pass
@classmethod
@abstractmethod
def create(cls, **kwargs):
"""
:param kwargs:
{
name, username, asset, comment, password, public_key, private_key,
(org_id)
}
:return: <AuthBook>对象
"""
pass
@classmethod
def raise_does_not_exist(cls, name):
raise cls.ObjectDoesNotExist(cls.MSG_NOT_EXIST.format(name))
@classmethod
def raise_multiple_return(cls, name, length):
raise cls.MultipleObjectsReturned(cls.MSG_MULTIPLE.format(name, length))

View File

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

31
apps/assets/backends/external/db.py vendored Normal file
View File

@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
#
from assets.models import AuthBook
from ..base import BaseBackend
class AuthBookBackend(BaseBackend):
@classmethod
def filter(cls, username=None, asset=None, latest=True):
queryset = AuthBook.objects.all()
if username:
queryset = queryset.filter(username=username)
if asset:
queryset = queryset.filter(asset=asset)
if latest:
queryset = queryset.latest_version()
return queryset
@classmethod
def create(cls, **kwargs):
auth_info = {
'password': kwargs.pop('password', ''),
'public_key': kwargs.pop('public_key', ''),
'private_key': kwargs.pop('private_key', '')
}
obj = AuthBook.objects.create(**kwargs)
obj.set_auth(**auth_info)
return obj

16
apps/assets/backends/external/utils.py vendored Normal file
View File

@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
#
# from django.conf import settings
from .db import AuthBookBackend
# from .vault import VaultBackend
def get_backend():
default_backend = AuthBookBackend
# if settings.BACKEND_ASSET_USER_AUTH_VAULT:
# return VaultBackend
return default_backend

19
apps/assets/backends/external/vault.py vendored Normal file
View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
#
from ..base import BaseBackend
class VaultBackend(BaseBackend):
@classmethod
def get(cls, username, asset):
pass
@classmethod
def filter(cls, username=None, asset=None, latest=True):
pass
@classmethod
def create(cls, **kwargs):
pass

View File

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

View File

@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
#
from assets.models import Asset
from ..base import BaseBackend
from .utils import construct_authbook_object
class AdminUserBackend(BaseBackend):
@classmethod
def filter(cls, username=None, asset=None, **kwargs):
instances = cls.construct_authbook_objects(username, asset)
return instances
@classmethod
def _get_assets(cls, asset):
if not asset:
assets = Asset.objects.all().prefetch_related('admin_user')
else:
assets = [asset]
return assets
@classmethod
def construct_authbook_objects(cls, username, asset):
instances = []
assets = cls._get_assets(asset)
for asset in assets:
if username and asset.admin_user.username != username:
continue
instance = construct_authbook_object(asset.admin_user, asset)
instances.append(instance)
return instances
@classmethod
def create(cls, **kwargs):
raise cls.NotSupportError("Not support create")

View File

@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
#
from ..base import BaseBackend
from .admin_user import AdminUserBackend
from .system_user import SystemUserBackend
class AssetUserBackend(BaseBackend):
@classmethod
def filter(cls, username=None, asset=None, **kwargs):
admin_user_instances = AdminUserBackend.filter(username, asset)
system_user_instances = SystemUserBackend.filter(username, asset)
instances = cls._merge_instances(admin_user_instances, system_user_instances)
return instances
@classmethod
def _merge_instances(cls, admin_user_instances, system_user_instances):
admin_user_instances_keyword_list = [
{'username': instance.username, 'asset': instance.asset}
for instance in admin_user_instances
]
instances = [
instance for instance in system_user_instances
if instance.keyword not in admin_user_instances_keyword_list
]
admin_user_instances.extend(instances)
return admin_user_instances
@classmethod
def create(cls, **kwargs):
raise cls.NotSupportError("Not support create")

View File

@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
#
import itertools
from assets.models import Asset
from ..base import BaseBackend
from .utils import construct_authbook_object
class SystemUserBackend(BaseBackend):
@classmethod
def filter(cls, username=None, asset=None, **kwargs):
instances = cls.construct_authbook_objects(username, asset)
return instances
@classmethod
def _distinct_system_users_by_username(cls, system_users):
system_users = sorted(
system_users,
key=lambda su: (su.username, su.priority, su.date_updated),
reverse=True,
)
results = itertools.groupby(system_users, key=lambda su: su.username)
system_users = [next(result[1]) for result in results]
return system_users
@classmethod
def _filter_system_users_by_username(cls, system_users, username):
_system_users = cls._distinct_system_users_by_username(system_users)
if username:
_system_users = [su for su in _system_users if username == su.username]
return _system_users
@classmethod
def _construct_authbook_objects(cls, system_users, asset):
instances = []
for system_user in system_users:
instance = construct_authbook_object(system_user, asset)
instances.append(instance)
return instances
@classmethod
def _get_assets_with_system_users(cls, asset=None):
"""
{ 'asset': set(<SystemUser>, <SystemUser>, ...) }
"""
if not asset:
_assets = Asset.objects.all().prefetch_related('systemuser_set')
else:
_assets = [asset]
assets = {asset: set(asset.systemuser_set.all()) for asset in _assets}
return assets
@classmethod
def construct_authbook_objects(cls, username, asset):
"""
:return: [<AuthBook>, <AuthBook>, ...]
"""
instances = []
assets = cls._get_assets_with_system_users(asset)
for _asset, _system_users in assets.items():
_system_users = cls._filter_system_users_by_username(_system_users, username)
_instances = cls._construct_authbook_objects(_system_users, _asset)
instances.extend(_instances)
return instances
@classmethod
def create(cls, **kwargs):
raise Exception("Not support create")

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
#
from assets.models import AuthBook
def construct_authbook_object(asset_user, asset):
"""
作用: 将<AssetUser>对象构造成为<AuthBook>对象并返回
:param asset_user: <AdminUser>或<SystemUser>对象
:param asset: <Asset>对象
:return: <AuthBook>对象
"""
fields = [
'id', 'name', 'username', 'comment', 'org_id',
'_password', '_private_key', '_public_key',
'date_created', 'date_updated', 'created_by'
]
obj = AuthBook(asset=asset, version=0, is_latest=True)
for field in fields:
value = getattr(asset_user, field)
setattr(obj, field, value)
return obj

View File

@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
#
from .base import BaseBackend
from .external.utils import get_backend
from .internal.asset_user import AssetUserBackend
class AssetUserManager(BaseBackend):
"""
资产用户管理器
"""
external_backend = get_backend()
internal_backend = AssetUserBackend
@classmethod
def filter(cls, username=None, asset=None, **kwargs):
external_instance = list(cls.external_backend.filter(username, asset))
internal_instance = list(cls.internal_backend.filter(username, asset))
instances = cls._merge_instances(external_instance, internal_instance)
return instances
@classmethod
def create(cls, **kwargs):
instance = cls.external_backend.create(**kwargs)
return instance
@classmethod
def _merge_instances(cls, external_instances, internal_instances):
external_instances_keyword_list = [
{'username': instance.username, 'asset': instance.asset}
for instance in external_instances
]
instances = [
instance for instance in internal_instances
if instance.keyword not in external_instances_keyword_list
]
external_instances.extend(instances)
return external_instances

View File

@@ -32,7 +32,22 @@ TEST_SYSTEM_USER_CONN_TASKS = [
}
]
ASSET_USER_CONN_CACHE_KEY = 'ASSET_USER_CONN_{}_{}'
TEST_ASSET_USER_CONN_TASKS = [
{
"name": "ping",
"action": {
"module": "ping",
}
}
]
TASK_OPTIONS = {
'timeout': 10,
'forks': 10,
}
CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX = '_KEY_ASSET_BULK_UPDATE_ID_{}'

View File

@@ -4,3 +4,4 @@ from .asset import *
from .label import *
from .user import *
from .domain import *
from .cmd_filter import *

View File

@@ -3,20 +3,23 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from ..models import Asset, AdminUser
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(forms.ModelForm):
class AssetCreateForm(OrgModelForm):
class Meta:
model = Asset
fields = [
'hostname', 'ip', 'public_ip', 'port', 'comment',
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
'domain',
'domain', 'protocol',
]
widgets = {
@@ -27,66 +30,65 @@ class AssetCreateForm(forms.ModelForm):
'class': 'select2', 'data-placeholder': _('Admin user')
}),
'labels': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Labels')
'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': _("* required Must set exact system platform, Windows, Linux ..."),
'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(forms.ModelForm):
class AssetUpdateForm(OrgModelForm):
class Meta:
model = Asset
fields = [
'hostname', 'ip', 'port', 'nodes', 'is_active', 'platform',
'public_ip', 'number', 'comment', 'admin_user', 'labels',
'domain',
'domain', 'protocol',
]
widgets = {
'nodes': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Nodes')
'class': 'select2', 'data-placeholder': _('Node')
}),
'admin_user': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Admin user')
}),
'labels': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Labels')
'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': _("* required Must set exact system platform, Windows, Linux ..."),
'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(forms.ModelForm):
class AssetBulkUpdateForm(OrgModelForm):
assets = forms.ModelMultipleChoiceField(
required=True, help_text='* required',
required=True,
label=_('Select assets'), queryset=Asset.objects.all(),
widget=forms.SelectMultiple(
attrs={
@@ -95,34 +97,29 @@ class AssetBulkUpdateForm(forms.ModelForm):
}
)
)
port = forms.IntegerField(
label=_('Port'), required=False, min_value=1, max_value=65535,
)
admin_user = forms.ModelChoiceField(
required=False, queryset=AdminUser.objects.all(),
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'
'assets', 'port', 'admin_user', 'labels', 'platform',
'protocol', 'domain',
]
widgets = {
'labels': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Select labels')}
attrs={'class': 'select2', 'data-placeholder': _('Label')}
),
'nodes': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Select nodes')}
attrs={'class': 'select2', 'data-placeholder': _('Node')}
),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 重写其他字段为不再required
for name, field in self.fields.items():
if name != 'assets':
field.required = False
def save(self, commit=True):
changed_fields = []
for field in self._meta.fields:
@@ -133,14 +130,14 @@ class AssetBulkUpdateForm(forms.ModelForm):
if k in changed_fields}
assets = cleaned_data.pop('assets')
labels = cleaned_data.pop('labels', [])
nodes = cleaned_data.pop('nodes')
nodes = cleaned_data.pop('nodes', None)
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))
for asset in assets:
asset.labels.set(labels)
if nodes:
for node in nodes:
node.assets.add(*tuple(assets))
for asset in assets:
asset.nodes.set(nodes)
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

@@ -3,6 +3,7 @@
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
@@ -27,6 +28,15 @@ class DomainForm(forms.ModelForm):
initial['assets'] = kwargs['instance'].assets.all()
super().__init__(*args, **kwargs)
# 前端渲染优化, 防止过多资产
assets_field = self.fields.get('assets')
if not self.data:
instance = kwargs.get('instance')
if instance:
assets_field.queryset = instance.assets.all()
else:
assets_field.queryset = Asset.objects.none()
def save(self, commit=True):
instance = super().save(commit=commit)
assets = self.cleaned_data['assets']
@@ -34,7 +44,13 @@ class DomainForm(forms.ModelForm):
return instance
class GatewayForm(PasswordAndKeyAuthForm):
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')
protocol_field = self.fields.get('protocol')
protocol_field.choices = [Gateway.PROTOCOL_CHOICES[0]]
def save(self, commit=True):
# Because we define custom field, so we need rewrite :method: `save`
@@ -50,11 +66,10 @@ class GatewayForm(PasswordAndKeyAuthForm):
'name', 'ip', 'port', 'username', 'protocol', 'domain', 'password',
'private_key_file', 'is_active', 'comment',
]
help_texts = {
'protocol': _("SSH gateway support proxy SSH,RDP,VNC")
}
widgets = {
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
}
help_texts = {
'name': '* required',
'username': '* required',
}

View File

@@ -26,6 +26,15 @@ class LabelForm(forms.ModelForm):
initial['assets'] = kwargs['instance'].assets.all()
super().__init__(*args, **kwargs)
# 前端渲染优化, 防止过多资产
assets_field = self.fields.get('assets')
if not self.data:
instance = kwargs.get('instance')
if instance:
assets_field.queryset = instance.assets.all()
else:
assets_field.queryset = Asset.objects.none()
def save(self, commit=True):
label = super().save(commit=commit)
assets = self.cleaned_data['assets']

View File

@@ -3,8 +3,9 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from ..models import AdminUser, SystemUser
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__ = [
@@ -34,8 +35,12 @@ class PasswordAndKeyAuthForm(forms.ModelForm):
if private_key_file:
key_string = private_key_file.read()
private_key_file.seek(0)
key_string = key_string.decode()
if not validate_ssh_private_key(key_string, password):
raise forms.ValidationError(_('Invalid private key'))
msg = _('Invalid private key, Only support '
'RSA/DSA format key')
raise forms.ValidationError(msg)
return private_key_file
def validate_password_key(self):
@@ -79,13 +84,9 @@ class AdminUserForm(PasswordAndKeyAuthForm):
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
}
help_texts = {
'name': '* required',
'username': '* required',
}
class SystemUserForm(PasswordAndKeyAuthForm):
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)
@@ -93,14 +94,26 @@ class SystemUserForm(PasswordAndKeyAuthForm):
# 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.LOGIN_MANUAL or \
protocol in [SystemUser.PROTOCOL_RDP,
SystemUser.PROTOCOL_TELNET,
SystemUser.PROTOCOL_VNC]:
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)
system_user.set_auth(password=password, private_key=private_key,
public_key=public_key)
return system_user
def clean(self):
@@ -109,20 +122,38 @@ class SystemUserForm(PasswordAndKeyAuthForm):
if not self.instance and not auto_generate:
super().validate_password_key()
def clean_username(self):
username = self.data.get('username')
login_mode = self.data.get('login_mode')
protocol = self.data.get('protocol')
if username:
return username
if login_mode == SystemUser.LOGIN_AUTO and \
protocol != SystemUser.PROTOCOL_VNC:
msg = _('* Automatic login mode must fill in the username.')
raise forms.ValidationError(msg)
return username
class Meta:
model = SystemUser
fields = [
'name', 'username', 'protocol', 'auto_generate_key',
'password', 'private_key_file', 'auto_push', 'sudo',
'comment', 'shell', 'priority',
'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'),
}
'priority': _('1-100, 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.'),
'sudo': _("Use comma split multi command, ex: /bin/whoami,/bin/ifconfig")
}

View File

@@ -11,7 +11,6 @@
"""
from common.mixins import AdminUserRequiredMixin
from common.permissions import IsAppUser, IsSuperUser, IsValidUser, IsSuperUserOrAppUser
from common.permissions import AdminUserRequiredMixin
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
from users.models import User, UserGroup
from perms.utils import NodePermissionUtil

View File

@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-01-05 10:07
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='adminuser',
options={'ordering': ['name'], 'verbose_name': 'Admin user'},
),
migrations.AlterModelOptions(
name='asset',
options={'verbose_name': 'Asset'},
),
migrations.AlterModelOptions(
name='assetgroup',
options={'ordering': ['name'], 'verbose_name': 'Asset group'},
),
migrations.AlterModelOptions(
name='cluster',
options={'ordering': ['name'], 'verbose_name': 'Cluster'},
),
migrations.AlterModelOptions(
name='systemuser',
options={'ordering': ['name'], 'verbose_name': 'System user'},
),
]

View File

@@ -0,0 +1,158 @@
# Generated by Django 2.1.7 on 2019-02-28 10:16
import assets.models.asset
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
replaces = [('assets', '0002_auto_20180105_1807'), ('assets', '0003_auto_20180109_2331'), ('assets', '0004_auto_20180125_1218'), ('assets', '0005_auto_20180126_1637'), ('assets', '0006_auto_20180130_1502'), ('assets', '0007_auto_20180225_1815'), ('assets', '0008_auto_20180306_1804'), ('assets', '0009_auto_20180307_1212')]
dependencies = [
('assets', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='adminuser',
options={'ordering': ['name'], 'verbose_name': 'Admin user'},
),
migrations.AlterModelOptions(
name='asset',
options={'verbose_name': 'Asset'},
),
migrations.AlterModelOptions(
name='assetgroup',
options={'ordering': ['name'], 'verbose_name': 'Asset group'},
),
migrations.AlterModelOptions(
name='cluster',
options={'ordering': ['name'], 'verbose_name': 'Cluster'},
),
migrations.AlterModelOptions(
name='systemuser',
options={'ordering': ['name'], 'verbose_name': 'System user'},
),
migrations.RemoveField(
model_name='asset',
name='cluster',
),
migrations.AlterField(
model_name='assetgroup',
name='created_by',
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by'),
),
migrations.CreateModel(
name='Label',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, verbose_name='Name')),
('value', models.CharField(max_length=128, verbose_name='Value')),
('category', models.CharField(choices=[('S', 'System'), ('U', 'User')], default='U', max_length=128, 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, verbose_name='Date created')),
],
options={
'db_table': 'assets_label',
},
),
migrations.AlterUniqueTogether(
name='label',
unique_together={('name', 'value')},
),
migrations.AddField(
model_name='asset',
name='labels',
field=models.ManyToManyField(blank=True, related_name='assets', to='assets.Label', verbose_name='Labels'),
),
migrations.RemoveField(
model_name='asset',
name='cabinet_no',
),
migrations.RemoveField(
model_name='asset',
name='cabinet_pos',
),
migrations.RemoveField(
model_name='asset',
name='env',
),
migrations.RemoveField(
model_name='asset',
name='remote_card_ip',
),
migrations.RemoveField(
model_name='asset',
name='status',
),
migrations.RemoveField(
model_name='asset',
name='type',
),
migrations.CreateModel(
name='Node',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('key', models.CharField(max_length=64, unique=True, verbose_name='Key')),
('value', models.CharField(max_length=128, verbose_name='Value')),
('child_mark', models.IntegerField(default=0)),
('date_create', models.DateTimeField(auto_now_add=True)),
],
),
migrations.RemoveField(
model_name='asset',
name='groups',
),
migrations.RemoveField(
model_name='systemuser',
name='cluster',
),
migrations.AlterField(
model_name='asset',
name='admin_user',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='assets.AdminUser', verbose_name='Admin user'),
),
migrations.AlterField(
model_name='systemuser',
name='protocol',
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol'),
),
migrations.AddField(
model_name='asset',
name='nodes',
field=models.ManyToManyField(default=assets.models.asset.default_node, related_name='assets', to='assets.Node', verbose_name='Nodes'),
),
migrations.AddField(
model_name='systemuser',
name='nodes',
field=models.ManyToManyField(blank=True, to='assets.Node', verbose_name='Nodes'),
),
migrations.AlterField(
model_name='adminuser',
name='created_by',
field=models.CharField(max_length=128, null=True, verbose_name='Created by'),
),
migrations.AlterField(
model_name='adminuser',
name='username',
field=models.CharField(max_length=128, verbose_name='Username'),
),
migrations.AlterField(
model_name='asset',
name='platform',
field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'),
),
migrations.AlterField(
model_name='systemuser',
name='created_by',
field=models.CharField(max_length=128, null=True, verbose_name='Created by'),
),
migrations.AlterField(
model_name='systemuser',
name='username',
field=models.CharField(max_length=128, verbose_name='Username'),
),
]

View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-01-09 15:31
from __future__ import unicode_literals
import assets.models.asset
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('assets', '0002_auto_20180105_1807'),
]
operations = [
migrations.AlterField(
model_name='asset',
name='cluster',
field=models.ForeignKey(default=assets.models.asset.default_cluster, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='assets', to='assets.Cluster', verbose_name='Cluster'),
),
]

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-01-25 04:18
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0003_auto_20180109_2331'),
]
operations = [
migrations.AlterField(
model_name='assetgroup',
name='created_by',
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by'),
),
]

View File

@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-01-26 08:37
from __future__ import unicode_literals
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0004_auto_20180125_1218'),
]
operations = [
migrations.CreateModel(
name='Label',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, verbose_name='Name')),
('value', models.CharField(max_length=128, verbose_name='Value')),
('category', models.CharField(choices=[('S', 'System'), ('U', 'User')], default='U', max_length=128, 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, verbose_name='Date created')),
],
options={
'db_table': 'assets_label',
},
),
migrations.AlterUniqueTogether(
name='label',
unique_together=set([('name', 'value')]),
),
migrations.AddField(
model_name='asset',
name='labels',
field=models.ManyToManyField(blank=True, related_name='assets', to='assets.Label', verbose_name='Labels'),
),
]

View File

@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-01-30 07:02
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0005_auto_20180126_1637'),
]
operations = [
migrations.RemoveField(
model_name='asset',
name='cabinet_no',
),
migrations.RemoveField(
model_name='asset',
name='cabinet_pos',
),
migrations.RemoveField(
model_name='asset',
name='env',
),
migrations.RemoveField(
model_name='asset',
name='remote_card_ip',
),
migrations.RemoveField(
model_name='asset',
name='status',
),
migrations.RemoveField(
model_name='asset',
name='type',
),
]

View File

@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-02-25 10:15
from __future__ import unicode_literals
import assets.models.asset
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0006_auto_20180130_1502'),
]
operations = [
migrations.CreateModel(
name='Node',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('key', models.CharField(max_length=64, unique=True, verbose_name='Key')),
('value', models.CharField(max_length=128, unique=True, verbose_name='Value')),
('child_mark', models.IntegerField(default=0)),
('date_create', models.DateTimeField(auto_now_add=True)),
],
),
migrations.RemoveField(
model_name='asset',
name='cluster',
),
migrations.RemoveField(
model_name='asset',
name='groups',
),
migrations.RemoveField(
model_name='systemuser',
name='cluster',
),
migrations.AlterField(
model_name='asset',
name='admin_user',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='assets.AdminUser', verbose_name='Admin user'),
),
migrations.AlterField(
model_name='systemuser',
name='protocol',
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol'),
),
migrations.AddField(
model_name='asset',
name='nodes',
field=models.ManyToManyField(default=assets.models.asset.default_node, related_name='assets', to='assets.Node', verbose_name='Nodes'),
),
migrations.AddField(
model_name='systemuser',
name='nodes',
field=models.ManyToManyField(blank=True, to='assets.Node', verbose_name='Nodes'),
),
]

View File

@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-03-06 10:04
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0007_auto_20180225_1815'),
]
operations = [
migrations.AlterField(
model_name='adminuser',
name='created_by',
field=models.CharField(max_length=128, null=True, verbose_name='Created by'),
),
migrations.AlterField(
model_name='adminuser',
name='username',
field=models.CharField(max_length=128, verbose_name='Username'),
),
migrations.AlterField(
model_name='asset',
name='platform',
field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'),
),
migrations.AlterField(
model_name='systemuser',
name='created_by',
field=models.CharField(max_length=128, null=True, verbose_name='Created by'),
),
migrations.AlterField(
model_name='systemuser',
name='username',
field=models.CharField(max_length=128, verbose_name='Username'),
),
]

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-03-07 04:12
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0008_auto_20180306_1804'),
]
operations = [
migrations.AlterField(
model_name='node',
name='value',
field=models.CharField(max_length=128, verbose_name='Value'),
),
]

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-03-07 09:49
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0009_auto_20180307_1212'),
]
operations = [
migrations.AlterField(
model_name='node',
name='value',
field=models.CharField(max_length=128, unique=True, verbose_name='Value'),
),
]

View File

@@ -0,0 +1,220 @@
# Generated by Django 2.1.7 on 2019-02-28 10:16
import assets.models.utils
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import uuid
# Functions from the following migrations need manual copying.
# Move them and any dependencies into this file, then update the
# RunPython operations to refer to the local versions:
# assets.migrations.0017_auto_20180702_1415
def migrate_win_to_ssh_protocol(apps, schema_editor):
asset_model = apps.get_model("assets", "Asset")
db_alias = schema_editor.connection.alias
asset_model.objects.using(db_alias).filter(platform__startswith='Win').update(protocol='rdp')
class Migration(migrations.Migration):
replaces = [('assets', '0010_auto_20180307_1749'), ('assets', '0011_auto_20180326_0957'), ('assets', '0012_auto_20180404_1302'), ('assets', '0013_auto_20180411_1135'), ('assets', '0014_auto_20180427_1245'), ('assets', '0015_auto_20180510_1235'), ('assets', '0016_auto_20180511_1203'), ('assets', '0017_auto_20180702_1415'), ('assets', '0018_auto_20180807_1116'), ('assets', '0019_auto_20180816_1320')]
dependencies = [
('assets', '0009_auto_20180307_1212'),
]
operations = [
migrations.AlterField(
model_name='node',
name='value',
field=models.CharField(max_length=128, unique=True, verbose_name='Value'),
),
migrations.CreateModel(
name='Domain',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('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')),
],
),
migrations.CreateModel(
name='Gateway',
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(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], 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')),
('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')),
('ip', models.GenericIPAddressField(db_index=True, verbose_name='IP')),
('port', models.IntegerField(default=22, verbose_name='Port')),
('protocol', models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol')),
('comment', models.CharField(blank=True, max_length=128, null=True, verbose_name='Comment')),
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Domain', verbose_name='Domain')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='asset',
name='domain',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assets', to='assets.Domain', verbose_name='Domain'),
),
migrations.AddField(
model_name='systemuser',
name='assets',
field=models.ManyToManyField(blank=True, to='assets.Asset', verbose_name='Assets'),
),
migrations.AlterField(
model_name='systemuser',
name='sudo',
field=models.TextField(default='/bin/whoami', verbose_name='Sudo'),
),
migrations.AlterField(
model_name='adminuser',
name='username',
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='systemuser',
name='username',
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='adminuser',
name='username',
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='systemuser',
name='username',
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='node',
name='value',
field=models.CharField(max_length=128, verbose_name='Value'),
),
migrations.AddField(
model_name='asset',
name='protocol',
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=128, verbose_name='Protocol'),
),
migrations.AddField(
model_name='systemuser',
name='login_mode',
field=models.CharField(choices=[('auto', 'Automatic login'), ('manual', 'Manually login')], default='auto', max_length=10, verbose_name='Login mode'),
),
migrations.AlterField(
model_name='adminuser',
name='username',
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='asset',
name='platform',
field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Windows2016', 'Windows(2016)'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'),
),
migrations.AlterField(
model_name='systemuser',
name='protocol',
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=16, verbose_name='Protocol'),
),
migrations.AlterField(
model_name='systemuser',
name='username',
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.RunPython(
code=migrate_win_to_ssh_protocol,
),
migrations.AddField(
model_name='adminuser',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='asset',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='domain',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='gateway',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='label',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='node',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='systemuser',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AlterField(
model_name='adminuser',
name='name',
field=models.CharField(max_length=128, verbose_name='Name'),
),
migrations.AlterField(
model_name='asset',
name='hostname',
field=models.CharField(max_length=128, verbose_name='Hostname'),
),
migrations.AlterField(
model_name='gateway',
name='name',
field=models.CharField(max_length=128, verbose_name='Name'),
),
migrations.AlterField(
model_name='systemuser',
name='name',
field=models.CharField(max_length=128, verbose_name='Name'),
),
migrations.AlterUniqueTogether(
name='adminuser',
unique_together={('name', 'org_id')},
),
migrations.AddField(
model_name='asset',
name='cpu_vcpus',
field=models.IntegerField(null=True, verbose_name='CPU vcpus'),
),
migrations.AlterUniqueTogether(
name='asset',
unique_together={('org_id', 'hostname')},
),
migrations.AlterUniqueTogether(
name='gateway',
unique_together={('name', 'org_id')},
),
migrations.AlterUniqueTogether(
name='systemuser',
unique_together={('name', 'org_id')},
),
migrations.AlterUniqueTogether(
name='label',
unique_together={('name', 'value', 'org_id')},
),
]

View File

@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-03-26 01:57
from __future__ import unicode_literals
import assets.models.utils
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0010_auto_20180307_1749'),
]
operations = [
migrations.CreateModel(
name='Domain',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('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')),
],
),
migrations.CreateModel(
name='Gateway',
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=128, 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')),
('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')),
('ip', models.GenericIPAddressField(db_index=True, verbose_name='IP')),
('port', models.IntegerField(default=22, verbose_name='Port')),
('protocol', models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol')),
('comment', models.CharField(blank=True, max_length=128, null=True, verbose_name='Comment')),
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Domain', verbose_name='Domain')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='asset',
name='domain',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='assets', to='assets.Domain', verbose_name='Domain'),
),
]

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-04-04 05:02
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('assets', '0011_auto_20180326_0957'),
]
operations = [
migrations.AlterField(
model_name='asset',
name='domain',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assets', to='assets.Domain', verbose_name='Domain'),
),
]

View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-04-11 03:35
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0012_auto_20180404_1302'),
]
operations = [
migrations.AddField(
model_name='systemuser',
name='assets',
field=models.ManyToManyField(blank=True, to='assets.Asset', verbose_name='Assets'),
),
migrations.AlterField(
model_name='systemuser',
name='sudo',
field=models.TextField(default='/bin/whoami', verbose_name='Sudo'),
),
]

View File

@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-04-27 04:45
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0013_auto_20180411_1135'),
]
operations = [
migrations.AlterField(
model_name='adminuser',
name='username',
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='gateway',
name='username',
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='systemuser',
name='username',
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
),
]

View File

@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-05-10 04:35
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0014_auto_20180427_1245'),
]
operations = [
migrations.AlterField(
model_name='adminuser',
name='username',
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='gateway',
name='username',
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='systemuser',
name='username',
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
]

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-05-11 04:03
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0015_auto_20180510_1235'),
]
operations = [
migrations.AlterField(
model_name='node',
name='value',
field=models.CharField(max_length=128, verbose_name='Value'),
),
]

View File

@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-07-02 06:15
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
def migrate_win_to_ssh_protocol(apps, schema_editor):
asset_model = apps.get_model("assets", "Asset")
db_alias = schema_editor.connection.alias
asset_model.objects.using(db_alias).filter(platform__startswith='Win').update(protocol='rdp')
class Migration(migrations.Migration):
dependencies = [
('assets', '0016_auto_20180511_1203'),
]
operations = [
migrations.AddField(
model_name='asset',
name='protocol',
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=128, verbose_name='Protocol'),
),
migrations.AddField(
model_name='systemuser',
name='login_mode',
field=models.CharField(choices=[('auto', 'Automatic login'), ('manual', 'Manually login')], default='auto', max_length=10, verbose_name='Login mode'),
),
migrations.AlterField(
model_name='adminuser',
name='username',
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='asset',
name='platform',
field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Windows2016', 'Windows(2016)'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'),
),
migrations.AlterField(
model_name='gateway',
name='username',
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='systemuser',
name='protocol',
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=16, verbose_name='Protocol'),
),
migrations.AlterField(
model_name='systemuser',
name='username',
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.RunPython(migrate_win_to_ssh_protocol),
]

View File

@@ -0,0 +1,84 @@
# Generated by Django 2.0.7 on 2018-08-07 03:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0017_auto_20180702_1415'),
]
operations = [
migrations.AddField(
model_name='adminuser',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='asset',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='domain',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='gateway',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='label',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='node',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='systemuser',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AlterField(
model_name='adminuser',
name='name',
field=models.CharField(max_length=128, verbose_name='Name'),
),
migrations.AlterField(
model_name='asset',
name='hostname',
field=models.CharField(max_length=128, verbose_name='Hostname'),
),
migrations.AlterField(
model_name='gateway',
name='name',
field=models.CharField(max_length=128, verbose_name='Name'),
),
migrations.AlterField(
model_name='systemuser',
name='name',
field=models.CharField(max_length=128, verbose_name='Name'),
),
migrations.AlterUniqueTogether(
name='adminuser',
unique_together={('name', 'org_id')},
),
migrations.AlterUniqueTogether(
name='asset',
unique_together={('org_id', 'hostname')},
),
migrations.AlterUniqueTogether(
name='gateway',
unique_together={('name', 'org_id')},
),
migrations.AlterUniqueTogether(
name='systemuser',
unique_together={('name', 'org_id')},
),
]

View File

@@ -0,0 +1,22 @@
# Generated by Django 2.0.7 on 2018-08-16 05:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0018_auto_20180807_1116'),
]
operations = [
migrations.AddField(
model_name='asset',
name='cpu_vcpus',
field=models.IntegerField(null=True, verbose_name='CPU vcpus'),
),
migrations.AlterUniqueTogether(
name='label',
unique_together={('name', 'value', 'org_id')},
),
]

View File

@@ -0,0 +1,48 @@
# Generated by Django 2.0.7 on 2018-08-16 08:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0019_auto_20180816_1320'),
]
operations = [
migrations.AlterField(
model_name='adminuser',
name='org_id',
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
),
migrations.AlterField(
model_name='asset',
name='org_id',
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
),
migrations.AlterField(
model_name='domain',
name='org_id',
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
),
migrations.AlterField(
model_name='gateway',
name='org_id',
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
),
migrations.AlterField(
model_name='label',
name='org_id',
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
),
migrations.AlterField(
model_name='node',
name='org_id',
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
),
migrations.AlterField(
model_name='systemuser',
name='org_id',
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
),
]

View File

@@ -0,0 +1,25 @@
# Generated by Django 2.1 on 2018-09-03 03:32
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0020_auto_20180816_1652'),
]
operations = [
migrations.AlterModelOptions(
name='domain',
options={'verbose_name': 'Domain'},
),
migrations.AlterModelOptions(
name='gateway',
options={'verbose_name': 'Gateway'},
),
migrations.AlterModelOptions(
name='node',
options={'verbose_name': 'Node'},
),
]

View File

@@ -0,0 +1,56 @@
# Generated by Django 2.1.1 on 2018-10-12 09:17
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0021_auto_20180903_1132'),
]
operations = [
migrations.CreateModel(
name='CommandFilter',
fields=[
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('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(blank=True, default='', max_length=128, verbose_name='Created by')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='CommandFilterRule',
fields=[
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('type', models.CharField(choices=[('regex', 'Regex'), ('command', 'Command')], default='command', max_length=16, verbose_name='Type')),
('priority', models.IntegerField(default=50, help_text='1-100, the lower will be match first', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority')),
('content', models.TextField(help_text='One line one command', max_length=1024, verbose_name='Content')),
('action', models.IntegerField(choices=[(0, 'Deny'), (1, 'Allow')], default=0, verbose_name='Action')),
('comment', models.CharField(blank=True, default='', max_length=64, verbose_name='Comment')),
('date_created', models.DateTimeField(auto_now_add=True)),
('date_updated', models.DateTimeField(auto_now=True)),
('created_by', models.CharField(blank=True, default='', max_length=128, verbose_name='Created by')),
('filter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rules', to='assets.CommandFilter', verbose_name='Filter')),
],
options={
'ordering': ('priority', 'action'),
},
),
migrations.AddField(
model_name='systemuser',
name='cmd_filters',
field=models.ManyToManyField(blank=True, related_name='system_users', to='assets.CommandFilter', verbose_name='Command filter'),
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 2.1.1 on 2018-10-16 08:50
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0022_auto_20181012_1717'),
]
operations = [
migrations.AlterModelOptions(
name='commandfilterrule',
options={'ordering': ('-priority', 'action')},
),
migrations.AlterField(
model_name='commandfilterrule',
name='priority',
field=models.IntegerField(default=50, help_text='1-100, the higher will be match first', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority'),
),
migrations.AlterField(
model_name='systemuser',
name='priority',
field=models.IntegerField(default=20, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority'),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 2.1.4 on 2018-12-19 08:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0023_auto_20181016_1650'),
]
operations = [
migrations.AlterField(
model_name='asset',
name='protocol',
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)'), ('vnc', 'vnc')], default='ssh', max_length=128, verbose_name='Protocol'),
),
migrations.AlterField(
model_name='systemuser',
name='protocol',
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)'), ('vnc', 'vnc')], default='ssh', max_length=16, verbose_name='Protocol'),
),
]

View File

@@ -0,0 +1,21 @@
# Generated by Django 2.1.7 on 2019-02-21 11:02
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0024_auto_20181219_1614'),
]
operations = [
migrations.AlterModelOptions(
name='commandfilter',
options={'verbose_name': 'Command filter'},
),
migrations.AlterModelOptions(
name='commandfilterrule',
options={'ordering': ('-priority', 'action'), 'verbose_name': 'Command filter rule'},
),
]

View File

@@ -0,0 +1,43 @@
# Generated by Django 2.1.7 on 2019-03-25 12:35
import assets.models.utils
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0025_auto_20190221_1902'),
]
operations = [
migrations.CreateModel(
name='AuthBook',
fields=[
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, verbose_name='Name')),
('username', models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], 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=128, null=True, verbose_name='Created by')),
('is_latest', models.BooleanField(default=False, verbose_name='Latest version')),
('version', models.IntegerField(default=1, verbose_name='Version')),
('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Asset', verbose_name='Asset')),
],
options={
'verbose_name': 'AuthBook',
},
),
migrations.AlterModelOptions(
name='node',
options={'ordering': ['key'], 'verbose_name': 'Node'},
),
]

View File

@@ -1,11 +1,10 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from .user import AdminUser, SystemUser
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 *
from .authbook import *

View File

@@ -5,13 +5,16 @@
import uuid
import logging
import random
from functools import reduce
from collections import defaultdict
from django.db import models
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from django.core.cache import cache
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__)
@@ -30,12 +33,21 @@ def default_cluster():
def default_node():
try:
from .node import Node
return Node.root()
root = Node.root()
return root
except:
return None
class Asset(models.Model):
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'),
@@ -43,12 +55,27 @@ class Asset(models.Model):
('MacOS', 'MacOS'),
('BSD', 'BSD'),
('Windows', 'Windows'),
('Windows2016', 'Windows(2016)'),
('Other', 'Other'),
)
PROTOCOL_SSH = 'ssh'
PROTOCOL_RDP = 'rdp'
PROTOCOL_TELNET = 'telnet'
PROTOCOL_VNC = 'vnc'
PROTOCOL_CHOICES = (
(PROTOCOL_SSH, 'ssh'),
(PROTOCOL_RDP, 'rdp'),
(PROTOCOL_TELNET, 'telnet (beta)'),
(PROTOCOL_VNC, 'vnc'),
)
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=PROTOCOL_SSH, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol'))
port = models.IntegerField(default=22, verbose_name=_('Port'))
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'))
@@ -68,11 +95,11 @@ class Asset(models.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'))
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, choices=PLATFORM_CHOICES, default='Linux', 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'))
@@ -83,8 +110,17 @@ class Asset(models.Model):
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'))
objects = OrgManager.from_queryset(AssetQuerySet)()
CONNECTIVITY_CACHE_KEY = '_JMS_ASSET_CONNECTIVITY_{}'
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
CONNECTIVITY_CHOICES = (
(UNREACHABLE, _("Unreachable")),
(REACHABLE, _('Reachable')),
(UNKNOWN, _("Unknown")),
)
def __str__(self):
return self.hostname
return '{0.hostname}({0.ip})'.format(self)
@property
def is_valid(self):
@@ -95,31 +131,89 @@ class Asset(models.Model):
return True, ''
return False, warning
def support_ansible(self):
if self.platform in ("Windows", "Windows2016", "Other"):
return False
if self.protocol != 'ssh':
return False
return True
def is_unixlike(self):
if self.platform not in ("Windows",):
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_count * self.cpu_cores,
self.cpu_vcpus or self.cpu_count * self.cpu_cores,
self.memory, self.disk_total
)
else:
return ''
@property
def is_connective(self):
def connectivity(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
return self.REACHABLE
key = self.CONNECTIVITY_CACHE_KEY.format(str(self.id))
cached = cache.get(key, None)
return cached if cached is not None else self.UNKNOWN
@connectivity.setter
def connectivity(self, value):
key = self.CONNECTIVITY_CACHE_KEY.format(str(self.id))
cache.set(key, value, 3600*2)
def get_auth_info(self):
if self.admin_user:
self.admin_user.load_specific_asset_auth(self)
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 as_node(self):
from .node import Node
fake_node = Node()
fake_node.id = self.id
fake_node.key = self.id
fake_node.value = self.hostname
fake_node.asset = self
fake_node.is_node = False
return fake_node
def to_json(self):
info = {
@@ -132,24 +226,14 @@ class Asset(models.Model):
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, First using asset user,
otherwise using cluster admin user
Ansible use it create inventory
Todo: May be move to ops implements it
"""
data = self.to_json()
if self.admin_user:
self.admin_user.load_specific_asset_auth(self)
admin_user = self.admin_user
data.update({
'username': admin_user.username,
@@ -160,8 +244,38 @@ class Asset(models.Model):
})
return data
def as_tree_node(self, parent_node):
from common.tree import TreeNode
icon_skin = 'file'
if self.platform.lower() == 'windows':
icon_skin = 'windows'
elif self.platform.lower() == 'linux':
icon_skin = 'linux'
data = {
'id': str(self.id),
'name': self.hostname,
'title': self.ip,
'pId': parent_node.key,
'isParent': False,
'open': False,
'iconSkin': icon_skin,
'meta': {
'type': 'asset',
'asset': {
'id': self.id,
'hostname': self.hostname,
'ip': self.ip,
'port': self.port,
'platform': self.platform,
'protocol': self.protocol,
}
}
}
tree_node = TreeNode(**data)
return tree_node
class Meta:
unique_together = ('ip', 'port')
unique_together = [('org_id', 'hostname')]
verbose_name = _("Asset")
@classmethod
@@ -169,16 +283,23 @@ class Asset(models.Model):
from random import seed, choice
import forgery_py
from django.db import IntegrityError
from .node import Node
nodes = list(Node.objects.all())
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()),
port=22,
created_by='Fake')
try:
asset.save()
if nodes and len(nodes) > 3:
_nodes = random.sample(nodes, 3)
else:
_nodes = [Node.default_node()]
asset.nodes.set(_nodes)
asset.system_users = [choice(SystemUser.objects.all()) for i in range(3)]
logger.debug('Generate fake asset : %s' % asset.ip)
except IntegrityError:

View File

@@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
#
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.core.cache import cache
from orgs.mixins import OrgManager
from .base import AssetUser
from ..const import ASSET_USER_CONN_CACHE_KEY
__all__ = ['AuthBook']
class AuthBookQuerySet(models.QuerySet):
def latest_version(self):
return self.filter(is_latest=True)
class AuthBookManager(OrgManager):
pass
class AuthBook(AssetUser):
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset'))
is_latest = models.BooleanField(default=False, verbose_name=_('Latest version'))
version = models.IntegerField(default=1, verbose_name=_('Version'))
objects = AuthBookManager.from_queryset(AuthBookQuerySet)()
class Meta:
verbose_name = _('AuthBook')
def _set_latest(self):
self._remove_pre_obj_latest()
self.is_latest = True
self.save()
def _get_pre_obj(self):
pre_obj = self.__class__.objects.filter(
username=self.username, asset=self.asset).latest_version().first()
return pre_obj
def _remove_pre_obj_latest(self):
pre_obj = self._get_pre_obj()
if pre_obj:
pre_obj.is_latest = False
pre_obj.save()
def _set_version(self):
pre_obj = self._get_pre_obj()
if pre_obj:
self.version = pre_obj.version + 1
else:
self.version = 1
self.save()
def set_version_and_latest(self):
self._set_version()
self._set_latest()
@property
def _conn_cache_key(self):
return ASSET_USER_CONN_CACHE_KEY.format(self.id, self.asset.id)
@property
def connectivity(self):
value = cache.get(self._conn_cache_key, self.UNKNOWN)
return value
@connectivity.setter
def connectivity(self, value):
_connectivity = self.UNKNOWN
for host in value.get('dark', {}).keys():
if host == self.asset.hostname:
_connectivity = self.UNREACHABLE
for host in value.get('contacted', {}).keys():
if host == self.asset.hostname:
_connectivity = self.REACHABLE
cache.set(self._conn_cache_key, _connectivity, 3600)
@property
def keyword(self):
return {'username': self.username, 'asset': self.asset}
def __str__(self):
return '{}@{}'.format(self.username, self.asset)

View File

@@ -9,16 +9,22 @@ 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.utils import (
get_signer, ssh_key_string_to_obj, ssh_key_gen, get_logger
)
from common.validators import alphanumeric
from orgs.mixins import OrgModelMixin
from .utils import private_key_validator
signer = get_signer()
logger = get_logger(__file__)
class AssetUser(models.Model):
class AssetUser(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
username = models.CharField(max_length=128, verbose_name=_('Username'))
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'))
@@ -27,6 +33,13 @@ class AssetUser(models.Model):
date_updated = models.DateTimeField(auto_now=True)
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
CONNECTIVITY_CHOICES = (
(UNREACHABLE, _("Unreachable")),
(REACHABLE, _('Reachable')),
(UNKNOWN, _("Unknown")),
)
@property
def password(self):
if self._password:
@@ -36,8 +49,8 @@ class AssetUser(models.Model):
@password.setter
def password(self, password_raw):
raise AttributeError("Using set_auth do that")
# self._password = signer.sign(password_raw)
# raise AttributeError("Using set_auth do that")
self._password = signer.sign(password_raw)
@property
def private_key(self):
@@ -46,8 +59,8 @@ class AssetUser(models.Model):
@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)
# raise AttributeError("Using set_auth do that")
self._private_key = signer.sign(private_key_raw)
@property
def private_key_obj(self):
@@ -79,6 +92,11 @@ class AssetUser(models.Model):
else:
return None
@public_key.setter
def public_key(self, public_key_raw):
# raise AttributeError("Using set_auth do that")
self._public_key = signer.sign(public_key_raw)
@property
def public_key_obj(self):
if self.public_key:
@@ -103,10 +121,38 @@ class AssetUser(models.Model):
if update_fields:
self.save(update_fields=update_fields)
def get_auth(self, asset=None):
pass
def load_specific_asset_auth(self, asset):
from ..backends.multi import AssetUserManager
try:
other = AssetUserManager.get(username=self.username, asset=asset)
except Exception as e:
logger.error(e, exc_info=True)
else:
self._merge_auth(other)
def _merge_auth(self, other):
if not other:
return
if other.password:
self.password = other.password
if other.public_key:
self.public_key = other.public_key
if other.private_key:
self.private_key = other.private_key
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.name, password=password
username=self.username
)
self.set_auth(password=password,
private_key=private_key,

View File

@@ -52,7 +52,8 @@ class Cluster(models.Model):
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(['北京联通', '北京电信', 'BGP全网通']),
operator=choice([_('Beijing unicom'), _('Beijing telecom'), _('BGP full netcom')]),
comment=forgery_py.lorem_ipsum.sentence(),
created_by='Fake')
try:

View File

@@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
#
import uuid
import re
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 Meta:
verbose_name = _("Command filter")
class CommandFilterRule(OrgModelMixin):
TYPE_REGEX = 'regex'
TYPE_COMMAND = 'command'
TYPE_CHOICES = (
(TYPE_REGEX, _('Regex')),
(TYPE_COMMAND, _('Command')),
)
ACTION_DENY, ACTION_ALLOW, ACTION_UNKNOWN = range(3)
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 higher 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'))
__pattern = None
class Meta:
ordering = ('-priority', 'action')
verbose_name = _("Command filter rule")
@property
def _pattern(self):
if self.__pattern:
return self.__pattern
if self.type == 'command':
regex = []
for cmd in self.content.split('\r\n'):
cmd = cmd.replace(' ', '\s+')
regex.append(r'\b{0}\b'.format(cmd))
self.__pattern = re.compile(r'{}'.format('|'.join(regex)))
else:
self.__pattern = re.compile(r'{0}'.format(self.content))
return self.__pattern
def match(self, data):
found = self._pattern.search(data)
if not found:
return self.ACTION_UNKNOWN, ''
if self.action == self.ACTION_ALLOW:
return self.ACTION_ALLOW, found.group()
else:
return self.ACTION_DENY, found.group()
def __str__(self):
return '{} % {}'.format(self.type, self.content)

View File

@@ -4,21 +4,27 @@
import uuid
import random
import paramiko
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(models.Model):
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
@@ -34,19 +40,57 @@ class Domain(models.Model):
class Gateway(AssetUser):
SSH_PROTOCOL = 'ssh'
RDP_PROTOCOL = 'rdp'
PROTOCOL_SSH = 'ssh'
PROTOCOL_RDP = 'rdp'
PROTOCOL_CHOICES = (
(SSH_PROTOCOL, 'ssh'),
(RDP_PROTOCOL, 'rdp'),
(PROTOCOL_SSH, 'ssh'),
(PROTOCOL_RDP, '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, verbose_name=_("Domain"))
protocol = models.CharField(choices=PROTOCOL_CHOICES, max_length=16, default=PROTOCOL_SSH, 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")
def test_connective(self, local_port=None):
if local_port is None:
local_port = self.port
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
proxy = paramiko.SSHClient()
proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
proxy.connect(self.ip, port=self.port,
username=self.username,
password=self.password,
pkey=self.private_key_obj)
except(paramiko.AuthenticationException,
paramiko.BadAuthenticationType,
paramiko.SSHException) as e:
return False, str(e)
try:
sock = proxy.get_transport().open_channel(
'direct-tcpip', ('127.0.0.1', local_port), ('127.0.0.1', 0)
)
client.connect("127.0.0.1", port=local_port,
username=self.username,
password=self.password,
key_filename=self.private_key_file,
sock=sock,
timeout=5)
except (paramiko.SSHException, paramiko.ssh_exception.SSHException,
paramiko.AuthenticationException, TimeoutError) as e:
return False, str(e)
finally:
client.close()
return True, None

View File

@@ -4,9 +4,10 @@
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelMixin
class Label(models.Model):
class Label(OrgModelMixin):
SYSTEM_CATEGORY = "S"
USER_CATEGORY = "U"
CATEGORY_CHOICES = (
@@ -16,7 +17,8 @@ class Label(models.Model):
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"))
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(
@@ -34,4 +36,4 @@ class Label(models.Model):
class Meta:
db_table = "assets_label"
unique_together = ('name', 'value')
unique_together = [('name', 'value', 'org_id')]

View File

@@ -2,33 +2,117 @@
#
import uuid
from django.db import models
from django.db import models, transaction
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
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(models.Model):
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, unique=True, verbose_name=_("Value"))
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
_assets_amount = None
_full_value_cache_key = '_NODE_VALUE_{}'
_assets_amount_cache_key = '_NODE_ASSETS_AMOUNT_{}'
class Meta:
verbose_name = _("Node")
ordering = ['key']
def __str__(self):
return self.value
return self.full_value
def __eq__(self, other):
if not other:
return False
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 assets_amount(self):
"""
获取节点下所有资产数量速度太慢所以需要重写使用cache等方案
:return:
"""
if self._assets_amount is not None:
return self._assets_amount
cache_key = self._assets_amount_cache_key.format(self.key)
cached = cache.get(cache_key)
if cached is not None:
return cached
assets_amount = self.get_all_assets().count()
cache.set(cache_key, assets_amount, 3600)
return assets_amount
@assets_amount.setter
def assets_amount(self, value):
self._assets_amount = value
def expire_assets_amount(self):
ancestor_keys = self.get_ancestor_keys(with_self=True)
cache_keys = [self._assets_amount_cache_key.format(k) for k in ancestor_keys]
cache.delete_many(cache_keys)
@classmethod
def expire_nodes_assets_amount(cls, nodes=None):
if nodes:
for node in nodes:
node.expire_assets_amount()
return
key = cls._assets_amount_cache_key.format('*')
cache.delete_pattern(key)
@property
def full_value(self):
if self == self.__class__.root():
key = self._full_value_cache_key.format(self.key)
cached = cache.get(key)
if cached:
return cached
if self.is_root():
return self.value
else:
return '{}/{}'.format(self.value, self.parent.full_value)
parent_full_value = self.parent.full_value
value = parent_full_value + ' / ' + self.value
key = self._full_value_cache_key.format(self.key)
cache.set(key, value, 3600)
return value
def expire_full_value(self):
key = self._full_value_cache_key.format(self.key)
cache.delete_pattern(key+'*')
@classmethod
def expire_nodes_full_value(cls, nodes=None):
if nodes:
for node in nodes:
node.expire_full_value()
return
key = cls._full_value_cache_key.format('*')
cache.delete_pattern(key+'*')
@property
def level(self):
@@ -40,80 +124,186 @@ class Node(models.Model):
self.save()
return "{}:{}".format(self.key, mark)
def create_child(self, value):
child_key = self.get_next_child_key()
child = self.__class__.objects.create(key=child_key, value=value)
return child
def get_next_child_preset_name(self):
name = ugettext("New node")
values = [
child.value[child.value.rfind(' '):]
for child in self.get_children()
if child.value.startswith(name)
]
values = [int(value) for value in values if value.strip().isdigit()]
count = max(values) + 1 if values else 1
return '{} {}'.format(name, count)
def get_children(self):
return self.__class__.objects.filter(key__regex=r'{}:[0-9]+$'.format(self.key))
def create_child(self, value, _id=None):
with transaction.atomic():
child_key = self.get_next_child_key()
child = self.__class__.objects.create(id=_id, key=child_key, value=value)
return child
def get_all_children(self):
return self.__class__.objects.filter(key__startswith='{}:'.format(self.key))
def get_children(self, with_self=False):
pattern = r'^{0}$|^{0}:[0-9]+$' if with_self else r'^{0}:[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):
children = list(self.get_all_children())
children.append(self)
return children
ancestor = self.get_ancestor()
children = self.get_all_children()
return [*tuple(ancestor), self, *tuple(children)]
def get_assets(self):
from .asset import Asset
assets = Asset.objects.filter(nodes__id=self.id)
return assets
if self.is_default_node():
assets = Asset.objects.filter(Q(nodes__id=self.id) | Q(nodes__isnull=True))
else:
assets = Asset.objects.filter(nodes__id=self.id)
return assets.distinct()
def get_active_assets(self):
return self.get_assets().filter(is_active=True)
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_root():
assets = Asset.objects.all()
args.append(Q(nodes__key__regex=pattern) | Q(nodes=None))
else:
nodes = self.get_family()
assets = Asset.objects.filter(nodes__in=nodes)
kwargs['nodes__key__regex'] = pattern
assets = Asset.objects.filter(*args, **kwargs).distinct()
return assets
def get_all_active_assets(self):
return self.get_all_assets().filter(is_active=True)
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):
return self.key == '0'
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.key == "0":
return self.__class__.root()
elif not self.key.startswith("0"):
return self.__class__.root()
parent_key = ":".join(self.key.split(":")[:-1])
if self.is_root():
return self
try:
parent = self.__class__.objects.get(key=parent_key)
parent = self.__class__.objects.get(key=self.parent_key)
return parent
except Node.DoesNotExist:
return self.__class__.root()
else:
return parent
@parent.setter
def parent(self, parent):
self.key = parent.get_next_child_key()
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()
@property
def ancestor(self):
if self.parent == self.__class__.root():
return [self.__class__.root()]
else:
return [self.parent, *tuple(self.parent.ancestor)]
def get_ancestor_keys(self, with_self=False):
parent_keys = []
key_list = self.key.split(":")
if not with_self:
key_list.pop()
for i in range(len(key_list)):
parent_keys.append(":".join(key_list))
key_list.pop()
return parent_keys
@property
def ancestor_with_node(self):
ancestor = self.ancestor
ancestor.insert(0, self)
def get_ancestor(self, with_self=False):
ancestor_keys = self.get_ancestor_keys(with_self=with_self)
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 not _current_org.is_real():
return cls.default_node()
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 ['1']
key = max([int(k) for k in org_nodes_roots_keys])
key = str(key + 1) if key != 0 else '2'
set_current_org(_current_org)
root = cls.objects.create(key=key, value=_current_org.name)
return root
@classmethod
def root(cls):
obj, created = cls.objects.get_or_create(
key='0', defaults={"key": '0', 'value': "ROOT"}
)
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'}
obj, created = cls.objects.get_or_create(defaults=defaults, key='1')
return obj
def as_tree_node(self):
from common.tree import TreeNode
from ..serializers import NodeSerializer
name = '{} ({})'.format(self.value, self.assets_amount)
node_serializer = NodeSerializer(instance=self)
data = {
'id': self.key,
'name': name,
'title': name,
'pId': self.parent_key,
'isParent': True,
'open': self.is_root(),
'meta': {
'node': node_serializer.data,
'type': 'node'
}
}
tree_node = TreeNode(**data)
return tree_node
@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

@@ -7,13 +7,14 @@ import logging
from django.core.cache import cache
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.core.validators import MinValueValidator, MaxValueValidator
from common.utils import get_signer
from ..const import SYSTEM_USER_CONN_CACHE_KEY
from .base import AssetUser
__all__ = ['AdminUser', 'SystemUser',]
__all__ = ['AdminUser', 'SystemUser']
logger = logging.getLogger(__name__)
signer = get_signer()
@@ -30,6 +31,7 @@ class AdminUser(AssetUser):
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)
CONNECTIVE_CACHE_KEY = '_JMS_ADMIN_USER_CONNECTIVE_{}'
def __str__(self):
return self.name
@@ -66,8 +68,26 @@ class AdminUser(AssetUser):
def assets_amount(self):
return self.get_related_assets().count()
@property
def connectivity(self):
from .asset import Asset
assets = self.get_related_assets().values_list('id', 'hostname', flat=True)
data = {
'unreachable': [],
'reachable': [],
}
for asset_id, hostname in assets:
key = Asset.CONNECTIVITY_CACHE_KEY.format(str(self.id))
value = cache.get(key, Asset.UNKNOWN)
if value == Asset.REACHABLE:
data['reachable'].append(hostname)
elif value == Asset.UNREACHABLE:
data['unreachable'].append(hostname)
return data
class Meta:
ordering = ['name']
unique_together = [('name', 'org_id')]
verbose_name = _("Admin user")
@classmethod
@@ -92,22 +112,39 @@ class AdminUser(AssetUser):
class SystemUser(AssetUser):
SSH_PROTOCOL = 'ssh'
RDP_PROTOCOL = 'rdp'
PROTOCOL_SSH = 'ssh'
PROTOCOL_RDP = 'rdp'
PROTOCOL_TELNET = 'telnet'
PROTOCOL_VNC = 'vnc'
PROTOCOL_CHOICES = (
(SSH_PROTOCOL, 'ssh'),
(RDP_PROTOCOL, 'rdp'),
(PROTOCOL_SSH, 'ssh'),
(PROTOCOL_RDP, 'rdp'),
(PROTOCOL_TELNET, 'telnet (beta)'),
(PROTOCOL_VNC, 'vnc'),
)
LOGIN_AUTO = 'auto'
LOGIN_MANUAL = 'manual'
LOGIN_MODE_CHOICES = (
(LOGIN_AUTO, _('Automatic login')),
(LOGIN_MANUAL, _('Manually login'))
)
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
priority = models.IntegerField(default=10, verbose_name=_("Priority"))
assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets"))
priority = models.IntegerField(default=20, verbose_name=_("Priority"), validators=[MinValueValidator(1), MaxValueValidator(100)])
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(default='/sbin/ifconfig', verbose_name=_('Sudo'))
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=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode'))
cmd_filters = models.ManyToManyField('CommandFilter', related_name='system_users', verbose_name=_("Command filter"), blank=True)
SYSTEM_USER_CACHE_KEY = "__SYSTEM_USER_CACHED_{}"
CONNECTIVE_CACHE_KEY = '_JMS_SYSTEM_USER_CONNECTIVE_{}'
def __str__(self):
return self.name
return '{0.name}({0.username})'.format(self)
def to_json(self):
return {
@@ -119,34 +156,94 @@ class SystemUser(AssetUser):
'auto_push': self.auto_push,
}
@property
def assets(self):
assets = set()
for node in self.nodes.all():
assets.update(set(node.get_all_assets()))
def get_related_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
def connectivity(self):
cache_key = self.CONNECTIVE_CACHE_KEY.format(str(self.id))
value = cache.get(cache_key, None)
if not value or 'unreachable' not in value:
return {'unreachable': [], 'reachable': []}
else:
return value
@connectivity.setter
def connectivity(self, value):
data = self.connectivity
unreachable = data['unreachable']
reachable = data['reachable']
for host in value.get('dark', {}).keys():
if host not in unreachable:
unreachable.append(host)
if host in reachable:
reachable.remove(host)
for host in value.get('contacted'):
if host not in reachable:
reachable.append(host)
if host in unreachable:
unreachable.remove(host)
cache_key = self.CONNECTIVE_CACHE_KEY.format(str(self.id))
cache.set(cache_key, data, 3600)
@property
def unreachable_assets(self):
return list(self.assets_connective.get('dark', {}).keys())
def assets_unreachable(self):
return self.connectivity.get('unreachable')
@property
def reachable_assets(self):
return self.assets_connective.get('contacted', [])
def assets_reachable(self):
return self.connectivity.get('reachable')
@property
def login_mode_display(self):
return self.get_login_mode_display()
def is_need_push(self):
if self.auto_push and self.protocol == self.__class__.SSH_PROTOCOL:
if self.auto_push and self.protocol == self.PROTOCOL_SSH:
return True
else:
return False
def set_cache(self):
cache.set(self.SYSTEM_USER_CACHE_KEY.format(self.id), self, 3600)
def expire_cache(self):
cache.delete(self.SYSTEM_USER_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()
).distinct()
return rules
def is_command_can_run(self, command):
for rule in self.cmd_filter_rules:
action, matched_cmd = rule.match(command)
if action == rule.ACTION_ALLOW:
return True, None
elif action == rule.ACTION_DENY:
return False, matched_cmd
return True, None
@classmethod
def get_system_user_by_id_or_cached(cls, sid):
cached = cache.get(cls.SYSTEM_USER_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
@@ -168,6 +265,3 @@ class SystemUser(AssetUser):
except IntegrityError:
print('Error continue')
continue

View File

@@ -7,3 +7,5 @@ from .label import *
from .system_user import *
from .node import *
from .domain import *
from .cmd_filter import *
from .asset_user import *

View File

@@ -3,6 +3,8 @@
from django.core.cache import cache
from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer
from ..models import Node, AdminUser
from ..const import ADMIN_USER_CONN_CACHE_KEY
@@ -18,6 +20,7 @@ class AdminUserSerializer(serializers.ModelSerializer):
reachable_amount = serializers.SerializerMethodField()
class Meta:
list_serializer_class = AdaptedBulkListSerializer
model = AdminUser
fields = '__all__'
@@ -66,4 +69,5 @@ class ReplaceNodeAdminUserSerializer(serializers.ModelSerializer):
fields = ['id', 'nodes']
class TaskIDSerializer(serializers.Serializer):
task = serializers.CharField(read_only=True)

View File

@@ -1,45 +1,63 @@
# -*- coding: utf-8 -*-
#
from rest_framework import serializers
from rest_framework_bulk.serializers import BulkListSerializer
from common.mixins import BulkSerializerMixin
from common.serializers import AdaptedBulkListSerializer
from ..models import Asset
from .system_user import AssetSystemUserSerializer
__all__ = [
'AssetSerializer', 'AssetGrantedSerializer', 'MyAssetGrantedSerializer',
'AssetAsNodeSerializer', 'AssetSimpleSerializer',
]
class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
"""
资产的数据结构
"""
class Meta:
model = Asset
list_serializer_class = BulkListSerializer
list_serializer_class = AdaptedBulkListSerializer
fields = '__all__'
validators = [] # If not set to [], partial bulk update will be error
validators = []
@classmethod
def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """
queryset = queryset.prefetch_related('labels', 'nodes')\
.select_related('admin_user')
return queryset
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
fields.extend([
'hardware_info', 'is_connective',
'hardware_info', 'connectivity', 'org_name'
])
return fields
class AssetAsNodeSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
fields = ['id', 'hostname', 'ip', 'port', 'platform', 'protocol']
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"
"platform", "comment", "protocol", "org_id", "org_name",
)
@staticmethod
@@ -57,6 +75,12 @@ class MyAssetGrantedSerializer(AssetGrantedSerializer):
model = Asset
fields = (
"id", "hostname", "system_users_granted",
"is_active", "system_users_join",
"os", "platform", "comment",
"is_active", "system_users_join", "org_name",
"os", "platform", "comment", "org_id", "protocol"
)
class AssetSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
fields = ['id', 'hostname', 'port', 'ip', 'connectivity']

View File

@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext as _
from rest_framework import serializers
from ..models import AuthBook
from ..backends.multi import AssetUserManager
__all__ = [
'AssetUserSerializer', 'AssetUserAuthInfoSerializer',
]
class AssetUserSerializer(serializers.ModelSerializer):
password = serializers.CharField(
max_length=256, allow_blank=True, allow_null=True, write_only=True,
required=False, help_text=_('Password')
)
public_key = serializers.CharField(
max_length=4096, allow_blank=True, allow_null=True, write_only=True,
required=False, help_text=_('Public key')
)
private_key = serializers.CharField(
max_length=4096, allow_blank=True, allow_null=True, write_only=True,
required=False, help_text=_('Private key')
)
class Meta:
model = AuthBook
read_only_fields = (
'date_created', 'date_updated', 'created_by',
'is_latest', 'version', 'connectivity',
)
fields = '__all__'
extra_kwargs = {
'username': {'required': True}
}
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
fields = [f for f in fields if not f.startswith('_') and f != 'id']
fields.extend(['connectivity'])
return fields
def create(self, validated_data):
kwargs = {
'name': validated_data.get('name'),
'username': validated_data.get('username'),
'asset': validated_data.get('asset'),
'comment': validated_data.get('comment', ''),
'org_id': validated_data.get('org_id', ''),
'password': validated_data.get('password'),
'public_key': validated_data.get('public_key'),
'private_key': validated_data.get('private_key')
}
instance = AssetUserManager.create(**kwargs)
return instance
class AssetUserAuthInfoSerializer(serializers.ModelSerializer):
class Meta:
model = AuthBook
fields = ['password', 'private_key', 'public_key']

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
#
from rest_framework import serializers
from common.fields import ChoiceDisplayField
from common.serializers import AdaptedBulkListSerializer
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
list_serializer_class = AdaptedBulkListSerializer
fields = '__all__'
class CommandFilterRuleSerializer(serializers.ModelSerializer):
serializer_choice_field = ChoiceDisplayField
class Meta:
model = CommandFilterRule
fields = '__all__'
list_serializer_class = AdaptedBulkListSerializer

View File

@@ -2,6 +2,8 @@
#
from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer
from ..models import Domain, Gateway
@@ -12,6 +14,7 @@ class DomainSerializer(serializers.ModelSerializer):
class Meta:
model = Domain
fields = '__all__'
list_serializer_class = AdaptedBulkListSerializer
@staticmethod
def get_asset_count(obj):
@@ -23,9 +26,9 @@ class DomainSerializer(serializers.ModelSerializer):
class GatewaySerializer(serializers.ModelSerializer):
class Meta:
model = Gateway
list_serializer_class = AdaptedBulkListSerializer
fields = [
'id', 'name', 'ip', 'port', 'protocol', 'username',
'domain', 'is_active', 'date_created', 'date_updated',

View File

@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
#
from rest_framework import serializers
from rest_framework_bulk.serializers import BulkListSerializer
from common.serializers import AdaptedBulkListSerializer
from ..models import Label
@@ -12,7 +13,7 @@ class LabelSerializer(serializers.ModelSerializer):
class Meta:
model = Label
fields = '__all__'
list_serializer_class = BulkListSerializer
list_serializer_class = AdaptedBulkListSerializer
@staticmethod
def get_asset_count(obj):

View File

@@ -1,63 +1,36 @@
# -*- 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
class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer):
"""
授权资产组
"""
assets_granted = AssetGrantedSerializer(many=True, read_only=True)
assets_amount = serializers.SerializerMethodField()
parent = serializers.SerializerMethodField()
name = serializers.SerializerMethodField()
__all__ = [
'NodeSerializer', "NodeAddChildrenSerializer",
"NodeAssetsSerializer",
]
class NodeSerializer(serializers.ModelSerializer):
assets_amount = serializers.IntegerField(read_only=True)
class Meta:
model = Node
fields = [
'id', 'key', 'name', 'value', 'parent',
'assets_granted', 'assets_amount',
'id', 'key', 'value', 'assets_amount', 'org_id',
]
read_only_fields = [
'key', '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):
parent = serializers.SerializerMethodField()
assets_amount = serializers.SerializerMethodField()
class Meta:
model = Node
fields = ['id', 'key', 'value', 'parent', 'assets_amount']
list_serializer_class = BulkListSerializer
@staticmethod
def get_parent(obj):
return obj.parent.id
@staticmethod
def get_assets_amount(obj):
return obj.get_all_assets().count()
def get_fields(self):
fields = super().get_fields()
field = fields["key"]
field.required = False
return fields
def validate_value(self, data):
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 data in values:
raise serializers.ValidationError(
'The same level node name cannot be the same'
)
return data
class NodeAssetsSerializer(serializers.ModelSerializer):
@@ -70,3 +43,4 @@ class NodeAssetsSerializer(serializers.ModelSerializer):
class NodeAddChildrenSerializer(serializers.Serializer):
nodes = serializers.ListField()

View File

@@ -1,6 +1,8 @@
from rest_framework import serializers
from ..models import SystemUser
from common.serializers import AdaptedBulkListSerializer
from ..models import SystemUser, Asset
from .base import AuthSerializer
@@ -17,14 +19,22 @@ class SystemUserSerializer(serializers.ModelSerializer):
class Meta:
model = SystemUser
exclude = ('_password', '_private_key', '_public_key')
list_serializer_class = AdaptedBulkListSerializer
def get_field_names(self, declared_fields, info):
fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info)
fields.extend([
'login_mode_display',
])
return fields
@staticmethod
def get_unreachable_assets(obj):
return obj.unreachable_assets
return obj.assets_unreachable
@staticmethod
def get_reachable_assets(obj):
return obj.reachable_assets
return obj.assets_reachable
def get_unreachable_amount(self, obj):
return len(self.get_unreachable_assets(obj))
@@ -34,7 +44,7 @@ class SystemUserSerializer(serializers.ModelSerializer):
@staticmethod
def get_assets_amount(obj):
return len(obj.assets)
return len(obj.get_related_assets())
class SystemUserAuthSerializer(AuthSerializer):
@@ -46,7 +56,7 @@ class SystemUserAuthSerializer(AuthSerializer):
model = SystemUser
fields = [
"id", "name", "username", "protocol",
"password", "private_key",
"login_mode", "password", "private_key",
]
@@ -54,9 +64,18 @@ class AssetSystemUserSerializer(serializers.ModelSerializer):
"""
查看授权的资产系统用户的数据结构这个和AssetSerializer不同字段少
"""
actions = serializers.SerializerMethodField()
class Meta:
model = SystemUser
fields = ('id', 'name', 'username', 'priority', 'protocol', 'comment',)
fields = (
'id', 'name', 'username', 'priority',
'protocol', 'comment', 'login_mode', 'actions',
)
@staticmethod
def get_actions(obj):
return [action.name for action in obj.actions]
class SystemUserSimpleSerializer(serializers.ModelSerializer):
@@ -65,4 +84,7 @@ class SystemUserSimpleSerializer(serializers.ModelSerializer):
"""
class Meta:
model = SystemUser
fields = ('id', 'name', 'username')
fields = ('id', 'name', 'username')

View File

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

View File

@@ -1,14 +1,16 @@
# -*- coding: utf-8 -*-
#
from django.db.models.signals import post_save, m2m_changed
from collections import defaultdict
from django.db.models.signals import post_save, m2m_changed, post_delete
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_node, \
push_node_system_users_to_asset
from .models import Asset, SystemUser, Node, AuthBook
from .tasks import (
update_assets_hardware_info_util,
test_asset_connectivity_util,
push_system_user_to_assets
)
logger = get_logger(__file__)
@@ -20,8 +22,8 @@ def update_asset_hardware_info_on_created(asset):
def test_asset_conn_on_created(asset):
logger.debug("Test asset `{}` connectability".format(asset))
test_asset_connectability_util.delay([asset])
logger.debug("Test asset `{}` connectivity".format(asset))
test_asset_connectivity_util.delay([asset])
def set_asset_root_node(asset):
@@ -31,41 +33,89 @@ def set_asset_root_node(asset):
@receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier")
def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
set_asset_root_node(instance)
if created:
logger.info("Asset `{}` create signal received".format(instance))
update_asset_hardware_info_on_created(instance)
test_asset_conn_on_created(instance)
# 过期节点资产数量
nodes = instance.nodes.all()
Node.expire_nodes_assets_amount(nodes)
@receiver(post_delete, sender=Asset, dispatch_uid="my_unique_identifier")
def on_asset_delete(sender, instance=None, **kwargs):
# 过期节点资产数量
nodes = instance.nodes.all()
Node.expire_nodes_assets_amount(nodes)
@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:
for node in instance.nodes.all():
push_system_user_to_node(instance, node)
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_node_change(sender, instance=None, **kwargs):
def on_system_user_nodes_change(sender, instance=None, **kwargs):
if instance and kwargs["action"] == "post_add":
for pk in kwargs['pk_set']:
node = kwargs['model'].objects.get(pk=pk)
push_system_user_to_node(instance, node)
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.delay(instance, assets)
@receiver(m2m_changed, sender=Asset.nodes.through)
def on_asset_node_changed(sender, instance=None, **kwargs):
if isinstance(instance, Asset) and kwargs['action'] == 'post_add':
logger.debug("Asset node change signal received")
for pk in kwargs['pk_set']:
node = kwargs['model'].objects.get(pk=pk)
push_node_system_users_to_asset(node, [instance])
logger.debug("Asset nodes change signal received")
if isinstance(instance, Asset):
if kwargs['action'] == 'pre_remove':
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
Node.expire_nodes_assets_amount(nodes)
if kwargs['action'] == 'post_add':
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
Node.expire_nodes_assets_amount(nodes)
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) and kwargs['action'] == 'post_add':
logger.debug("Node assets change signal received")
if isinstance(instance, Node):
logger.debug("Node assets change signal {} received".format(instance))
# 当节点和资产关系发生改变时,过期资产数量缓存
instance.expire_assets_amount()
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
push_node_system_users_to_asset(instance, assets)
if kwargs['action'] == 'post_add':
# 重新关联系统用户和资产的关系
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()
@receiver(post_save, sender=AuthBook)
def on_auth_book_created(sender, instance=None, created=False, **kwargs):
if created:
logger.debug('Receive create auth book object signal.')
instance.set_version_and_latest()

View File

@@ -4,14 +4,15 @@ import re
import os
from celery import shared_task
from django.core.cache import cache
from django.utils.translation import ugettext as _
from django.core.cache import cache
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 common.utils import (
capacity_convert, sum_capacity, encrypt_password, get_logger
)
from ops.celery.decorator import (
register_as_period_task, after_app_shutdown_clean_periodic
)
from .models import SystemUser, AdminUser, Asset
from . import const
@@ -20,34 +21,57 @@ from . import const
FORKS = 10
TIMEOUT = 60
logger = get_logger(__file__)
CACHE_MAX_TIME = 60*60*60
CACHE_MAX_TIME = 60*60*2
disk_pattern = re.compile(r'^hd|sd|xvd|vd')
PERIOD_TASK = os.environ.get("PERIOD_TASK", "on")
def check_asset_can_run_ansible(asset):
if not asset.is_active:
msg = _("Asset has been disabled, skipped: {}").format(asset)
logger.info(msg)
return False
if not asset.support_ansible():
msg = _("Asset may not be support ansible, skipped: {}").format(asset)
logger.info(msg)
return False
return True
def clean_hosts(assets):
clean_assets = []
for asset in assets:
if not check_asset_can_run_ansible(asset):
continue
clean_assets.append(asset)
if not clean_assets:
print(_("No assets matched, stop task"))
return clean_assets
@shared_task
def set_assets_hardware_info(result, **kwargs):
def set_assets_hardware_info(assets, 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 assets:
:param result:
:param kwargs: {task_name: ""}
:return:
"""
result_raw = result[0]
assets_updated = []
for hostname, info in result_raw.get('ok', {}).items():
success_result = result_raw.get('ok', {})
for asset in assets:
hostname = asset.hostname
info = success_result.get(hostname, {})
info = info.get('setup', {}).get('ansible_facts', {})
if not info:
logger.error("Get asset info failed: {}".format(hostname))
logger.error(_("Get asset info failed: {}").format(hostname))
continue
asset = get_object_or_none(Asset, hostname=hostname)
if not asset:
continue
___vendor = info.get('ansible_system_vendor', 'Unknown')
___model = info.get('ansible_product_name', 'Unknown')
___sn = info.get('ansible_product_serial', 'Unknown')
@@ -59,8 +83,12 @@ def set_assets_hardware_info(result, **kwargs):
___cpu_model = 'Unknown'
___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', []))
___memory = '%s %s' % capacity_convert('{} MB'.format(info.get('ansible_memtotal_mb')))
___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.get('ansible_devices', {}).items():
if disk_pattern.match(dev) and dev_info['removable'] == '0':
@@ -92,32 +120,30 @@ def update_assets_hardware_info_util(assets, task_name=None):
"""
from ops.utils import update_or_create_ansible_task
if task_name is None:
# task_name = _("Update some assets hardware info")
task_name = _("更新资产硬件信息")
task_name = _("Update some assets hardware info")
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
hostname_list = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()]
hosts = clean_hosts(assets)
if not hosts:
return {}
created_by = str(assets[0].org_id)
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',
task_name, hosts=hosts, tasks=tasks, created_by=created_by,
pattern='all', options=const.TASK_OPTIONS, run_as_admin=True,
)
result = task.run()
# Todo: may be somewhere using
# Manual run callback function
set_assets_hardware_info(result)
set_assets_hardware_info(assets, result)
return result
@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)
task_name = _("Update asset hardware info: {}").format(asset.hostname)
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
@shared_task
def update_assets_hardware_info_period():
"""
Update asset hardware period task
@@ -127,126 +153,41 @@ def update_assets_hardware_info_period():
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.hostname 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.hostname 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):
def test_asset_connectivity_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.hostname for asset in assets if asset.is_active and asset.is_unixlike()]
task_name = _("Test assets connectivity")
hosts = clean_hosts(assets)
if not hosts:
logger.info("No hosts, passed")
return {}
tasks = const.TEST_ADMIN_USER_CONN_TASKS
created_by = assets[0].org_id
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',
options=const.TASK_OPTIONS, run_as_admin=True, created_by=created_by,
)
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)
for asset in assets:
if asset.hostname in summary.get('dark', {}):
asset.connectivity = asset.UNREACHABLE
elif asset.hostname in summary.get('contacted', []):
asset.connectivity = asset.REACHABLE
else:
asset.connectivity = asset.UNKNOWN
return summary
@shared_task
def test_asset_connectability_manual(asset):
summary = test_asset_connectability_util([asset])
def test_asset_connectivity_manual(asset):
task_name = _("Test assets connectivity: {}").format(asset)
summary = test_asset_connectivity_util([asset], task_name=task_name)
if summary.get('dark'):
return False, summary['dark']
@@ -254,64 +195,108 @@ def test_asset_connectability_manual(asset):
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, task_name):
def test_admin_user_connectivity_util(admin_user, task_name):
"""
Test system cant connect his assets or not.
:param system_user:
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 = system_user.assets
hosts = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()]
tasks = const.TEST_SYSTEM_USER_CONN_TASKS
assets = admin_user.get_related_assets()
hosts = clean_hosts(assets)
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)
return test_system_user_connectability_util(system_user, task_name)
summary = test_asset_connectivity_util(hosts, task_name)
return summary
@shared_task
@register_as_period_task(interval=3600)
@after_app_ready_start
@after_app_shutdown_clean
def test_system_user_connectability_period():
def test_admin_user_connectivity_period():
"""
A period task that update the ansible task period
"""
if PERIOD_TASK != "on":
logger.debug("Period task disabled, test system user connectability pass")
logger.debug('Period task off, skip')
return
key = '_JMS_TEST_ADMIN_USER_CONNECTIVITY_PERIOD'
prev_execute_time = cache.get(key)
if prev_execute_time:
logger.debug("Test admin user connectivity, less than 40 minutes, skip")
return
cache.set(key, 1, 60*40)
admin_users = AdminUser.objects.all()
for admin_user in admin_users:
task_name = _("Test admin user connectivity period: {}").format(admin_user.name)
test_admin_user_connectivity_util(admin_user, task_name)
cache.set(key, 1, 60*40)
@shared_task
def test_admin_user_connectivity_manual(admin_user):
task_name = _("Test admin user connectivity: {}").format(admin_user.name)
test_admin_user_connectivity_util(admin_user, task_name)
return True
## System user connective ##
@shared_task
def set_system_user_connectivity_info(system_user, result):
summary = result[1]
system_user.connectivity = summary
@shared_task
def test_system_user_connectivity_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
tasks = const.TEST_SYSTEM_USER_CONN_TASKS
hosts = clean_hosts(assets)
if not hosts:
return {}
task, created = update_or_create_ansible_task(
task_name, hosts=hosts, tasks=tasks, pattern='all',
options=const.TASK_OPTIONS,
run_as=system_user.username, created_by=system_user.org_id,
)
result = task.run()
set_system_user_connectivity_info(system_user, result)
return result
@shared_task
def test_system_user_connectivity_manual(system_user):
task_name = _("Test system user connectivity: {}").format(system_user)
assets = system_user.get_related_assets()
return test_system_user_connectivity_util(system_user, assets, task_name)
@shared_task
def test_system_user_connectivity_a_asset(system_user, asset):
task_name = _("Test system user connectivity: {} => {}").format(
system_user, asset
)
return test_system_user_connectivity_util(system_user, [asset], task_name)
@shared_task
def test_system_user_connectivity_period():
if PERIOD_TASK != "on":
logger.debug("Period task disabled, test system user connectivity 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)
task_name = _("Test system user connectivity period: {}").format(system_user)
assets = system_user.get_related_assets()
test_system_user_connectivity_util(system_user, assets, task_name)
#### Push system user tasks ####
@@ -333,6 +318,24 @@ def get_push_system_user_tasks(system_user):
),
}
})
tasks.extend([
{
'name': 'Check home dir exists',
'action': {
'module': 'stat',
'args': 'path=/home/{}'.format(system_user.username)
},
'register': 'home_existed'
},
{
'name': "Set home dir permission",
'action': {
'module': 'file',
'args': "path=/home/{0} owner={0} group={0} mode=700".format(system_user.username)
},
'when': 'home_existed.stat.exists == true'
}
])
if system_user.public_key:
tasks.append({
'name': 'Set {} authorized key'.format(system_user.username),
@@ -344,6 +347,12 @@ def get_push_system_user_tasks(system_user):
}
})
if system_user.sudo:
sudo = system_user.sudo.replace('\r\n', '\n').replace('\r', '\n')
sudo_list = sudo.split('\n')
sudo_tmp = []
for s in sudo_list:
sudo_tmp.append(s.strip(','))
sudo = ','.join(sudo_tmp)
tasks.append({
'name': 'Set {} sudo setting'.format(system_user.username),
'action': {
@@ -351,8 +360,7 @@ def get_push_system_user_tasks(system_user):
'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,
system_user.username, sudo,
)
}
})
@@ -360,84 +368,102 @@ def get_push_system_user_tasks(system_user):
@shared_task
def push_system_user_util(system_users, assets, task_name):
def push_system_user_util(system_user, 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.hostname 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()
def get_node_push_system_user_task_name(system_user, node):
# return _("Push system user to node: {} => {}").format(
return _("推送系统用户到节点资产: {} => {}").format(
system_user.name,
node.value
)
@shared_task
def push_system_user_to_node(system_user, node):
logger.info("Start push system user node: {} => {}".format(system_user.name, node.value))
assets = node.get_all_assets()
task_name = get_node_push_system_user_task_name(system_user, node)
push_system_user_util([system_user], assets, task_name)
@shared_task
def push_system_user_related_nodes(system_user):
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)
msg = _("Push system user task skip, auto push not enable or "
"protocol is not ssh: {}").format(system_user.name)
logger.info(msg)
return
nodes = system_user.nodes.all()
for node in nodes:
push_system_user_to_node(system_user, node)
hosts = clean_hosts(assets)
if not hosts:
return {}
for host in hosts:
system_user.load_specific_asset_auth(host)
tasks = get_push_system_user_tasks(system_user)
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=[host], tasks=tasks, pattern='all',
options=const.TASK_OPTIONS, run_as_admin=True,
created_by=system_user.org_id,
)
task.run()
@shared_task
def push_system_user_to_assets_manual(system_user):
push_system_user_related_nodes(system_user)
assets = system_user.get_related_assets()
task_name = _("Push system users to assets: {}").format(system_user.name)
return push_system_user_util(system_user, assets, task_name=task_name)
def push_node_system_users_to_asset(node, assets):
system_users = []
nodes = node.ancestor_with_node
# 获取该节点所有父节点有的系统用户, 然后推送
for n in nodes:
system_users.extend(list(n.systemuser_set.all()))
@shared_task
def push_system_user_a_asset_manual(system_user, asset):
task_name = _("Push system users to asset: {} => {}").format(
system_user.name, asset
)
return push_system_user_util(system_user, [asset], task_name=task_name)
if system_users:
# task_name = _("Push system users to node: {}").format(node.value)
task_name = _("推送节点系统用户到新加入资产中: {}").format(node.value)
push_system_user_util.delay(system_users, assets, task_name)
@shared_task
def push_system_user_to_assets(system_user, assets):
task_name = _("Push system users to assets: {}").format(system_user.name)
return push_system_user_util(system_user, assets, task_name)
@shared_task
@after_app_shutdown_clean_periodic
def test_system_user_connectability_period():
pass
@shared_task
@after_app_shutdown_clean_periodic
def test_admin_user_connectability_period():
pass
@shared_task
def set_asset_user_connectivity_info(asset_user, result):
summary = result[1]
asset_user.connectivity = summary
@shared_task
def test_asset_user_connectivity_util(asset_user, task_name):
"""
:param asset_user: <AuthBook>对象
:param task_name:
:return:
"""
from ops.utils import update_or_create_ansible_task
tasks = const.TEST_ASSET_USER_CONN_TASKS
if not check_asset_can_run_ansible(asset_user.asset):
return
task, created = update_or_create_ansible_task(
task_name, hosts=[asset_user.asset], tasks=tasks, pattern='all',
options=const.TASK_OPTIONS,
run_as=asset_user.username, created_by=asset_user.org_id
)
result = task.run()
set_asset_user_connectivity_info(asset_user, result)
@shared_task
def test_asset_users_connectivity_manual(asset_users):
"""
:param asset_users: <AuthBook>对象
"""
for asset_user in asset_users:
task_name = _("Test asset user connectivity: {}").format(asset_user)
test_asset_user_connectivity_util(asset_user, task_name)
# @shared_task
# @register_as_period_task(interval=3600)
# @after_app_ready_start
# # @after_app_shutdown_clean
# @after_app_shutdown_clean_periodic
# def push_system_user_period():
# for system_user in SystemUser.objects.all():
# push_system_user_related_nodes(system_user)

View File

@@ -31,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

@@ -1,132 +1,124 @@
{% extends '_modal.html' %}
{% load i18n %}
{% load static %}
{% block modal_class %}modal-lg{% endblock %}
{% block modal_id %}asset_list_modal{% endblock %}
{#{% block modal_title%}{% trans "Please select assets" %}{% endblock %}#}
{% block modal_title%}{% trans "Asset list" %}{% endblock %}
{% block modal_body %}
{#<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_modal_table" 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="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>
<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 modal_table;
function initModalTable() {
var options = {
ele: $('#asset_modal_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
{% 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: 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: 5, 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: 6, 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: "cpu_cores"}, {data: "is_active", orderable: false },
{data: "is_connective", orderable: false}, {data: "id", orderable: false }
],
op_html: $('#actions').html()
};
modal_table = jumpserver.initServerSideDataTable(options);
return modal_table;
}
$(document).ready(function(){
initModalTable();
}).on('click', '#btn_select_assets', function () {
var data_table = $('#asset_modal_table').DataTable();
var id_list = [];
data_table.rows({selected: true}).every(function(){
id_list.push(this.data().id);
});
var current_node;
var nodes = zTree.getSelectedNodes();
if (nodes && nodes.length === 1) {
current_node = nodes[0]
} else {
var zTree2, asset_table2 = 0;
function initTable2() {
if(asset_table2){
return
}
var data = {
'assets': id_list
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
}
var success = function () {
modal_table.ajax.reload()
function onNodeSelected2(event, treeNode) {
var url = asset_table2.ajax.url();
url = setUrlParam(url, "node_id", treeNode.meta.node.id);
asset_table2.ajax.url(url);
asset_table2.ajax.reload();
}
function initTree2() {
var url = '{% url 'api-assets:node-children-tree' %}?assets=0';
var setting = {
view: {
dblClickExpand: false,
showLine: true
},
data: {
simpleData: {
enable: true
}
},
async: {
enable: true,
url: url,
autoParam: ["id=key", "name=n", "level=lv"],
type: 'get'
},
callback: {
onSelected: onNodeSelected2
}
};
zTree2 = $.fn.zTree.init($("#assetTree2"), setting);
}
APIUpdateAttr({
'url': '/api/assets/v1/nodes/' + current_node.id + '/assets/add/',
'method': 'PUT',
'body': JSON.stringify(data),
'success': success
})
$(document).ready(function(){
}).on('show.bs.modal', function () {
initTable2();
initTree2();
})
</script>
{% endblock %}
{% block modal_confirm_id %}btn_select_assets{% endblock %}
{% block modal_button %}
{{ block.super }}
{% endblock %}
{% block modal_confirm_id %}btn_asset_modal_confirm{% endblock %}

View File

@@ -0,0 +1,28 @@
{% extends '_modal.html' %}
{% load i18n %}
{% block modal_id %}asset_user_auth_modal{% endblock %}
{% block modal_title%}{% trans "Update asset user auth" %}{% endblock %}
{% block modal_body %}
<form class="form-horizontal" role="form" onkeydown="if(event.keyCode==13){ $('#btn_asset_user_auth_modal_confirm').trigger('click'); return false;}">
{% csrf_token %}
<div class="form-group">
<label class="col-sm-2 control-label">{% trans "Hostname" %}</label>
<div class="col-sm-10">
<p class="form-control-static" id="id_hostname_p"></p>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">{% trans "Username" %}</label>
<div class="col-sm-10">
<p class="form-control-static" id="id_username_p"></p>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">{% trans "Password" %}</label>
<div class="col-sm-10">
<input class="form-control" id="id_password" type="password" name="password" placeholder="{% trans 'Please input password' %}"/>
</div>
</div>
</form>
{% endblock %}
{% block modal_confirm_id %}btn_asset_user_auth_modal_confirm{% endblock %}

View File

@@ -0,0 +1,18 @@
{% extends '_modal.html' %}
{% load i18n %}
{% block modal_id %}gateway_test{% endblock %}
{% block modal_title%}{% trans "Test gateway test connection" %}{% endblock %}
{% block modal_body %}
{% load bootstrap3 %}
<form method="post" class="form-horizontal" action="" id="test_gateway_form" style="padding-top: 10px">
<div class="form-group">
<input id="gateway_id" name="gateway_id" hidden>
<label for="port" class="col-sm-2 control-label">{% trans 'SSH Port' %}</label>
<div class="col-sm-9" id="select2-container">
<input id="ssh_test_port" name="port" class="form-control">
<span class="help-block">{% trans 'If use nat, set the ssh real port' %}</span>
</div>
</div>
</form>
{% endblock %}
{% block modal_confirm_id %}btn_gateway_test{% endblock %}

View File

@@ -36,12 +36,13 @@
{% 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 id="auth_title_id">{% trans 'Auth' %}</h3>
{% block auth %}
<h3>{% trans 'Auth' %}</h3>
<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>
@@ -55,12 +56,16 @@
{% 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" %}
@@ -79,43 +84,90 @@
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
var protocol_id = '#' + '{{ form.protocol.id_for_label }}';
var password_id = '#' + '{{ form.password.id_for_label }}';
var private_key_id = '#' + '{{ form.private_key_file.id_for_label }}';
var sudo_id = '#' + '{{ form.sudo.id_for_label }}';
var shell_id = '#' + '{{ form.shell.id_for_label }}';
<script>
var protocol_id = '#' + '{{ form.protocol.id_for_label }}';
var login_mode_id = '#' + '{{ form.login_mode.id_for_label }}';
var need_change_field = [auto_generate_key, private_key_id, sudo_id, shell_id] ;
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 }}';
function authFieldsDisplay() {
if ($(auto_generate_key).prop('checked')) {
$('.auth-fields').addClass('hidden');
} else {
$('.auth-fields').removeClass('hidden');
}
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() {
var protocol = $(protocol_id + " option:selected").text();
if (protocol === 'rdp' || protocol === 'vnc') {
$('.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 === 'telnet (beta)') {
$('.auth-fields').removeClass('hidden');
$('#command-filter-block').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
}
authFieldsDisplay();
$('#command-filter-block').removeClass('hidden');
$.each(need_change_field, function (index, value) {
$(value).closest('.form-group').removeClass('hidden')
});
}
}
function protocolChange() {
if ($(protocol_id).attr('value') === 'rdp') {
$.each(need_change_field, function (index, value) {
$(value).addClass('hidden')
});
$(password_id).removeClass('hidden')
} else {
$.each(need_change_field, function (index, value) {
$(value).removeClass('hidden')
});
}
}
$(document).ready(function () {
$('.select2').select2();
authFieldsDisplay();
protocolChange();
$(auto_generate_key).change(function () {
authFieldsDisplay();
});
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')
})
</script>
}
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>
{% 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

@@ -45,13 +45,11 @@
<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>
<th>{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
@@ -86,45 +84,68 @@
</div>
</div>
</div>
{% include 'assets/_asset_user_auth_modal.html' %}
{% endblock %}
{% block custom_foot_js %}
<script>
function initTable() {
var reachable = {{ admin_user.REACHABLE }};
var unreachable = {{ admin_user.UNREACHABLE }};
var options = {
ele: $('#asset_list_table'),
buttons: [],
order: [],
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
{targets: 0, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
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) {
{targets: 3, createdCell: function (td, cellData) {
if (cellData === unreachable) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
} else if (cellData === reachable) {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}}],
ajax_url: '{% url "api-assets:asset-list" %}?admin_user_id={{ admin_user.id }}',
} else {
$(td).html('')
}
}},
{targets: 4, createdCell: function (td, cellData, rowData) {
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 update_auth_btn = ' <a class="btn btn-xs btn-primary btn-update-asset-user-auth" data-aid="{{ DEFAULT_PK }}" data-hostname="hostname777">{% trans "Update auth" %}</a>'.replace("{{ DEFAULT_PK }}", cellData).replace("hostname777", rowData.hostname);
$(td).html(test_btn + update_auth_btn);
}}
],
ajax_url: '{% url "api-assets:admin-user-assets" pk=admin_user.id %}',
columns: [
{data: function(){return ""}}, {data: "hostname" }, {data: "ip" },
{data: "port" }, {data: "is_connective" }],
{data: "hostname" }, {data: "ip" },
{data: "port" }, {data: "connectivity" }, {data: "id"}],
op_html: $('#actions').html()
};
jumpserver.initServerSideDataTable(options);
}
function initAssetUserAuthModalForm(hostname, username){
$('#id_hostname_p').html(hostname);
$('#id_username_p').html(username);
$('#id_password').parent().removeClass('has-error');
$('#id_password').val('');
}
var assetId ;
$(document).ready(function () {
initTable();
})
.on('click', '.btn-test-connective', function () {
var the_url = "{% url 'api-assets:admin-user-connective' pk=admin_user.id %}";
.on('click', '.btn-test-asset', function () {
var asset_id = $(this).data('uid');
var the_url = "{% url 'api-assets:asset-alive-test' pk=DEFAULT_PK %}".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')
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({
url: the_url,
@@ -133,5 +154,52 @@ $(document).ready(function () {
flash_message: false
});
})
.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
});
})
.on('click', '.btn-update-asset-user-auth', function() {
assetId = $(this).data('aid');
var hostname = $(this).data('hostname');
var username = '{{ admin_user.username }}';
initAssetUserAuthModalForm(hostname, username);
$("#asset_user_auth_modal").modal();
})
.on('click', '#btn_asset_user_auth_modal_confirm', function(){
var password = $('#id_password').val();
if (password){
var data = {
'name': "{{ admin_user.username }}",
'asset': assetId,
'username': "{{ admin_user.username }}",
'password': password
};
formSubmit({
data: data,
url: "{% url 'api-assets:asset-user-list' %}",
method: 'POST',
success: function () {
toastr.success("{% trans 'Update successfully!' %}");
},
error: function () {
toastr.error("{% trans 'Update failed!' %}");
}
});
$("#asset_user_auth_modal").modal('hide');
}
else{
$('#id_password').parent().addClass('has-error');
}
})
</script>
{% endblock %}

View File

@@ -90,7 +90,7 @@
<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.value }}</option>
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>
{% endfor %}
</select>
</td>

View File

@@ -5,8 +5,11 @@
{% block help_message %}
<div class="alert alert-info help-message">
管理用户是服务器的root或拥有 NOPASSWD: ALL sudo权限的用户Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。
Windows或其它硬件可以随意设置一个
{# 管理用户是资产(被控服务器)上的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 %}
@@ -41,9 +44,10 @@ $(document).ready(function(){
var options = {
ele: $('#admin_user_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
{targets: 1, render: function (cellData, tp, rowData, meta) {
cellData = htmlEscape(cellData);
var detail_btn = '<a href="{% url "assets:admin-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
return detail_btn.replace('{{ DEFAULT_PK }}', rowData.id);
}},
{targets: 4, createdCell: function (td, cellData) {
var innerHtml = "";
@@ -79,7 +83,6 @@ $(document).ready(function(){
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);
@@ -87,10 +90,10 @@ $(document).ready(function(){
$(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: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" }]
columns: [{data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount" },
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment"}, {data: "id"}]
};
jumpserver.initDataTable(options);
jumpserver.initServerSideDataTable(options)
})
.on('click', '.btn_admin_user_delete', function () {
@@ -107,6 +110,3 @@ $(document).ready(function(){
});
</script>
{% endblock %}

View File

@@ -0,0 +1,218 @@
{% extends 'base.html' %}
{% load common_tags %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
{% 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:asset-detail' pk=asset.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Asset detail' %}</a>
</li>
<li class="active">
<a href="{% url 'assets:asset-user-list' pk=asset.id %}" class="text-center"><i class="fa fa-bar-chart-o"></i> {% trans 'Asset user 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 users of' %} <b>{{ asset.hostname }} </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_user_list">
<thead>
<tr>
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
<th class="text-center">{% trans 'Username' %}</th>
<th class="text-center">{% trans 'Password version' %}</th>
<th class="text-center">{% trans 'Reachable' %}</th>
<th class="text-center">{% trans 'Date updated' %}</th>
<th class="text-center">{% trans 'Action' %}</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 modify' %}
</div>
<div class="panel-body">
<table class="table">
<tbody>
{% if asset.protocol == 'ssh' %}
<tr class="no-borders-tr">
<td>{% trans 'Test connective' %}:</td>
<td>
<span class="pull-right">
<button type="button" class="btn btn-primary btn-xs" id="btn-bulk-test-connective" style="width: 54px">{% trans 'Test' %}</button>
</span>
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% include 'assets/_asset_user_auth_modal.html' %}
{% endblock %}
{% block custom_foot_js %}
<script>
function initAssetUserAuthModalForm(hostname){
$('#id_hostname_p').html(hostname);
$('#id_username_p').html(username);
$('#id_password').parent().removeClass('has-error');
$('#id_password').val('');
}
function initAssetUserTable() {
var reachable = {{ asset.admin_user.REACHABLE }};
var unreachable = {{ asset.admin_user.UNREACHABLE }};
var options = {
ele: $('#asset_user_list'),
buttons: [],
order: [],
columnDefs: [
{targets: 3, createdCell: function (td, cellData) {
if (cellData === unreachable) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else if (cellData === reachable) {
$(td).html('<i class="fa fa-check text-navy"></i>')
} else {
$(td).html('')
}
}},
{targets: 4, createdCell: function (td, cellData) {
$(td).html(cellData.slice(0, -6));
}},
{targets: 5, createdCell: function (td, cellData) {
var update_auth_btn = ' <a class="btn btn-xs btn-primary btn-update-asset-user-auth" data-username="DEFAULT_USERNAME">{% trans "Update auth" %}</a>'.replace("DEFAULT_USERNAME", cellData);
{% if asset.protocol == 'ssh' %}
var test_btn = ' <a class="btn btn-xs btn-info btn-test-connective" data-username="DEFAULT_USERNAME">{% trans "Test" %}</a>'.replace("DEFAULT_USERNAME", cellData);
$(td).html(test_btn + update_auth_btn);
{% else %}
$(td).html(update_auth_btn);
{% endif %}
{#var check_btn = ' <a class="btn btn-xs btn-info btn-check-asset-user-auth" data-username="DEFAULT_USERNAME">{% trans "Check auth" %}</a>'.replace("DEFAULT_USERNAME", cellData);#}
}}
],
ajax_url: '{% url "api-assets:asset-user-list" %}' + '?asset_id={{ asset.id }}',
columns: [
{data: function (){return ''}}, {data: "username" },
{data: "version"}, {data: "connectivity"}, {data: "date_updated"},
{data: "username", orderable: false}
],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
}
var username;
$(document).ready(function () {
initAssetUserTable();
})
{#.on('click', '.btn-check-asset-user-auth', function(){#}
{# var username = $(this).data('username');#}
{# var the_url = "{% url 'api-assets:asset-user-auth-info' %}" + '?asset_id={{ asset.id }}' + '&username=' + username;#}
{# $.ajax({#}
{# url: the_url,#}
{# method: 'GET',#}
{# success: function (data) {#}
{# alert("Password: " + data.password);#}
{# }#}
{# });#}
{# })#}
.on('click', '.btn-update-asset-user-auth', function() {
username = $(this).data('username');
var hostname = "{{ asset.hostname }}";
initAssetUserAuthModalForm(hostname, username);
$("#asset_user_auth_modal").modal();
})
.on('click', '#btn_asset_user_auth_modal_confirm', function(){
var password = $('#id_password').val();
if (password){
var data = {
'name': username,
'asset': "{{ asset.id }}",
'username': username,
'password': password
};
formSubmit({
data: data,
url: "{% url 'api-assets:asset-user-list' %}",
method: 'POST',
success: function () {
toastr.success("{% trans 'Update successfully!' %}");
},
error: function () {
toastr.error("{% trans 'Update failed!' %}");
}
});
$("#asset_user_auth_modal").modal('hide');
}
else{
$('#id_password').parent().addClass('has-error');
}
})
.on('click', '.btn-test-connective', function () {
var username = $(this).data('username');
var the_url = "{% url 'api-assets:asset-user-connective' %}" + "?asset_id={{ asset.id }}" + "&username=" + username;
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-bulk-test-connective', function () {
var the_url = "{% url 'api-assets:asset-user-connective' %}" + "?asset_id={{ 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')
};
APIUpdateAttr({
url: the_url,
method: 'GET',
success: success,
flash_message: false
});
})
</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

@@ -16,6 +16,7 @@
<h3>{% trans 'Basic' %}</h3>
{% bootstrap_field form.hostname layout="horizontal" %}
{% bootstrap_field form.ip layout="horizontal" %}
{% bootstrap_field form.protocol layout="horizontal" %}
{% bootstrap_field form.port layout="horizontal" %}
{% bootstrap_field form.platform layout="horizontal" %}
{% bootstrap_field form.public_ip layout="horizontal" %}
@@ -34,7 +35,7 @@
<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 'Select labels' %}" style="width: 100%" multiple="" tabindex="4" id="{{ form.labels.id_for_label }}">
<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 %}
@@ -85,6 +86,23 @@ $(document).ready(function () {
allowClear: true,
templateSelection: format
});
$('#id_nodes.select2').select2({
closeOnSelect: false
});
$("#id_protocol").change(function (){
var protocol = $("#id_protocol option:selected").text();
var port = 22;
if(protocol === 'rdp'){
port = 3389;
}
else if(protocol === 'telnet (beta)'){
port = 23;
}
else if(protocol === 'vnc'){
port = 5901;
}
$("#id_port").val(port);
});
})
</script>
{% endblock %}

View File

@@ -19,6 +19,9 @@
<li class="active">
<a href="{% url 'assets:asset-detail' pk=asset.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Asset detail' %} </a>
</li>
<li>
<a href="{% url 'assets:asset-user-list' pk=asset.id %}" class="text-center"><i class="fa fa-bar-chart-o"></i> {% trans 'Asset user list' %} </a>
</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>{% trans 'Update' %}</a>
@@ -32,7 +35,7 @@
</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>{{ asset.hostname }}</b></span>
@@ -69,10 +72,18 @@
<td>{% trans 'Port' %}:</td>
<td><b>{{ asset.port }}</b></td>
</tr>
<tr>
<td>{% trans 'Protocol' %}:</td>
<td><b>{{ asset.protocol }}</b></td>
</tr>
<tr>
<td>{% trans 'Admin user' %}:</td>
<td><b>{{ asset.admin_user }}</b></td>
</tr>
<tr>
<td>{% trans 'Domain' %}:</td>
<td><b>{{ asset.domain|default:"" }}</b></td>
</tr>
<tr>
<td>{% trans 'Vendor' %}:</td>
<td><b>{{ asset.vendor|default:"" }}</b></td>
@@ -130,8 +141,8 @@
</div>
</div>
</div>
{% if user.is_superuser %}
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
{% if user.is_superuser or user.is_org_admin %}
<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 modify' %}
@@ -155,7 +166,7 @@
</span>
</td>
</tr>
{% if asset.is_unixlike %}
{% if asset.protocol == 'ssh' %}
<tr>
<td>{% trans 'Refresh hardware' %}:</td>
<td>
@@ -190,7 +201,7 @@
<td colspan="2" class="no-borders">
<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.name }}</option>
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>
{% endfor %}
</select>
</td>
@@ -204,7 +215,7 @@
{% for node in asset.nodes.all %}
<tr>
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node.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-node" type="button"><i class="fa fa-minus"></i></button>
</td>
@@ -305,9 +316,9 @@ $(document).ready(function () {
success_message: success
});
if (status === "False") {
$(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").html('True');
$(".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-update-nodes', function () {
if (Object.keys(jumpserver.nodes_selected).length === 0) {

View File

@@ -4,12 +4,14 @@
{% 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/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">
@@ -17,20 +19,25 @@
position:absolute;
visibility:hidden;
text-align: left;
top: 100%;
{#top: 100%;#}
top: 0;
left: 0;
z-index: 1000;
float: left;
padding: 5px 0;
{#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;#}
}
list-style: none outside none;
}
.dropdown a:hover {
background-color: #f1f1f1
}
@@ -41,13 +48,12 @@
{% block content %}
<div class="wrapper wrapper-content">
<div class="row">
<div class="col-lg-3" id="split-left">
<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">
<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>
@@ -59,57 +65,56 @@
<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 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>
<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 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 '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>
@@ -117,15 +122,22 @@
<div id="rMenu">
<ul class="dropdown-menu">
<li id="menu_asset_create" class="btn-create-asset" tabindex="-1"><a>{% trans 'Create asset' %}</a></li>
<li id="menu_asset_add" class="btn-add-asset" data-toggle="modal" data-target="#asset_list_modal" tabindex="0"><a>{% trans 'Add asset' %}</a></li>
<li id="menu_refresh_hardware_info" class="btn-refresh-hardware" tabindex="-1"><a>{% trans 'Refresh node hardware info' %}</a></li>
<li id="menu_test_connective" class="btn-test-connective" tabindex="-1"><a>{% trans 'Test node connective' %}</a></li>
<li class="divider"></li>
<li id="m_create" tabindex="-1" onclick="addTreeNode();"><a>{% trans 'Add node' %}</a></li>
<li id="m_del" tabindex="-1" onclick="editTreeNode();"><a>{% trans 'Rename node' %}</a></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="m_del" tabindex="-1" onclick="removeTreeNode();"><a>{% trans 'Delete node' %}</a></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="menu_refresh_assets_amount" class="btn-refresh-assets-amount" tabindex="-1"><a><i class="fa fa-refresh"></i> {% trans 'Refresh all node assets amount' %}</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>
{# <li id="fresh_tree" class="btn-refresh-tree" tabindex="-1"><a><i class="fa fa-refresh"></i> {% trans 'Refresh' %}</a></li>#}
</ul>
</div>
@@ -136,11 +148,15 @@
{% block custom_foot_js %}
<script>
var zTree, rMenu, asset_table, show = 0;
var update_node_action = "";
var current_node_id = null;
var current_node = null;
function initTable() {
var options = {
ele: $('#asset_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
{% 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));
@@ -149,22 +165,16 @@ function initTable() {
$(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: 5, createdCell: function (td, cellData) {
if (cellData === 'Unknown'){
$(td).html('<i class="fa fa-circle text-warning"></i>')
} else if (!cellData) {
if (cellData === 1){
$(td).html('<i class="fa fa-circle text-navy"></i>')
} else if (cellData === 0) {
$(td).html('<i class="fa fa-circle text-danger"></i>')
} else {
$(td).html('<i class="fa fa-circle text-navy"></i>')
$(td).html('<i class="fa fa-circle text-warning"></i>')
}
}},
{targets: 6, createdCell: function (td, cellData, rowData) {
{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)
@@ -173,8 +183,8 @@ function initTable() {
ajax_url: '{% url "api-assets:asset-list" %}',
columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" },
{data: "cpu_cores"}, {data: "is_active", orderable: false },
{data: "is_connective", orderable: false}, {data: "id", orderable: false }
{data: "cpu_cores", orderable: false},
{data: "connectivity", orderable: false}, {data: "id", orderable: false }
],
op_html: $('#actions').html()
};
@@ -188,16 +198,21 @@ function addTreeNode() {
if (!parentNode){
return
}
var url = "{% url 'api-assets:node-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", parentNode.id );
var url = "{% url 'api-assets:node-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", parentNode.meta.node.id);
$.post(url, {}, function (data, status){
if (status === "success") {
var newNode = {
id: data["key"],
name: data["value"],
id: data["id"],
pId: parentNode.id
pId: parentNode.id,
meta: {
"node": data
}
};
newNode.checked = zTree.getSelectedNodes()[0].checked;
zTree.addNodes(parentNode, 0, newNode);
var node = zTree.getNodeByParam('id', newNode.id, parentNode);
zTree.editName(node);
} else {
alert("{% trans 'Create node failed' %}")
}
@@ -210,11 +225,12 @@ function removeTreeNode() {
if (!current_node){
return
}
if (current_node.children && current_node.children.length > 0) {
alert("{% trans 'Have child node, cancel' %}")
} else {
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.id );
toastr.error("{% trans 'Have child node, cancel' %}");
} else if (current_node.meta.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_id);
$.ajax({
url: url,
method: "DELETE",
@@ -227,12 +243,12 @@ function removeTreeNode() {
function editTreeNode() {
hideRMenu();
var current_node = zTree.getSelectedNodes()[0];
if (!current_node){
return
var current_node = zTree.getSelectedNodes()[0];
if (!current_node){
return
}
if (current_node.value) {
current_node.name = current_node.value;
if (current_node) {
current_node.name = current_node.meta.node.value;
}
zTree.editName(current_node);
}
@@ -249,14 +265,9 @@ function OnRightClick(event, treeId, treeNode) {
function showRMenu(type, x, y) {
$("#rMenu ul").show();
{#if (type === "root") {#}
{# return#}
{# } else {#}
{# $("#m_del").show();#}
{# $("#m_check").show();#}
{# $("#m_unCheck").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);
@@ -279,7 +290,7 @@ function onBodyMouseDown(event){
function onRename(event, treeId, treeNode, isCancel){
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", treeNode.id);
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
var data = {"value": treeNode.name};
if (isCancel){
return
@@ -287,19 +298,33 @@ function onRename(event, treeId, treeNode, isCancel){
APIUpdateAttr({
url: url,
body: JSON.stringify(data),
method: "PATCH"
method: "PATCH",
success_message: "{% trans 'Rename success' %}",
fail_message: "{% trans 'Rename failed, do not change the root node name' %}",
success: function () {
treeNode.name = treeNode.name + ' (' + treeNode.meta.node.assets_amount + ')'
zTree.updateNode(treeNode);
console.log("Success: " + treeNode.name)
}
})
}
function onSelected(event, treeNode) {
current_node = treeNode;
current_node_id = treeNode.meta.node.id;
zTree.expandNode(current_node, true);
var url = asset_table.ajax.url();
url = setUrlParam(url, "node_id", treeNode.id);
setCookie('node_selected', treeNode.id);
url = setUrlParam(url, "node_id", current_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() {
// TODO: 是否应该添加
// 暂时忽略之前选中的内容
return
var query_node_id = $.getUrlParam("node");
var cookie_node_id = getCookie('node_selected');
var node;
@@ -311,7 +336,7 @@ function selectQueryNode() {
node_id = cookie_node_id;
}
node = zTree.getNodesByParam("id", node_id, null);
node = zTree.getNodesByParam("node_id", node_id, null);
if (node){
zTree.selectNode(node[0]);
}
@@ -324,15 +349,11 @@ function beforeDrag() {
function beforeDrop(treeId, treeNodes, targetNode, moveType) {
var treeNodesNames = [];
$.each(treeNodes, function (index, value) {
treeNodesNames.push(value.value);
treeNodesNames.push(value.name);
});
var msg = "你想移动节点: `" + treeNodesNames.join(",") + "` 到 `" + targetNode.value + "` 下吗?";
if (confirm(msg)){
return true
} else {
return false
}
var msg = "你想移动节点: `" + treeNodesNames.join(",") + "` 到 `" + targetNode.name + "` 下吗?";
return confirm(msg);
}
function onDrag(event, treeId, treeNodes) {
@@ -341,10 +362,10 @@ function onDrag(event, treeId, treeNodes) {
function onDrop(event, treeId, treeNodes, targetNode, moveType) {
var treeNodesIds = [];
$.each(treeNodes, function (index, value) {
treeNodesIds.push(value.id);
treeNodesIds.push(value.meta.node.id);
});
var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.id);
var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.meta.node.id);
var body = {nodes: treeNodesIds};
APIUpdateAttr({
url: the_url,
@@ -354,6 +375,14 @@ function onDrop(event, treeId, treeNodes, targetNode, moveType) {
}
function initTree() {
if (zTree) {
return
}
var url = '{% url 'api-assets:node-children-tree' %}?assets=0&all=';
var showCurrentAsset = getCookie('show_current_asset');
if (!showCurrentAsset) {
url += '1'
}
var setting = {
view: {
dblClickExpand: false,
@@ -364,6 +393,12 @@ function initTree() {
enable: true
}
},
async: {
enable: true,
url: url,
autoParam: ["id=key", "name=n", "level=lv"],
type: 'get'
},
edit: {
enable: true,
showRemoveBtn: false,
@@ -386,21 +421,8 @@ function initTree() {
};
var zNodes = [];
$.get("{% url 'api-assets:node-list' %}", function(data, status){
$.each(data, function (index, value) {
value["pId"] = value["parent"];
{#if (value["key"] === "0") {#}
value["open"] = 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");
rMenu = $("#rMenu");
selectQueryNode();
});
zTree = $.fn.zTree.init($("#assetTree"), setting, zNodes);
rMenu = $("#rMenu");
}
function toggle() {
@@ -421,6 +443,13 @@ function toggle() {
$(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();
@@ -430,11 +459,7 @@ $(document).ready(function(){
.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)
@@ -442,7 +467,7 @@ $(document).ready(function(){
$.ajax({
url: "{% url "assets:asset-export" %}",
method: 'POST',
data: JSON.stringify({assets_id: assets, node_id: current_node.id}),
data: JSON.stringify({assets_id: assets, node_id: current_node_id}),
dataType: "json",
success: function (data, textStatus) {
window.open(data.redirect)
@@ -455,11 +480,8 @@ $(document).ready(function(){
.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 += "?node_id=" + current_node.id;
if (current_node_id){
action = setUrlParam(action, 'node_id', current_node_id);
$form.attr("action", action)
}
$form.find('.help-block').remove();
@@ -481,25 +503,14 @@ $(document).ready(function(){
})
.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.id;
if (current_node_id) {
url += "?node_id=" + current_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.id);
var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id);
function success(data) {
rMenu.css({"visibility" : "hidden"});
var task_id = data.task;
@@ -516,15 +527,10 @@ $(document).ready(function(){
})
.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 {
if (!current_node_id) {
return null;
}
var the_url = url.replace("{{ DEFAULT_PK }}", current_node.id);
var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id);
function success(data) {
rMenu.css({"visibility" : "hidden"});
var task_id = data.task;
@@ -538,6 +544,33 @@ $(document).ready(function(){
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-test-connective', function () {
hideRMenu();
})
.on('click', '#menu_refresh_assets_amount', function () {
hideRMenu();
var url = "{% url 'api-assets:refresh-assets-amount' %}";
APIUpdateAttr({
'url': url,
'method': 'GET'
});
window.location.reload();
})
.on('click', '.btn_asset_delete', function () {
var $this = $(this);
var $data_table = $("#asset_list_table").DataTable();
@@ -599,6 +632,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
@@ -624,17 +658,28 @@ $(document).ready(function(){
});
}
function doUpdate() {
var id_list_string = id_list.join(',');
var url = "{% url 'assets:asset-bulk-update' %}?assets_id=" + id_list_string;
location.href = url
var data = {
'assets_id':id_list
};
function error(data) {
toastr.error(JSON.parse(data).error)
}
function success(data) {
location.href = data.url;
}
APIUpdateAttr({
'url': "{% url 'api-assets:asset-bulk-update-select' %}",
'method': 'POST',
'body': JSON.stringify(data),
'flash_message': false,
'success': success,
'error': error,
})
}
function doRemove() {
var current_node;
var nodes = zTree.getSelectedNodes();
if (nodes && nodes.length === 1) {
current_node = nodes[0]
} else {
if (!current_node_id) {
return
}
@@ -647,7 +692,7 @@ $(document).ready(function(){
};
APIUpdateAttr({
'url': '/api/assets/v1/nodes/' + current_node.id + '/assets/remove/',
'url': '/api/assets/v1/nodes/' + current_node_id + '/assets/remove/',
'method': 'PUT',
'body': JSON.stringify(data),
'success': success
@@ -673,7 +718,40 @@ $(document).ready(function(){
break;
}
$(".ipt_check_all").prop("checked", false)
});
})
.on('click', '#btn_asset_modal_confirm', function () {
var assets_selected = asset_table2.selected;
if (!current_node_id) {
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_id);
} else {
url = "{% url 'api-assets:node-add-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_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 %}

View File

@@ -12,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>
@@ -21,6 +21,7 @@
<h3>{% trans 'Basic' %}</h3>
{% bootstrap_field form.hostname layout="horizontal" %}
{% bootstrap_field form.ip layout="horizontal" %}
{% bootstrap_field form.protocol layout="horizontal" %}
{% bootstrap_field form.port layout="horizontal" %}
{% bootstrap_field form.platform layout="horizontal" %}
{% bootstrap_field form.public_ip layout="horizontal" %}
@@ -39,7 +40,7 @@
<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="Select labels" style="width: 100%" multiple="" tabindex="4" id="{{ form.labels.id_for_label }}">
<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 %}

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 %}

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