Compare commits

..

1267 Commits
0.3.1 ... 1.0.0

Author SHA1 Message Date
老广
35ed881139 Merge pull request #1070 from jumpserver/dev
[Update] 升级版本号
2018-03-14 20:03:02 +08:00
ibuler
a58db9826e [Update] 升级版本号 2018-03-14 19:57:35 +08:00
老广
5250a223c3 Merge pull request #1069 from jumpserver/dev
Dev to master
2018-03-14 19:56:04 +08:00
ibuler
15427fb743 [Update] 还原ldap配置 2018-03-14 19:37:36 +08:00
ibuler
7874a1539c [Update] 修改配置,默认先从数据库认证 2018-03-14 19:33:48 +08:00
老广
9f23520712 Merge pull request #1068 from jumpserver/dev
merge dev to master
2018-03-14 18:39:31 +08:00
ibuler
63f1ec839c [Update] merge with master 2018-03-14 18:38:54 +08:00
ibuler
7e087ad5c6 Merge remote-tracking branch 'github/dev' into dev 2018-03-14 18:36:22 +08:00
ibuler
93895b6b92 [Update] 修改一些文案说明和翻译 2018-03-14 18:14:37 +08:00
q4speed
4d7fbcc49b 修复文字过长bug 2018-03-14 18:01:37 +08:00
ibuler
3822d51888 Merge branch 'docs' into dev 2018-03-14 16:21:53 +08:00
ibuler
aa5187dc39 Merge remote-tracking branch 'github/dev' into dev 2018-03-14 16:21:35 +08:00
ibuler
e9be4f51e5 [Update] 删除资产详情中的windows等测试连接 2018-03-14 16:19:59 +08:00
ibuler
496403bde0 [Update] 修改docs 2018-03-14 16:08:27 +08:00
ibuler
4f522c1cd1 [Update] docs 2018-03-14 16:06:05 +08:00
ibuler
5c3846a886 [Update] 添加Faq 2018-03-14 15:54:27 +08:00
ibuler
42f297e6c4 [Update] 更新一些文案 2018-03-14 13:13:32 +08:00
fit2cloud-fengyi
5d88c7b779 add git install 2018-03-14 13:09:51 +08:00
ibuler
74047d19d0 Merge branch 'dev' of bitbucket.org:jumpserver/core into dev 2018-03-14 12:18:17 +08:00
ibuler
8b63961d4d [Update] 默认开启定时检测,运行失败默认log为warn 2018-03-14 12:17:48 +08:00
liuzheng712
9c2470b67c feat: update the sdk 2018-03-13 19:05:16 +08:00
liuzheng
240faee2b4 Update upgrade.rst 2018-03-13 14:53:23 +08:00
liuzheng
3d3d9b565a Update upgrade.rst 2018-03-13 14:51:22 +08:00
ibuler
2e8f5919a8 [Update] 默认关闭自动定时推送功能 2018-03-13 09:57:38 +08:00
ibuler
faf6a7b623 [Update] 删掉token api调用次数限制 2018-03-12 20:29:27 +08:00
ibuler
8e4ab9f3a9 [Update] 支持获取token的用户 2018-03-12 19:39:27 +08:00
ibuler
e4823a21e3 [Update] 优化部分代码 2018-03-12 18:29:06 +08:00
ibuler
634af19945 Merge branch 'dev' of bitbucket.org:jumpserver/core into dev 2018-03-12 18:24:14 +08:00
liuzheng712
b2ae0e8f44 fix: download update 2018-03-12 17:53:22 +08:00
ibuler
4a3e5e9b22 Merge branch 'dev' of bitbucket.org:jumpserver/core into dev 2018-03-12 16:44:10 +08:00
ibuler
43b4b7c55e [Feature] 资产节点增加批量测试可连接性和更新硬件信息 2018-03-12 11:41:12 +08:00
fit2cloud-fengyi
b19a0e1cdf 删除faq和久的intro 2018-03-12 10:33:55 +08:00
liuzheng
b92ccdd05e fix: update the jms-storage==0.0.11 2018-03-12 10:30:49 +08:00
fit2cloud-fengyi
34cc6abec5 Merge remote-tracking branch 'origin/docs' into docs 2018-03-12 10:28:54 +08:00
ibuler
f59f03adfd [Update] 支持使用环境变量关闭定时任务 2018-03-12 10:20:23 +08:00
老广
0f2b0b146d Update README.md 2018-03-12 09:17:34 +08:00
老广
e085fee101 Update README.md 2018-03-12 09:16:54 +08:00
ibuler
fe03011177 [Update] 不允许添加应用程序类型的用户 2018-03-11 19:46:40 +08:00
ibuler
9ffb079c8f [Update] 增加修改system user auth 的api 2018-03-09 12:53:08 +08:00
ibuler
050b6e6d88 [Update] 修改一些docs 2018-03-09 12:12:10 +08:00
fit2cloud-fengyi
422990a992 添加 venv 到 ignore 文件 2018-03-09 10:33:57 +08:00
老广
06ff7f75ce Merge pull request #1061 from toryzen/master
Update step_by_step.rst
2018-03-09 10:07:38 +08:00
toryzen
0b82b97c66 Update step_by_step.rst
删除多余的3.2章节
2018-03-09 10:04:59 +08:00
ibuler
fcbe94de37 [Update] 更新持久化 2018-03-08 12:28:45 +08:00
老广
ce35a8e2ac Merge pull request #1059 from jumpserver/dev
Dev
2018-03-08 12:11:50 +08:00
老广
bebd71895c Merge pull request #1058 from jumpserver/docs
Docs
2018-03-08 12:11:04 +08:00
ibuler
ea18727bdb Merge remote-tracking branch 'github/docs' into docs 2018-03-08 12:08:45 +08:00
fit2cloud-fengyi
b90c5d1e43 英文格式 2018-03-08 12:00:14 +08:00
liuzheng
0be31a8078 Merge pull request #1056 from jumpserver/dev
revert: add the old apps/users/migrations/0002_auto_20171225_1157.py
2018-03-08 09:18:25 +08:00
liuzheng712
9162f4a226 revert: add the old apps/users/migrations/0002_auto_20171225_1157.py 2018-03-08 09:13:40 +08:00
老广
3bf7d061c9 Merge pull request #1055 from jumpserver/dev
Dev
2018-03-07 23:31:31 +08:00
ibuler
51e15b583f Merge remote-tracking branch 'origin/dev' into dev 2018-03-07 23:29:17 +08:00
ibuler
60427b9908 [Update] README.md 2018-03-07 23:28:30 +08:00
老广
60bc42f630 Dev (#1054)
* [Update] 修改 success message, 添加资产组时可以添加资产

* [Update] system user form add label

* [Update] set default cluster

* [Update] 修改一些翻译

* [Bugfix] 修复重置密码bug

* [Bugfix] 默认default cluster

* [Bugfix] 用户添加报错

* 修改tab样式

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

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

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

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

* [Feature] 添加setting页面

* [Feature] 添加basic settings

* [Update] 修改翻译

* [Update] 修改config

* [Update] 启动加载common setting

* [Bugfix] 修复cluster创建的bug

* [Bugfix] 修复title显示Jumpserver

* [Bugfix] setting tables not found

* [Bugfix] settings add option

* [Feature] 添加后端paging

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

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

* [Bugfix] user login ip

* [Bugfix] for login ip

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

* [Remove] labels

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

* [Feature] 增加标签

* [Bugfix] 读取不到prefix

* For storage

* [Change] 修改部分翻译

* [Update] 启用ldap移动位置

* [Update] 修改翻译

* [Feature] 支持es存储命令

* Update README.md

* [Feature] 添加es支持

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

* [Update] 修改install.md

* [Update] remote default PAGE_SIZE stting

* [Feature] terminal config load

* [Feature] es support

* [Update] 修改requirement

* [Update] 修改requirements

* [Update] 修改dictfiled

* [Fix] 修改Logger

* [Bugfix] 倒序显示

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

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

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

* add logo test

* [Bugfix] 修复一些bug

* [Update] 修改copyrite

* [Update] 修改copyright

* Update ISSUE_TEMPLATE.md

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

* [Feature] 标签管理功能

* [Bugfix] git status

* [Model] 修改create_by字段

* [Update] 修改位置

* [Update] 修改签名md5算法

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

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

* [Bugfix] 修复资产搜索bug

* [Update] ansible disk bug

* [Update] ansible disk bug

* [Bugfix] 修复获取kvmcpu的bug

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

* [Bugfix] 修改翻译

* [Bugfix] 资产model 太长

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

修正"项目多语言目录"

* Update project_structure.md

* [Update] add debug log

* refactor: rename folder i18n

* [Feature] 添加链接token

* [Feature] Label 删除修改

* [Update] 修改部分翻译

* [Update] 修改小bug

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

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

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

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

* [Bugfix] 资产批量便捷

* [Update] 修改认证

* [Feature] 支持popover

* [Feature] tree

* [Feature] 添加资产树

* [Feature] 使用ztree

* [Feature] tree增删功能

* [Bugfix] 修复组详情bug

* [Bugfix] 修复组详情bug

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

* [Bugfix] 修改label api bug

* [Update] 去掉资产组添加

* [Update] 修改ztrr

* Update README.md

* [Update] 修改资产创建

* [Bugfix] 修复ldap认证bug

* [Update] 修改一处翻译

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

* [Abandon] ...

* Update README.md

* Update README.md

* Update README.md

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

* [Update] 修改授权

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

* feat: rdp support

* [Update] 拆分asset api module

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

* [Feature] 更改perms api

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

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

* [Update] 完成树形改造

* [Update] 完成资产书

* [Update] 修改资产model

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

* Update run server.py (#915)

Fix  for not callable error when  config.py not exists

* [Update]一些修改

* [Update] 修改初始

* feat: replay setting page and api

* 增加隐藏树功能

* [Update] 修改翻译

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

* feat: update app setting

* fix: app get replay storage

* [Update] 修改文案

* [Docs] 初始化doc

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

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

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

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

* [Update] terminal可以更改名称

* [Update] 统一copyright

* [Update] 修改文档

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

* [Update] 修改文案

* [Update] 支持拖拽更新

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

* [Update] 修改一些小问题

* 添加普通用户使用内容

* [Update] 修改一些文案

* Update README.md

* Update README.md

* Update README.md

* 用户列表

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

* [Delete] 删除build 页面

* 用户管理模块更新

* 用户管理模块更新

* [Update] 修改conf

* [Update] bugfix

* [Update] 更新文档地址

* 用户管理模块更新

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

* 管理用户

* 权限管理文档

* 权限管理文档命名修改

* 作业中心文档

* 细节改动

* 添加贡献者

* 截图

* [Update] 修改一些bug

* add ignore

* [Update] 修改文档

* fix bug

* add ignore

* [Igore] force

* [Update] 修改链接

* [Update] 修改版本

* 标签管理

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

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

* [Update] 修改Node value唯一

* 系统设置

* 快速启动修改

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

* feat: s3 replay file get

* feat: update

* [Update] 修改bug

* [Update] 修改Model

* [Update] 修改文档地址

* [Update] 修改gua的安装

* [Update] 修改推送任务

* [Update] 修改安装分支

* feat: update the requirements

* [Update] 更新README

* fix: remove gssapi==1.2.4

* [Update] 修改readme

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

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

* [Update] 修改readme

* [Update] index

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

* [Update] system user form add label

* [Update] set default cluster

* [Update] 修改一些翻译

* [Bugfix] 修复重置密码bug

* [Bugfix] 默认default cluster

* [Bugfix] 用户添加报错

* 修改tab样式

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

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

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

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

* [Feature] 添加setting页面

* [Feature] 添加basic settings

* [Update] 修改翻译

* [Update] 修改config

* [Update] 启动加载common setting

* [Bugfix] 修复cluster创建的bug

* [Bugfix] 修复title显示Jumpserver

* [Bugfix] setting tables not found

* [Bugfix] settings add option

* [Feature] 添加后端paging

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

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

* [Bugfix] user login ip

* [Bugfix] for login ip

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

* [Remove] labels

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

* [Feature] 增加标签

* [Bugfix] 读取不到prefix

* For storage

* [Change] 修改部分翻译

* [Update] 启用ldap移动位置

* [Update] 修改翻译

* [Feature] 支持es存储命令

* Update README.md

* [Feature] 添加es支持

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

* [Update] 修改install.md

* [Update] remote default PAGE_SIZE stting

* [Feature] terminal config load

* [Feature] es support

* [Update] 修改requirement

* [Update] 修改requirements

* [Update] 修改dictfiled

* [Fix] 修改Logger

* [Bugfix] 倒序显示

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

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

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

* add logo test

* [Bugfix] 修复一些bug

* [Update] 修改copyrite

* [Update] 修改copyright

* Update ISSUE_TEMPLATE.md

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

* [Feature] 标签管理功能

* [Bugfix] git status

* [Model] 修改create_by字段

* [Update] 修改位置

* [Update] 修改签名md5算法

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

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

* [Bugfix] 修复资产搜索bug

* [Update] ansible disk bug

* [Update] ansible disk bug

* [Bugfix] 修复获取kvmcpu的bug

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

* [Bugfix] 修改翻译

* [Bugfix] 资产model 太长

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

修正"项目多语言目录"

* Update project_structure.md

* [Update] add debug log

* refactor: rename folder i18n

* [Feature] 添加链接token

* [Feature] Label 删除修改

* [Update] 修改部分翻译

* [Update] 修改小bug

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

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

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

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

* [Bugfix] 资产批量便捷

* [Update] 修改认证

* [Feature] 支持popover

* [Feature] tree

* [Feature] 添加资产树

* [Feature] 使用ztree

* [Feature] tree增删功能

* [Bugfix] 修复组详情bug

* [Bugfix] 修复组详情bug

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

* [Bugfix] 修改label api bug

* [Update] 去掉资产组添加

* [Update] 修改ztrr

* Update README.md

* [Update] 修改资产创建

* [Bugfix] 修复ldap认证bug

* [Update] 修改一处翻译

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

* [Abandon] ...

* Update README.md

* Update README.md

* Update README.md

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

* [Update] 修改授权

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

* feat: rdp support

* [Update] 拆分asset api module

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

* [Feature] 更改perms api

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

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

* [Update] 完成树形改造

* [Update] 完成资产书

* [Update] 修改资产model

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

* Update run server.py (#915)

Fix  for not callable error when  config.py not exists

* [Update]一些修改

* [Update] 修改初始

* feat: replay setting page and api

* 增加隐藏树功能

* [Update] 修改翻译

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

* feat: update app setting

* fix: app get replay storage

* [Update] 修改文案

* [Docs] 初始化doc

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

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

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

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

* [Update] terminal可以更改名称

* [Update] 统一copyright

* [Update] 修改文档

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

* [Update] 修改文案

* [Update] 支持拖拽更新

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

* [Update] 修改一些小问题

* 添加普通用户使用内容

* [Update] 修改一些文案

* Update README.md

* Update README.md

* Update README.md

* 用户列表

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

* [Delete] 删除build 页面

* 用户管理模块更新

* 用户管理模块更新

* [Update] 修改conf

* [Update] bugfix

* [Update] 更新文档地址

* 用户管理模块更新

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

* 管理用户

* 权限管理文档

* 权限管理文档命名修改

* 作业中心文档

* 细节改动

* 添加贡献者

* 截图

* [Update] 修改一些bug

* add ignore

* [Update] 修改文档

* fix bug

* add ignore

* [Igore] force

* [Update] 修改链接

* [Update] 修改版本

* 标签管理

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

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

* [Update] 修改Node value唯一

* 系统设置

* 快速启动修改

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

* feat: s3 replay file get

* feat: update

* [Update] 修改bug

* [Update] 修改Model

* [Update] 修改文档地址

* [Update] 修改gua的安装

* [Update] 修改推送任务

* [Update] 修改安装分支

* feat: update the requirements

* [Update] 更新README

* fix: remove gssapi==1.2.4

* [Update] 修改readme

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

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

* [Update] 修改readme

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

* [Update] system user form add label

* [Update] set default cluster

* [Update] 修改一些翻译

* [Bugfix] 修复重置密码bug

* [Bugfix] 默认default cluster

* [Bugfix] 用户添加报错

* 修改tab样式

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

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

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

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

* [Feature] 添加setting页面

* [Feature] 添加basic settings

* [Update] 修改翻译

* [Update] 修改config

* [Update] 启动加载common setting

* [Bugfix] 修复cluster创建的bug

* [Bugfix] 修复title显示Jumpserver

* [Bugfix] setting tables not found

* [Bugfix] settings add option

* [Feature] 添加后端paging

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

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

* [Bugfix] user login ip

* [Bugfix] for login ip

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

* [Remove] labels

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

* [Feature] 增加标签

* [Bugfix] 读取不到prefix

* For storage

* [Change] 修改部分翻译

* [Update] 启用ldap移动位置

* [Update] 修改翻译

* [Feature] 支持es存储命令

* Update README.md

* [Feature] 添加es支持

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

* [Update] 修改install.md

* [Update] remote default PAGE_SIZE stting

* [Feature] terminal config load

* [Feature] es support

* [Update] 修改requirement

* [Update] 修改requirements

* [Update] 修改dictfiled

* [Fix] 修改Logger

* [Bugfix] 倒序显示

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

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

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

* add logo test

* [Bugfix] 修复一些bug

* [Update] 修改copyrite

* [Update] 修改copyright

* Update ISSUE_TEMPLATE.md

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

* [Feature] 标签管理功能

* [Bugfix] git status

* [Model] 修改create_by字段

* [Update] 修改位置

* [Update] 修改签名md5算法

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

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

* [Bugfix] 修复资产搜索bug

* [Update] ansible disk bug

* [Update] ansible disk bug

* [Bugfix] 修复获取kvmcpu的bug

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

* [Bugfix] 修改翻译

* [Bugfix] 资产model 太长

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

修正"项目多语言目录"

* Update project_structure.md

* [Update] add debug log

* refactor: rename folder i18n

* [Feature] 添加链接token

* [Feature] Label 删除修改

* [Update] 修改部分翻译

* [Update] 修改小bug

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

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

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

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

* [Bugfix] 资产批量便捷

* [Update] 修改认证

* [Feature] 支持popover

* [Feature] tree

* [Feature] 添加资产树

* [Feature] 使用ztree

* [Feature] tree增删功能

* [Bugfix] 修复组详情bug

* [Bugfix] 修复组详情bug

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

* [Bugfix] 修改label api bug

* [Update] 去掉资产组添加

* [Update] 修改ztrr

* Update README.md

* [Update] 修改资产创建

* [Bugfix] 修复ldap认证bug

* [Update] 修改一处翻译

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

* [Abandon] ...

* Update README.md

* Update README.md

* Update README.md

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

* [Update] 修改授权

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

* feat: rdp support

* [Update] 拆分asset api module

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

* [Feature] 更改perms api

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

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

* [Update] 完成树形改造

* [Update] 完成资产书

* [Update] 修改资产model

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

* Update run server.py (#915)

Fix  for not callable error when  config.py not exists

* [Update]一些修改

* [Update] 修改初始

* feat: replay setting page and api

* 增加隐藏树功能

* [Update] 修改翻译

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

* feat: update app setting

* fix: app get replay storage

* [Update] 修改文案

* [Docs] 初始化doc

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

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

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

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

* [Update] terminal可以更改名称

* [Update] 统一copyright

* [Update] 修改文档

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

* [Update] 修改文案

* [Update] 支持拖拽更新

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

* [Update] 修改一些小问题

* 添加普通用户使用内容

* [Update] 修改一些文案

* Update README.md

* Update README.md

* Update README.md

* 用户列表

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

* [Delete] 删除build 页面

* [Update] 修改conf

* [Update] bugfix

* [Update] 更新文档地址

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

* [Update] 修改一些bug

* [Update] 修改链接

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

* [Update] 修改Node value唯一

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

* feat: s3 replay file get

* feat: update

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

* [Update] system user form add label

* [Update] set default cluster

* [Update] 修改一些翻译

* [Bugfix] 修复重置密码bug

* [Bugfix] 默认default cluster

* [Bugfix] 用户添加报错

* 修改tab样式

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

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

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

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

* [Feature] 添加setting页面

* [Feature] 添加basic settings

* [Update] 修改翻译

* [Update] 修改config

* [Update] 启动加载common setting

* [Bugfix] 修复cluster创建的bug

* [Bugfix] 修复title显示Jumpserver

* [Bugfix] setting tables not found

* [Bugfix] settings add option

* [Feature] 添加后端paging

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

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

* [Bugfix] user login ip

* [Bugfix] for login ip

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

* [Remove] labels

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

* [Bugfix] 读取不到prefix

* [Change] 修改部分翻译

* [Update] 启用ldap移动位置

* [Update] 修改翻译

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

1. auto detect the encoding of csv
2. if id in csv is empty, let it equal 0
3. if hostname exists, give up this asset

* Add chardet to requirements.txt
2017-09-24 09:16:47 +08:00
老广
8342ba68c0 Update requirements.txt 2017-09-24 08:57:20 +08:00
crisewng
48ef5c421b 更新Mac安装ldap失败解决方法 (#613) 2017-09-24 08:41:42 +08:00
管宜尧
290872dcad bugfix: 解决用户失效时间为空时,无法使用密码进行ssh登录跳板机的问题 (#659)
* bugfix: 解决用户失效时间为空时,无法使用密码进行ssh登录跳板机的问题

bugfix: 解决用户失效时间为空时,无法使用密码进行ssh登录跳板机的问题。

```
AttributeError at /api/users/v1/auth/
'NoneType' object has no attribute 'strftime'

Request Method: POST
Request URL: http://127.0.0.1:8080/api/users/v1/auth/
Django Version: 1.11.4
Python Executable: /opt/py3/bin/python
Python Version: 3.6.1
Python Path: ['/data/deployment/jumpserver/apps', '/usr/local/lib/python36.zip', '/usr/local/lib/python3.6', '/usr/local/lib/python3.6/lib-dynload', '/opt/py3/lib/python3.6/site-packages', '/data/deployment/jumpserver', '/data/deployment/jumpserver/apps']
Server time: Wed, 30 Aug 2017 23:18:47 +0800
Installed Applications:
['users.apps.UsersConfig',
 'assets.apps.AssetsConfig',
 'perms.apps.PermsConfig',
 'ops.apps.OpsConfig',
 'audits.apps.AuditsConfig',
 'common.apps.CommonConfig',
 'applications.apps.ApplicationsConfig',
 'rest_framework',
 'rest_framework_swagger',
 'django_filters',
 'bootstrap3',
 'captcha',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.locale.LocaleMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'jumpserver.middleware.TimezoneMiddleware',
 'jumpserver.middleware.DemoMiddleware']


Traceback:  

File "/opt/py3/lib/python3.6/site-packages/django/core/handlers/exception.py" in inner
  41.             response = get_response(request)
File "/opt/py3/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response
  187.                 response = self.process_exception_by_middleware(e, request)
File "/opt/py3/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response
  185.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/usr/local/lib/python3.6/contextlib.py" in inner
  53.                 return func(*args, **kwds)
File "/opt/py3/lib/python3.6/site-packages/django/views/decorators/csrf.py" in wrapped_view
  58.         return view_func(*args, **kwargs)
File "/opt/py3/lib/python3.6/site-packages/django/views/generic/base.py" in view
  68.             return self.dispatch(request, *args, **kwargs)
File "/opt/py3/lib/python3.6/site-packages/rest_framework/views.py" in dispatch
  489.             response = self.handle_exception(exc)
File "/opt/py3/lib/python3.6/site-packages/rest_framework/views.py" in handle_exception
  449.             self.raise_uncaught_exception(exc)
File "/opt/py3/lib/python3.6/site-packages/rest_framework/views.py" in dispatch
  486.             response = handler(request, *args, **kwargs)
File "/data/deployment/jumpserver/apps/users/api.py" in post
  166.             return Response({'token': token, 'user': user.to_json()})
File "/data/deployment/jumpserver/apps/users/models/user.py" in to_json
  207.             'date_expired': self.date_expired.strftime('%Y-%m-%d %H:%M:%S')

Exception Type: AttributeError at /api/users/v1/auth/
Exception Value: 'NoneType' object has no attribute 'strftime'
Request information:
USER: AnonymousUser
GET: No GET data
POST: No POST data
FILES: No FILES data
COOKIES: No cookie data
```

* bugfix: 个人信息页面个人信息pannel错位

bugfix: 个人信息页面个人信息pannel错位
2017-09-24 08:40:59 +08:00
老广
d4fa082e17 Update Dockerfile 2017-09-14 09:49:42 +08:00
ibuler
e6537699fd [Bugfix] Update asset create template bug 2017-09-13 10:09:17 +08:00
老广
1d950c4f49 Update settings.py 2017-09-12 17:58:09 +08:00
老广
5e197fb0db Update README.md 2017-09-08 07:18:26 +08:00
ibuler
594ad0e14f Update db index more than 767 2017-08-31 16:54:45 +08:00
ibuler
d73d6e943b update user model charfield max length more than 767 bug 2017-08-31 16:04:57 +08:00
老广
eb8b02d88b Update README.md 2017-08-03 18:16:32 +08:00
老广
c8728e52d8 Update README.md 2017-08-03 18:14:36 +08:00
本杰明
8537e3e135 Update settings (#595)
修改redis有密码无法连接bug
2017-08-03 15:24:16 +08:00
ibuler
cd1d690fd3 [Bugfix] 修复一些bug 2017-07-28 21:52:43 +08:00
中国娃
67e953902b Update utils.py (#579) 2017-07-28 20:42:16 +08:00
ibuler
5ffd1f99fd [Bugfix] 修复更新bug 2017-07-24 22:42:01 +08:00
ibuler
cc1a339142 [bugfix] system user update bug 2017-07-24 21:59:55 +08:00
ibuler
1c78526f86 Fix bug 2017-07-20 07:58:16 +08:00
ibuler
b0a5289a42 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2017-07-20 07:55:45 +08:00
ibuler
477d23ea37 fix some bug 2017-07-20 07:55:24 +08:00
tonygatescxp
72c2290300 Update settings.py (#515)
修正Redis连接字符串
2017-07-17 17:18:14 +08:00
ibuler
61014c747e Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2017-07-15 13:51:15 +08:00
ibuler
b2ba1f3ca5 update requirements 2017-07-15 13:50:56 +08:00
Eli
7c154abf70 fix bug: variable i should be the host name (#538)
fix bug: variable i is not the host name, because summary['failed'] is something like [("hostname1", "error message 1"),("hostname1", "error message 1")]
2017-07-13 14:00:17 +08:00
ibuler
d012880a90 [Bugfix] 修复bug 2017-07-13 11:46:56 +08:00
ibuler
3ce07cf969 Update README 2017-07-12 20:45:21 +08:00
ibuler
24dd7b6347 update docker config 2017-07-10 15:11:28 +08:00
ibuler
9237c9dcee Add requirements 2017-07-10 11:31:33 +08:00
ibuler
143cabe5fc update settings 2017-07-10 11:02:51 +08:00
Eli
370cdc275a 添加一个资产,然后推送系统用户时出现(Asset实例无法序列化) (#477)
* error while push systemuser. error while create assets with adminuser(ssh key)

* fix errors in case of config.py dose not exist.

* change sign_t return from bytes to str. (#480)

* fix id_dsa check error (#458)

* fix id_dsa check error

* fix 邮件修改密码 token错误

* fix 3c8aec9 add )

* Dockerfile 优化 (#453)
2017-07-10 11:01:20 +08:00
老广
235cbe12ee Update README.md 2017-07-10 10:57:41 +08:00
Fengxu Lin
26a169d938 fix bug in pagination_range function (#511)
start and end may be float when current_num >= 3 and display % 2 == 1
2017-07-10 10:39:18 +08:00
ibuler
4e67749eef [Docs] 添加api dockers 2017-07-10 10:26:17 +08:00
ibuler
e120fd56a6 Update readme 2017-06-19 23:48:36 +08:00
ibuler
51b9e3732f Update readme 2017-06-19 23:41:45 +08:00
ibuler
f3a50610af [Update] update docker-compose 2017-06-19 23:15:09 +08:00
ibuler
4141ae517f update docker-compose 2017-06-19 18:13:44 +08:00
ibuler
29095a869c [Demo] update demo mode code 2017-06-19 17:59:13 +08:00
ibuler
cda22a6f0d add demo mode middleware 2017-06-15 22:35:03 +08:00
Caijun
8f4d8b1c02 Compile messages (#495) 2017-06-13 17:51:12 +08:00
Caijun
b502b06e82 Support i18n for asset platform field (#494) 2017-06-13 16:38:33 +08:00
谢义学
7d541ee916 Dockerfile 优化 (#453) 2017-06-06 11:25:14 +08:00
njqaaa
9b14244363 fix id_dsa check error (#458)
* fix id_dsa check error

* fix 邮件修改密码 token错误

* fix 3c8aec9 add )
2017-06-06 11:23:39 +08:00
bdlzhx
b680a42425 change sign_t return from bytes to str. (#480) 2017-06-06 11:22:29 +08:00
Caijun
cf07a6ebb7 Fix judging request.user valid on UserToken API (#476) 2017-06-02 17:15:59 +08:00
njqaaa
dd6c82b168 fix left's users logout (#446)
* fix left's users logout

* fix issues 445
2017-05-25 14:12:16 +08:00
crisewng
18169251fa [Bugfix] 修复管理用户上传私钥兼容问题 (#451) 2017-05-25 11:14:21 +08:00
ibuler
8ee1e468b8 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2017-05-24 20:13:01 +08:00
ibuler
1719eee264 [Bugfix] 修复py3 bytes bug 2017-05-24 20:12:50 +08:00
老广
ae94948246 Update README.md 2017-05-24 09:49:02 +08:00
老广
f19bcc48f6 Update python_style_guide.md 2017-05-22 23:28:35 +08:00
ibuler
41633be1aa [Bugfix] 修复无法删除资产组 2017-05-22 20:30:31 +08:00
ibuler
d2620a655c [Bugfix] 修复模板bug, 修复getattr bug 2017-05-22 20:18:13 +08:00
ibuler
24c1a931a5 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2017-05-22 19:56:39 +08:00
ibuler
31025b8c52 [bugfix] 修复重置密码bug 2017-05-22 19:51:54 +08:00
ibuler
25f3d5bf02 [Bugfix] 修复翻译一处bug 2017-05-22 19:48:15 +08:00
老广
e21bea6ec0 Update README.md 2017-05-22 12:31:37 +08:00
老广
0399074fcb Update README.md 2017-05-22 11:27:30 +08:00
ibuler
edc631ad60 [Docker] 添加Docker compose 2017-05-20 00:19:10 +08:00
GuangHongwei
ff374b7141 [Docker] Build a docker 2017-05-17 09:42:32 +08:00
GuangHongwei
458989328e [Bugfix] 修改bug,使用py3编程 2017-05-15 23:39:54 +08:00
GuangHongwei
3c8d6fbe1b Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2017-05-11 19:42:58 +08:00
GuangHongwei
a052ac8117 [Bugfix] 注释掉配置,影响登陆 2017-05-11 19:41:13 +08:00
老广
94a022e76b Update install.md 2017-05-11 09:54:00 +08:00
GuangHongwei
831ac60e25 [Update] 更新文档说明,使用python3 2017-05-11 09:43:59 +08:00
GuangHongwei
56e648e924 Update requirements 2017-05-11 09:19:37 +08:00
GuangHongwei
61c5eede94 Merge branch 'dev' of github.com:jumpserver/jumpserver 2017-05-10 10:00:48 +08:00
ibuler
2ec0ab871a Fix form bug 2017-04-12 18:06:32 +08:00
ibuler
695e4da85e [Fixture] 完成批量更新 2017-04-12 11:50:15 +08:00
ibuler
2aa9aafdf6 [Change] Bulk update asset 2017-04-11 17:29:53 +08:00
ibuler
071d1922d0 stash it 2017-04-10 22:55:15 +08:00
ibuler
95e64d7809 [Change] 修改导出和更新逻辑 2017-04-09 17:59:14 +08:00
ibuler
5418d0b44c [Change] Using csv replace xlsx 2017-04-09 00:45:28 +08:00
ibuler
bcc065eb8f [Compat] compat py3 change bootstrap form to bootstrap3 2017-04-07 23:52:46 +08:00
ibuler
a320b9e05e [Bugfix] 兼容py3 2017-04-07 19:11:27 +08:00
ibuler
b913bce398 [Bugfix] 修改翻译bug 2017-04-07 17:09:25 +08:00
ibuler
c11374ae39 [Bugfix] 修改翻译 2017-04-07 16:59:09 +08:00
假想控
eebd54a4e8 Update install.md 2017-04-06 18:46:00 +08:00
ibuler
92ebe85a3f [Fixture] 详情页添加system user 推送 2017-04-05 19:09:51 +08:00
ibuler
25b8108af0 Merge branch 'audits' 2017-04-04 21:52:29 +08:00
ibuler
8a2b008fbf Merge branch 'a' 2017-04-04 21:52:23 +08:00
ibuler
753440faff Merge branch 'dev' of github.com:jumpserver/jumpserver into audits 2017-04-04 21:52:04 +08:00
ibuler
be09db059d Update some bug 2017-04-04 21:47:58 +08:00
假想控
cbb14d140e Update install.md 2017-04-04 20:46:03 +08:00
假想控
f06cf887e1 Update install.md 2017-04-04 20:16:40 +08:00
假想控
194a698372 Update install.md 2017-04-04 20:15:30 +08:00
ibuler
5a5d5bdd51 [Fixture] 添加更新硬件信息api 2017-04-04 19:16:34 +08:00
ibuler
992af0f1cb [Fixture] 拆分asset view 2017-04-04 11:37:52 +08:00
ibuler
ac3553babb [Fixture] 修改asset detail 2017-04-04 11:14:59 +08:00
ibuler
97fb2a4fe6 [Fixture] 修改index页面 2017-04-03 00:27:18 +08:00
ibuler
709552f14c [Fixture] 添加首页 2017-04-02 18:09:40 +08:00
老广
365750565d Update requirements.txt 2017-04-01 13:53:34 +08:00
ibuler
66a2262ee9 [Fixture] 增加 asset groups assets api 2017-04-01 00:41:16 +08:00
ibuler
067426d5e0 [Fixture] 完成用户向导页 2017-03-31 23:46:00 +08:00
ibuler
87eb1914fb Merge branch 'dev' of github.com:jumpserver/jumpserver 2017-03-31 18:50:34 +08:00
ibuler
135899608e Merge branch 'master' of code.jumpserver.org:Jumpserver/jumpserver 2017-03-31 18:48:11 +08:00
ibuler
7720e9f3fd [Change] 修改图片 2017-03-31 18:47:50 +08:00
ibuler
61a481f427 [Fixture] 添加用户连接终端 2017-03-31 11:25:25 +08:00
ibuler
3fa5ce5404 [Fixture] 添加用户信息更改 2017-03-30 16:28:00 +08:00
ibuler
0983f294b7 [Fixture] 添加用户profile 2017-03-30 00:51:36 +08:00
ibuler
4e7b665ea8 [Change] 添加翻译,并添加用户登陆页面 2017-03-29 15:26:32 +08:00
右书僮
b34c5dde40 Merge branch 'master' of code.jumpserver.org:Jumpserver/jumpserver
# Conflicts:
#	apps/assets/templates/assets/asset_list.html
2017-03-27 08:03:23 +08:00
右书僮
9de2ff2052 修复idc_list删除时重新加载table 2017-03-27 07:58:35 +08:00
ibuler
a4ad9b49b9 Merge branch 'audits' 2017-03-26 19:41:17 +08:00
ibuler
90055f7680 [Bugfix] 添加翻译 2017-03-26 19:41:10 +08:00
假想控
2e31cf51f3 Update install.md 2017-03-26 14:56:17 +08:00
假想控
4714d5d42f Update install.md 2017-03-26 13:55:01 +08:00
假想控
5984b0b579 Update install.md 2017-03-26 13:54:11 +08:00
假想控
d700c448c6 Update install.md 2017-03-26 13:50:10 +08:00
假想控
ef8599b836 Update install.md 2017-03-26 13:46:26 +08:00
假想控
dd5b1097f2 Update install.md 2017-03-26 13:40:27 +08:00
假想控
f7c35fde2d Update install.md 2017-03-26 13:38:48 +08:00
假想控
4fbb4dfb55 Update install.md 2017-03-26 13:35:26 +08:00
假想控
84af0405d9 Update install.md 2017-03-26 13:32:38 +08:00
假想控
c0e5e5f896 Update install.md 2017-03-26 13:31:38 +08:00
老广
12bc63fbc9 Update install.md 2017-03-26 13:06:34 +08:00
老广
7bbb1d24ac Update install.md 2017-03-26 13:05:33 +08:00
ibuler
49f6ed524d Merge with master 2017-03-26 11:54:46 +08:00
ibuler
7ddfa2d25e [Doc] 添加doc 2017-03-26 11:50:09 +08:00
ibuler
d3cdfc1b9d Update ignore 2017-03-24 23:14:10 +08:00
ibuler
1f9dbb6bdc [Change] 修复验证码模糊度 2017-03-24 23:13:17 +08:00
ibuler
10eb13920b [Delete] 删除资产tag 2017-03-24 14:48:18 +08:00
ibuler
a57dac0706 [Fixture] 拆分proxy log 为在线和离线 2017-03-24 14:23:51 +08:00
右书僮
e0179ea332 删除资产HTML页面中SystemUser相关内容(视图中的相关API暂时未动) 2017-03-24 11:13:40 +08:00
右书僮
c931d3179b 修复资产组更新中 用户己选择的资产项不全问题 2017-03-24 11:02:55 +08:00
右书僮
c5666f1357 fix some files 2017-03-24 10:37:05 +08:00
ibuler
b60e5a7ee3 [Change] 修改一些view 2017-03-24 00:27:33 +08:00
ibuler
c940a4c0fb [Fixture] 增加 task list 删除按钮 2017-03-23 00:15:25 +08:00
ibuler
3f72ce4b1f [Update] 更新生成fake数据脚本 2017-03-22 23:36:43 +08:00
ibuler
5613dbb28b [Update] 更新生成fake数据脚本 2017-03-22 23:36:30 +08:00
ibuler
2ae57a7970 [Delete] 删除 system user一些属性 2017-03-22 22:15:15 +08:00
ibuler
610c9c5149 Pull it 2017-03-22 22:05:24 +08:00
ibuler
caec9709ef [Fixture] 添加task list 搜索,重试 2017-03-22 21:57:05 +08:00
ibuler
a4504dc0c7 [Change] 拆分tasks 2017-03-16 00:43:43 +08:00
ibuler
0fbd9843bd [Change] 修改runner, inventory位置 2017-03-16 00:19:47 +08:00
ibuler
240c7db416 [Fixture] 添加 rerun function 2017-03-14 14:15:13 +08:00
ibuler
915873135e [Fixture] 添加runner run record 2017-03-14 00:58:25 +08:00
ibuler
a822f667af [Fixture] 完成用户推送task 2017-03-09 14:55:33 +08:00
ibuler
c234b5b2d5 Update AdHocRunner 2017-03-06 23:34:54 +08:00
xiaokong1937@gmail.com
694899799b trivial changes 2017-03-06 21:05:00 +08:00
ibuler
eb5a9dd20f Remote ansible_api file 2017-03-05 23:30:14 +08:00
ibuler
6015f2423b [Fixture] 添加PlaybookRunner 2017-03-05 20:53:24 +08:00
ibuler
01e50d592e 完成AdHoc JMSHost JMSInventory 2017-03-05 11:38:02 +08:00
老广
0524e8dddf Update README.md 2017-03-04 16:41:36 +08:00
ibuler
ab62bfca7e [Bugfix] 修改一些bug 2017-03-04 16:38:26 +08:00
ibuler
7833fc8c80 Merge remote-tracking branch 'new_git/dev' into dev 2017-03-03 15:19:55 +08:00
ibuler
9c2c38c74a [Bugfix] 修复导入自定义过滤器问题 2017-03-03 15:15:10 +08:00
ibuler
78700cd2f5 merge with version 0.4.0 2017-03-03 10:28:45 +08:00
ibuler
695dc96ce8 Merge branch 'audits' 2017-03-03 10:24:34 +08:00
ibuler
f1d4cae20b Delete 0.3.1 version code 2017-03-03 10:13:58 +08:00
ibuler
9518672ad2 Add 2017-03-02 18:29:29 +08:00
ibuler
b055144908 Merge with audits 2017-03-01 15:35:11 +08:00
ibuler
7825c107ab [Bugfix] model FieldError 2017-03-01 15:30:19 +08:00
右书僮
dd5dd9d7e1 fixed html files 2017-02-25 23:20:20 +08:00
Webb
63fd3cfff2 fixed asset_group delete html 2017-02-25 20:18:13 +08:00
Webb
a6825ac91e fixed asset_list html and asset_update 2017-02-25 17:25:27 +08:00
Webb
8ee9d02ffc fixed html 2017-02-24 21:29:11 +08:00
ibuler
008be6a8e3 [Fixtrue] 完成terminal detail 和批量结束会话 2017-02-12 16:51:32 +08:00
ibuler
1f544b98ab [Stash] 删除结束会话之前 2017-02-11 19:49:15 +08:00
ibuler
42e4c64d06 [Bugfix] 修复一些bug 2017-02-11 12:13:02 +08:00
xiaokong1937@gmail.com
548d7ef99a fix #13;fix user context conflict problem in user-detail page 2017-02-09 21:19:49 +08:00
ibuler
9c7bd7d285 [Fixture] 添加record log 2017-02-07 21:51:44 +08:00
ibuler
a79c3dd156 [Fixture] 添加command log backends, 未来支持es 2017-02-06 23:13:27 +08:00
xiaokong1937@gmail.com
fe01f92545 user profile: update ssh pk 2017-02-03 13:37:05 +08:00
xiaokong1937@gmail.com
8a5d0b2d92 remove unused templatetags: users_tags and common_tags 2017-01-26 20:24:50 +08:00
xiaokong1937@gmail.com
f3647ea46d user-profile page 2017-01-25 21:19:16 +08:00
xiaokong1937@gmail.com
31bded8953 CAPTCHA_TEST_MODE settings and small fix 2017-01-23 12:29:36 +08:00
xiaokong1937@gmail.com
3f7cb4a458 ignore windows bat files 2017-01-23 10:52:59 +08:00
ibuler
0869931e67 [Bugfix] 修改了一些issue上的bug 2017-01-20 20:13:22 +08:00
ibuler
948214cacb Merge branch 'audits' 2017-01-20 14:00:11 +08:00
ibuler
df94d11f53 [Fix] 修改一些api bug 2017-01-20 13:59:53 +08:00
ibuler
ffed28c9c7 [Change] 修改perm的代码, 强制79个字符内 2017-01-17 17:11:01 +08:00
ibuler
25cb47d2f5 [Change] 拆分user view为多个view 2017-01-17 16:34:47 +08:00
wangfeng7399
0979480103 update dockerfile 2017-01-13 15:43:51 +08:00
王胜辉
dc4c37afb6 Update README.md 2017-01-13 07:30:23 +00:00
ibuler
87bbb6afde [Bugfix] 修改添加系统用户view 2017-01-10 23:41:14 +08:00
ibuler
8916221bba [Bugfix] 紧急修复url解析失败错误 2017-01-10 18:03:00 +08:00
ibuler
8658675f67 [Bugfix] 重命名applications导致api请求content_type不对 2017-01-09 23:12:32 +08:00
ibuler
be3f94d86c [BugFix] 修改requirements添加新的库 2017-01-09 01:58:23 +08:00
ibuler
17657deb0e [Bugfix] 和Master分支合并后,修复冲突,DRF 新版本要求 fields和exclude 2017-01-08 13:35:59 +08:00
ibuler
49861b6a84 Merge with master 2017-01-07 22:34:12 +08:00
ibuler
3da33a57e2 [Bugfix] 修改tab -> space, 删除system user的 as default 2017-01-07 20:17:22 +08:00
ibuler
9c6c6d6b4c [Feature] 添加Dockerfile 2017-01-07 20:07:33 +08:00
右书僮
dbdb8a58fe 资产相关API及Web 2017-01-06 20:34:24 +08:00
ibuler
b670259ab6 Modify terminal reqject templatte 2017-01-03 00:11:44 +08:00
ibuler
0907f5021e Finish form ajax submmit 2016-12-30 22:21:50 +08:00
ibuler
301e02bcd8 Update some api 2016-12-30 00:19:47 +08:00
ibuler
70da177ed7 Update api 2016-12-29 19:17:00 +08:00
ibuler
92d854b971 Update api 2016-12-29 00:29:59 +08:00
ibuler
d80fec6e60 Update some api 2016-12-28 00:28:52 +08:00
广宏伟
4c06257070 Update README.md 2016-12-27 23:44:05 +08:00
ibuler
d56f030dc4 Finish terminal accept 2016-12-27 00:59:52 +08:00
ibuler
775cd523eb Update app terminal name to applications 2016-12-25 23:10:53 +08:00
ibuler
a8fa4d2f0c update terminal regist 2016-12-25 17:44:39 +08:00
ibuler
2707012325 Finish access key auth 2016-12-25 13:15:28 +08:00
ibuler
c5ab49c515 Finish access key auth 2016-12-25 13:15:19 +08:00
ibuler
fe17bec752 Add access key auth 2016-12-22 01:07:05 +08:00
ibuler
5b4ce709af Add private token and change user group 2016-12-22 00:36:31 +08:00
ibuler
875aaa0029 update user model for create application user 2016-12-21 01:17:36 +08:00
ibuler
589b6d7cfe update 2016-12-21 01:03:52 +08:00
ibuler
649509dec1 Change models dir 2016-12-21 00:43:52 +08:00
ibuler
4d96978b4f Merge branch 'master' into audits 2016-12-20 23:08:09 +08:00
ibuler
81ec121918 Merge branch 'master' of code.jumpserver.org:jumpserver/jumpserver 2016-12-20 23:07:57 +08:00
ibuler
a448fd02e2 merged 2016-12-20 23:07:33 +08:00
ibuler
96d32f2e3e prepare merge ops 2016-12-20 23:06:27 +08:00
ibuler
4d71c2d1ff 修改token获取,拆分认证文件和权限文件 2016-12-20 01:19:50 +08:00
广宏伟
6ef33eb0c3 Merge branch 'ops_dev' into 'master'
Ops dev

See merge request !1
2016-12-19 23:58:49 +08:00
yumaojun03
046c7e21e9 Merge remote-tracking branch 'origin/master' into ops_dev
# Conflicts:
#	requirements.txt
2016-12-19 23:55:54 +08:00
yumaojun03
47d87e38a6 补充task需要的一些接口 2016-12-19 23:46:03 +08:00
ibuler
2ab8e92bf4 Add black line 2016-12-19 23:10:16 +08:00
yumaojun03
150e1030c3 ansible Task接口更上层抽象的基本实现 2016-12-19 14:07:21 +08:00
yumaojun03
86c5f0d3d3 完成cron和sudo的list和detail基础,ansible Task接口更上层抽象中 2016-12-19 00:24:51 +08:00
ibuler
d964221689 Update api 2016-12-16 19:32:05 +08:00
wangjun5
87eed2e59b add asset tool 2016-12-15 19:55:15 +08:00
广宏伟
a737564a6c Add new file 2016-12-14 19:58:20 +08:00
广宏伟
6374a9772c Add new file 2016-12-14 19:57:47 +08:00
yumaojun03
b348f7f1ce 添加了部分cron的list页面 2016-12-14 09:54:16 +08:00
yumaojun03
a0c9e3d117 添加了部分sudo的页面 2016-12-14 00:06:16 +08:00
yumaojun03
3eb8a702c8 为模型生成fake数据 2016-12-12 11:08:10 +08:00
yumaojun03
806d38bbb2 模拟数据测试 2016-12-11 20:50:26 +08:00
yumaojun03
84613e51d8 为了防止循环导入,采用__all__导出模块变量 2016-12-11 12:05:11 +08:00
ibuler
0e4804b59f Merge branch 'audits' 2016-12-06 10:43:05 +08:00
ibuler
69767e978d Add fake 2016-12-06 10:40:17 +08:00
yumaojun03
baba65ad43 修改url模块, 匹配整体架构风格. 2016-12-05 23:02:08 +08:00
yumaojun03
d0460d8691 修改url模块, 匹配整体架构风格. 2016-12-05 22:54:38 +08:00
ibuler
a7476222a9 Update import 2016-11-27 23:36:35 +08:00
Administrator
61ac9129b0 url少加了下划线 2016-11-27 22:45:53 +08:00
ibuler
3aea994101 asset import 2016-11-27 14:37:50 +08:00
Administrator
8e0afb2cc4 调整模型 2016-11-27 11:30:23 +08:00
ibuler
c1c9c7b68a update asset 2016-11-25 22:45:47 +08:00
ibuler
6e843533cb Base finish user 2016-11-25 11:00:51 +08:00
ibuler
d8a229c09b to Commpany 2016-11-25 08:39:24 +08:00
Administrator
2494fa5846 修改url 2016-11-24 22:34:55 +08:00
Administrator
984391e2b2 同步Master分支上的代码, 解决部分冲突问题 2016-11-24 22:10:04 +08:00
ibuler
72ad4b44ce update bug 2016-11-24 19:36:49 +08:00
Administrator
1bc88e5b11 Merge branch 'ansible_api' into ops_dev
# Conflicts:
#	apps/jumpserver/urls.py
#	apps/locale/zh/LC_MESSAGES/django.po
#	apps/templates/_nav.html
#	requirements.txt
#	run_server.py
2016-11-24 18:22:36 +08:00
Administrator
c289d6a4c5 Merge branch 'ansible_api' into ops_dev
# Conflicts:
#	apps/jumpserver/urls.py
#	apps/locale/zh/LC_MESSAGES/django.po
#	apps/templates/_nav.html
#	requirements.txt
#	run_server.py
2016-11-24 17:57:50 +08:00
Administrator
7c4aefd959 兼容py3测试 2016-11-24 17:10:43 +08:00
ibuler
c1a74aebc5 [BugFix] update some user import bug 2016-11-24 17:08:20 +08:00
ibuler
eae580e51f Update user import and export 2016-11-24 15:45:08 +08:00
ibuler
e28f7a3bec Add export and import 2016-11-24 00:48:57 +08:00
ibuler
be99eb82e8 udpate some bug 2016-11-23 19:37:47 +08:00
ibuler
5af97c969b export csv 2016-11-23 19:09:11 +08:00
ibuler
7dfde3a3c5 Update 2016-11-23 16:17:23 +08:00
Administrator
cd22c39078 [future] 将Task移到一个包内管理 2016-11-23 11:45:50 +08:00
Administrator
32a5aec34e [future] 调整app架构 2016-11-22 23:02:12 +08:00
Administrator
fea76178ee 添加作业中心i18n 2016-11-22 21:40:05 +08:00
Administrator
3abe2196dd [future] 添加作业中心 i18n相关 2016-11-22 21:38:38 +08:00
Administrator
18e0fee1a7 [future] 添加作业中心基础框架 2016-11-22 21:08:45 +08:00
Administrator
954814da65 [future] 添加Cron相关的基础API框架 2016-11-22 10:41:18 +08:00
ibuler
180af7e0bd Update csv 2016-11-22 00:59:49 +08:00
Administrator
79971d677d [future] 使用mixin去掉重复多余代码 2016-11-20 18:12:18 +08:00
Administrator
1e835d2fa9 [future] 增加503和501的自定义异常 2016-11-20 16:23:45 +08:00
Administrator
76f72dfb58 [future] 添加路由, 增加api认证, 测试所有添加的api 2016-11-20 16:22:41 +08:00
Administrator
5ae2711c6e sudo privilege删除走api 2016-11-20 14:48:18 +08:00
Administrator
39ae4a3a10 [future] url 调整 2016-11-20 12:22:56 +08:00
Administrator
961ecb3ee8 [future] 添加sudo相关的api方法 2016-11-20 12:18:44 +08:00
ibuler
205c11dfba Finish list paganation 2016-11-18 12:00:23 +08:00
广宏伟
c743715459 Update README.md 2016-11-18 10:22:35 +08:00
广宏伟
b4bd923304 Update README.md 2016-11-18 10:14:46 +08:00
ibuler
e89d3b3807 Update asset 2016-11-17 19:28:45 +08:00
ibuler
6e69c018b4 Merge branch 'audits' 2016-11-16 18:12:40 +08:00
ibuler
aff37092bf Rename urls 2016-11-16 18:12:14 +08:00
ibuler
5745c8cc4a Finish url namespace change 2016-11-16 17:45:46 +08:00
ibuler
a5e487441f prepare change api name 2016-11-16 17:38:03 +08:00
Administrator
c588436d55 初始化模板和api模块 2016-11-16 15:00:46 +08:00
Administrator
82412831d5 initial sudo views 2016-11-16 14:20:44 +08:00
ibuler
aa08f0aa48 Merge branch 'audits' of code.jumpserver.org:Jumpserver/jumpserver into audits 2016-11-16 12:34:01 +08:00
ibuler
32d12b7f78 Update user group asset permission 2016-11-16 12:33:54 +08:00
ibuler
3b30eb3278 Weiteng 2016-11-15 23:09:36 +08:00
ibuler
99c36f2a2c Fix bug 2016-11-15 19:44:08 +08:00
ibuler
db8b0022fc Update usergroup detail 2016-11-15 19:33:04 +08:00
ibuler
bd882c8bef User group detail 2016-11-15 00:48:48 +08:00
ibuler
8d358a7a68 UPdate some 2016-11-14 19:28:21 +08:00
广宏伟
8731e0816d Update README.md 2016-11-14 19:25:17 +08:00
广宏伟
074cc2f26e Update README.md 2016-11-14 19:24:27 +08:00
广宏伟
6eaaddd8ed Update README.md 2016-11-14 19:21:46 +08:00
ibuler
419876b575 Update heatbeat 2016-11-13 22:34:38 +08:00
ibuler
2635217421 Update celery 2016-11-11 09:48:47 +08:00
ibuler
c6fc3dfe91 Update terminal interval 2016-11-11 02:13:13 +08:00
ibuler
10aa8c40a7 Add login log 2016-11-10 23:54:21 +08:00
ibuler
f70abec5ef Finish user detail 2016-11-10 22:06:23 +08:00
ibuler
69f2bf664b Finish user permission revoke 2016-11-10 16:59:50 +08:00
ibuler
dde9ffb2ae Update perm api 2016-11-10 00:18:57 +08:00
ibuler
47090eb0f7 update some user api 2016-11-09 23:49:10 +08:00
ibuler
c5d625e261 Merge branch 'audits' of code.jumpserver.org:Jumpserver/jumpserver into audits 2016-11-09 19:29:26 +08:00
ibuler
8d7759d22f change user api 2016-11-09 19:29:15 +08:00
ibuler
0d4d64c274 Update api 2016-11-09 00:36:23 +08:00
ibuler
ea3f8af161 Fix pubkey auth bug 2016-11-07 16:59:52 +08:00
ibuler
a75e1db970 Add form validate 2016-11-07 00:39:26 +08:00
ibuler
072da114db Finish system user list 2016-11-06 22:45:26 +08:00
ibuler
968b1b4cb6 Stash 2016-11-06 21:29:04 +08:00
ibuler
afb923737c Merge with audits 2016-11-06 11:52:25 +08:00
ibuler
79c8f2275c Update perm api 2016-11-05 23:34:45 +08:00
Administrator
d9278c2c24 [future] 添加sudo相关的表,以及实现生成sudo内容的方法 2016-11-05 13:10:44 +08:00
ibuler
5b0f897118 Update proxy log and command log view 2016-11-05 12:45:59 +08:00
ibuler
41337d28c3 add proxy log search 2016-11-05 01:15:25 +08:00
ibuler
1d29c52a43 Update datatable 2016-11-04 19:25:10 +08:00
ibuler
53e97dac40 stash it 2016-11-04 18:33:16 +08:00
Administrator
8716d9c725 [future] 添加用于记录sudo相关的表 2016-11-04 18:12:10 +08:00
ibuler
f278b735cc Modify settings 2016-11-04 00:41:21 +08:00
ibuler
2a65e81316 detail page add update 2016-11-03 23:16:16 +08:00
ibuler
f437d3f883 update 2016-11-03 19:42:47 +08:00
Administrator
1fd0f8fdde [future] 添加sudo的配置模板 2016-11-03 18:26:10 +08:00
ibuler
61eadf6891 Modify asset detail 2016-11-03 00:09:38 +08:00
ibuler
3448f3eb0a Modify init data 2016-11-02 23:00:47 +08:00
ibuler
05a5e9cc69 Finish some bug 2016-11-02 19:44:11 +08:00
ibuler
34a0a37b63 Add token 2016-11-01 19:31:35 +08:00
ibuler
1159d9494c Update signer 2016-11-01 17:21:16 +08:00
Administrator
92f396761c [future] 完成获取硬件和链接测试的第一版v1
1. 完成数据结果的封装, 添加数据结构样列
2. 完成硬件和链接测试接口v1版
2016-11-01 10:37:03 +08:00
ibuler
f1dfba6a93 Update some asset issues 2016-10-31 19:31:56 +08:00
ibuler
b36d70987d Finish token access api 2016-10-31 18:58:30 +08:00
ibuler
315af35296 Finish token access api 2016-10-31 18:58:23 +08:00
Administrator
97b8bcd5ca [future]
1. settings 添加ops 的logger, 关于Ansible的log单独记录
2. models 增加Tasker模型, 添加AnsibleHostReuslt 对于数据处理的方法
3. ansible_api, callback 类,增加保存Tasker的逻辑,i其他兼容
4. taskers, 实现获取硬件信息和ping的 tasker接口。
2016-10-31 17:41:26 +08:00
江世峰
6b6fdcd5fd asset:add assets_bulk_update 2016-10-28 21:24:01 +08:00
江世峰
3d4f79ca59 asset:add assets_bulk_update 2016-10-28 21:19:37 +08:00
Administrator
ccf3851d81 [future] 初步添加测试链接的接口 2016-10-28 17:43:56 +08:00
Administrator
97d7e6cb9b [future] 修改celery 使用eventlet 作为concurrent pool, 添加获取资产硬件信息的 初步接口 2016-10-28 17:28:32 +08:00
Administrator
fd945513ac [fix] 修改模型的显示, 因为字段默认会加上Class的Name 2016-10-28 15:47:37 +08:00
ibuler
92251f2a45 Merge with master 2016-10-28 15:09:38 +08:00
Administrator
51c530c123 [future] ansible运行结果存入数据库 2016-10-28 14:58:09 +08:00
Administrator
96bc1cd8f1 [future]ansible运行结果存入数据库中... 2016-10-28 13:41:11 +08:00
ibuler
374dfbdac2 Force merge 2016-10-28 11:36:37 +08:00
ibuler
534321d1aa Merge with master 2016-10-28 11:34:07 +08:00
ibuler
69c6f81c31 Add fake to 2016-10-28 00:21:11 +08:00
ibuler
9e3d740b43 Add fake to 2016-10-28 00:17:10 +08:00
ibuler
5cdc80ec11 Add init data 2016-10-27 23:59:54 +08:00
ibuler
217586b536 Add fake 2016-10-27 23:55:19 +08:00
ibuler
2abec15691 Rm ws4redis 2016-10-27 23:50:44 +08:00
ibuler
688bfa556c Merge with audits 2016-10-27 23:04:02 +08:00
ibuler
589d0d0fac Finish command log list 2016-10-27 22:58:19 +08:00
ibuler
3c00c578c3 Update some vie 2016-10-27 19:35:02 +08:00
Administrator
eb5f0fcf68 [future] 在主机层面支持多种sudo, 以及回调结果的分类 2016-10-27 15:20:16 +08:00
ibuler
c0de35a683 Add layer and layer open command log 2016-10-27 00:52:44 +08:00
ibuler
d19b47a427 add command log modal 2016-10-27 00:03:05 +08:00
ibuler
573b3a8743 Update log_command modal 2016-10-26 19:31:54 +08:00
ibuler
5d3f9b4a03 Add command list 2016-10-26 19:10:14 +08:00
ibuler
c2aab50c7b Add proxy log list 2016-10-25 23:23:01 +08:00
Administrator
13f34a8bdd ops: [future]完成ansible2.0 API 的基本封装. 2016-10-25 18:15:02 +08:00
江世峰
f88c149076 asset:add assets_bulk 2016-10-25 02:16:20 +08:00
ibuler
6a510dad6c Update some bug 2016-10-24 19:32:53 +08:00
江世峰
cdb1602e06 asset:add assets_bulk 2016-10-22 20:21:36 +08:00
江世峰
80baecc8be Merge branch 'master' of code.jumpserver.org:Jumpserver/jumpserver
Merge
2016-10-21 21:15:04 +08:00
江世峰
73f0199dc0 asset:add assets_bulk 2016-10-21 21:14:49 +08:00
ibuler
6b161d5971 Update import 2016-10-20 19:01:57 +08:00
ibuler
67ecc108bd Command parser 2016-10-20 00:26:53 +08:00
ibuler
df380c343e Update api 2016-10-19 19:30:55 +08:00
ibuler
6164896793 Update some thing 2016-10-19 18:33:14 +08:00
ibuler
45dcb26123 Merge branch 'audits' 2016-10-19 15:14:07 +08:00
ibuler
bb9a067293 Finish command log 2016-10-19 01:05:28 +08:00
ibuler
961abad14b Add heatbeat 2016-10-18 23:49:04 +08:00
ibuler
17ade287ab Add proxy log api 2016-10-18 19:28:36 +08:00
ibuler
7513474366 Finish terminal app 2016-10-17 17:28:07 +08:00
ibuler
303659cb0e Change app name apps => terrminal 2016-10-17 15:24:41 +08:00
江世峰
4e1f9c97a5 asset:update assets_list by tag 2016-10-16 22:35:25 +08:00
ibuler
4531157c72 may be some wrong 2016-10-16 22:12:13 +08:00
ibuler
26a8bce2c3 Change auto_now to auto_now_add 2016-10-15 23:34:02 +08:00
ibuler
3383b2b535 Add terminal mode 2016-10-15 18:28:49 +08:00
ibuler
9960a6cd21 Plan create a new app: terminal 2016-10-15 17:14:56 +08:00
ibuler
0446f449e9 Add auth and permission backends 2016-10-15 16:04:54 +08:00
ibuler
a62a2178d0 Add user backend 2016-10-15 00:49:59 +08:00
ibuler
f038423ce2 Add isdangerous 2016-10-14 20:18:34 +08:00
江世峰
a3683f184e Merge branch 'master' of code.jumpserver.org:Jumpserver/jumpserver
Merge
2016-10-14 19:29:15 +08:00
江世峰
abe09e2a85 asset:update assets_list by tag 2016-10-14 19:28:58 +08:00
ibuler
3efad338eb Merge branch 'connect' 2016-10-13 14:49:04 +08:00
ibuler
29b3ef70ef Merge branch 'master' of code.jumpserver.org:Jumpserver/jumpserver 2016-10-12 19:43:43 +08:00
江世峰
73f5891f87 asset:update assets_list by tag 2016-10-12 18:57:51 +08:00
ibuler
081af2f953 Add proxy log api for create or update 2016-10-10 00:39:24 +08:00
ibuler
0c8922e30f Modify some bug 2016-10-09 19:27:49 +08:00
广宏伟
82610163cb Update README.md 2016-10-09 18:14:19 +08:00
ibuler
ba82c395f2 Move terminal to a new project 2016-10-09 15:14:41 +08:00
ibuler
0954f6d7e8 Add audits api 2016-10-09 00:12:18 +08:00
江世峰
b99b88a30f asset:update tag 2016-10-08 18:33:00 +08:00
ibuler
59727656c3 Update table desgin doc and audit log 2016-10-07 23:54:29 +08:00
江世峰
0c611b6429 Merge branch 'master' of code.simcu.com:jumpserver/jumpserver
merage
2016-10-07 23:04:48 +08:00
江世峰
2829445f4f assets:add tag 2016-10-07 23:04:37 +08:00
ibuler
0cda4e0905 Record command history 2016-10-07 08:53:28 +08:00
ibuler
aa02c211fe Modify inital data 2016-10-02 22:16:15 +08:00
ibuler
6b6105491c Add migrations 2016-10-02 22:13:08 +08:00
ibuler
45df58114c Merge branch 'connect' 2016-10-02 21:45:44 +08:00
ibuler
a04d772501 Merge with master 2016-10-02 21:45:26 +08:00
ibuler
820d608b18 Rm teminal app 2016-10-02 21:43:22 +08:00
ibuler
b54c973d82 Update requirement 2016-10-02 21:38:27 +08:00
ibuler
0234ff0252 Pripare web terminal server 2016-10-02 17:09:27 +08:00
xiaokong1937@gmail.com
6856fad0c0 integrate the user-group list page with its api; 2016-10-02 11:09:27 +08:00
xiaokong1937@gmail.com
d40aa49d8c user bulk import through Excel and close #20 2016-10-01 20:26:43 +08:00
xiaokong1937@gmail.com
05e961f29f user-group detail: fix #15 2016-09-30 20:50:40 +08:00
江世峰
474f7e0f68 Merge branch 'master' of code.simcu.com:jumpserver/jumpserver
merge
2016-09-30 18:26:02 +08:00
江世峰
fd52a85dbb fix #25 2016-09-30 18:25:50 +08:00
ibuler
d9866e1f38 Debug to find command 2016-09-30 00:07:55 +08:00
ibuler
0d25a8f5b7 Use thread replace process 2016-09-29 23:52:07 +08:00
ibuler
b4c6499139 Try to fix ssh server close client bug 2016-09-29 21:36:15 +08:00
xiaokong1937@gmail.com
d8143a67cd user-group-detail: user-adding frontend integration 2016-09-29 21:14:55 +08:00
ibuler
e3c620e138 Debug some bug for auth failed and exit 2016-09-29 18:35:52 +08:00
ibuler
acf51238d0 Modify ssh server bug for using public key 2016-09-29 18:01:26 +08:00
xiaoyu
e7fddf80ae user-group-detail: add users support 2016-09-29 16:41:55 +08:00
xiaokong1937@gmail.com
f9b49605e4 user-group detail page: users op 2016-09-28 21:11:43 +08:00
江世峰
0dec647116 Merge branch 'master' of code.simcu.com:jumpserver/jumpserver
rollback user_list.html
2016-09-28 13:45:03 +08:00
江世峰
3201853cdf rollback user_list.html 2016-09-28 13:44:51 +08:00
江世峰
e83ecb1254 assets_group_forbug 2016-09-28 12:05:34 +08:00
xiaokong1937@gmail.com
2522f0d80f trivial changes: refactor the jumpserver DataTable api 2016-09-27 21:28:02 +08:00
xiaokong1937@gmail.com
70a6ddc897 fix #27 2016-09-27 20:16:46 +08:00
ibuler
b17a12662c modify change windows size 2016-09-27 00:10:14 +08:00
ibuler
be1a374b14 Modify log 2016-09-26 22:16:21 +08:00
xiaokong1937@gmail.com
49f007601f add user-list-delete support for user-group detail page 2016-09-26 21:22:48 +08:00
xiaokong1937@gmail.com
74cdd2d0f3 user list groups bulk update 2016-09-26 19:39:43 +08:00
xiaoyu
15dcc760b4 user list bulk update modal 2016-09-26 16:33:10 +08:00
xiaoyu
d2197d99c2 fix #26 2016-09-26 11:30:43 +08:00
ibuler
badd319bb4 Modify some bug and add some logging 2016-09-26 00:05:23 +08:00
ibuler
e627b14e55 finish ssh server use more class 2016-09-25 23:38:42 +08:00
ibuler
4b7419559c Update ssh_server to some class 2016-09-25 23:11:09 +08:00
ibuler
2c64b78487 Update terminal 2016-09-25 21:21:25 +08:00
ibuler
5e33c2dc6b Update ssh config 2016-09-25 20:41:56 +08:00
ibuler
ebb30424fa Use process except thread 2016-09-25 19:53:55 +08:00
ibuler
216163f436 stash 2016-09-25 11:30:02 +08:00
ibuler
d3e9c8c9c0 Replace ssh server dir 2016-09-25 00:21:32 +08:00
ibuler
cfef374454 Update ssh server 2016-09-25 00:11:31 +08:00
ibuler
0d4ca9717e Merge branch 'master' into connect 2016-09-24 22:18:07 +08:00
ibuler
7ab47916f2 add __init__.py 2016-09-24 22:12:49 +08:00
ibuler
283c53fddf Update gitignore 2016-09-24 22:11:53 +08:00
ibuler
c5c11864e0 update 2016-09-24 22:10:17 +08:00
ibuler
da56310db9 update .gitignore 2016-09-24 22:09:20 +08:00
ibuler
7935c00338 update .gitignore 2016-09-24 22:02:44 +08:00
ibuler
1620f6a311 Update ignore 2016-09-24 22:01:19 +08:00
ibuler
4925f3227a update .gitignore 2016-09-24 21:57:24 +08:00
ibuler
c158edb574 Modify ignore 2016-09-24 21:54:58 +08:00
ibuler
ab18fe466b stash 2016-09-24 21:47:10 +08:00
ibuler
88bb620af2 Modify ignore 2016-09-24 11:50:25 +08:00
ibuler
a65d5269fe Modify git ignore 2016-09-24 11:46:11 +08:00
ibuler
1f3d763490 Add requirement 2016-09-24 11:44:59 +08:00
江世峰
184ac728db Merge branch 'master' of code.simcu.com:jumpserver/jumpserver
update:add_assets-group
2016-09-23 20:23:02 +08:00
江世峰
5bf414ffb5 update add_assets-group 2016-09-23 20:22:45 +08:00
xiaoyu
22163173fe user bulk delete view 2016-09-23 16:30:59 +08:00
ibuler
de0f8c24f7 finish example 2016-09-22 23:56:27 +08:00
ibuler
f946a4bfb3 finish example 2016-09-22 23:26:44 +08:00
江世峰
18341717a1 Merge branch 'master' of code.simcu.com:jumpserver/jumpserver
"assets-group"
2016-09-22 18:31:15 +08:00
江世峰
d2e989403f update assets-group 2016-09-22 18:31:04 +08:00
xiaoyu
af713672fb user list page bulk action 2016-09-22 16:06:46 +08:00
ibuler
db2d00f828 implement a some server 2016-09-22 00:37:13 +08:00
xiaoyu
515406c05d temp: deactive test 2016-09-21 16:43:41 +08:00
xiaoyu
61d1d9ec90 temp 2016-09-21 15:48:21 +08:00
ibuler
e020aaa368 ssh server 2016-09-21 00:43:19 +08:00
ibuler
771cf39944 ssh server 2016-09-21 00:38:17 +08:00
ibuler
c7f3aaa654 Change appname webterminal to terminal 2016-09-20 23:34:37 +08:00
ibuler
220892824c Finish some bug 2016-09-20 01:13:50 +08:00
ibuler
ce5950ca73 Merge with asset 2016-09-20 00:43:30 +08:00
ibuler
24e31a69cb finish asset 2016-09-20 00:39:33 +08:00
xiaokong1937@gmail.com
65461a09a7 user-group edit implement 2016-09-19 21:08:08 +08:00
江世峰
6c7e51041f update assets_group 2016-09-19 18:01:13 +08:00
江世峰
98757aa428 update assets_group 2016-09-19 17:25:41 +08:00
xiaoyu
9a81057d95 fix a typo 2016-09-19 15:51:28 +08:00
xiaoyu
f00da20764 user-group delete implement 2016-09-19 15:47:58 +08:00
ibuler
d323c9df88 Modify asset create 2016-09-19 00:07:52 +08:00
xiaokong1937@gmail.com
4e2a41f4cf user-group draft 2016-09-18 21:00:07 +08:00
xiaoyu
8a5a4f3362 fix #14 2016-09-18 14:28:34 +08:00
xiaoyu
8cdc4674d7 update toastr js and close #8 2016-09-18 10:32:23 +08:00
ibuler
acfe8950b8 MOdify asset create 2016-09-18 00:10:08 +08:00
ibuler
502c7e756b Modify asset 2016-09-17 23:43:41 +08:00
ibuler
7fd224e690 Merge with user-perm 2016-09-17 19:15:14 +08:00
ibuler
dc4d388d9a Merge with master 2016-09-17 18:53:11 +08:00
ibuler
df281defd8 Finish user detail asset grant .. 2016-09-17 18:51:19 +08:00
ibuler
7d3474aeea Add user permission user list 2016-09-17 15:20:33 +08:00
wangyong
343f139904 alter asset add and detail 2016-09-17 14:52:14 +08:00
wangyong
b95c1deee1 alter asset add and detail 2016-09-17 14:50:14 +08:00
ibuler
b5abb17568 Jiu zhe yang ba 2016-09-17 13:04:26 +08:00
ibuler
ab2eeb0da3 Finish user asset form 2016-09-17 01:04:52 +08:00
ibuler
e232962649 Update some template 2016-09-16 20:53:10 +08:00
wangyong
42012d7bfe merge master 2016-09-16 17:32:35 +08:00
ibuler
06b2c623cb Modify some bug 2016-09-16 17:23:47 +08:00
wangyong
792908eb35 asset add 2016-09-16 17:23:23 +08:00
ibuler
a091036744 Add user permission select 2016-09-16 16:09:11 +08:00
ibuler
d9812e2bdb Remove action from asset permission 2016-09-16 09:55:26 +08:00
ibuler
74b8ee8c10 Pre delete action 2016-09-16 09:38:07 +08:00
xiaokong1937@gmail.com
db5d04c37f fix #13 2016-09-16 08:45:29 +08:00
xiaokong1937@gmail.com
7984806b38 change user ssh reset type from private key to public key 2016-09-15 16:54:00 +08:00
ibuler
766bd3b76d Move js to jumpserver.js 2016-09-15 13:09:24 +08:00
ibuler
b2444c2aca Add fake data to init.json 2016-09-15 12:21:34 +08:00
ibuler
bb8852d57e Modify some style 2016-09-15 12:20:53 +08:00
ibuler
037d9323a4 Modify and code review 2016-09-15 11:19:36 +08:00
ibuler
f253d2ea69 Merge with master 2016-09-14 23:31:22 +08:00
ibuler
a4dc27f073 Finish permissin detail asset list and user list 2016-09-14 23:29:39 +08:00
ibuler
812df7b07b Restore settings.py 2016-09-14 16:52:09 +08:00
unknown
c237f82e51 restore settings 2016-09-14 16:10:15 +08:00
unknown
e7c20f0707 add assets manage Sweet Alert" 2016-09-14 15:51:09 +08:00
unknown
0f9bdab108 Merge branch 'master' of code.simcu.com:jumpserver/jumpserver 2016-09-14 15:40:47 +08:00
unknown
1005fbabbd add assets manage
Sweet Alert
2016-09-14 15:38:54 +08:00
unknown
8490583d73 add assets manage
Sweet Alert
2016-09-14 15:19:27 +08:00
ibuler
5bca783e12 permission user search 2016-09-14 01:08:26 +08:00
xiaokong1937@gmail.com
6751c5a63a reset user password and ssh pk implement 2016-09-13 21:45:10 +08:00
xiaoyu
00502ce33d trivial js style changes; bugfix for patch method 2016-09-13 16:38:43 +08:00
ibuler
4c4f598552 Finish asset permission detail and add user or user group list 2016-09-13 00:37:24 +08:00
unknown
7cff5cbf61 Merge branch 'master' of code.simcu.com:jumpserver/jumpserver 2016-09-12 15:36:13 +08:00
unknown
12831a7d21 text 2016-09-12 15:35:50 +08:00
ibuler
7b99a33a2f permission update and delete finished 2016-09-11 23:20:14 +08:00
ibuler
6d736d7309 Finish permission create and list 2016-09-11 22:45:24 +08:00
ibuler
f558ded5bb Plan delete some view 2016-09-11 20:13:56 +08:00
ibuler
324bb68667 user-pserm 2016-09-11 16:59:19 +08:00
ibuler
70cae93a4b Add user perm model and form 2016-09-11 09:50:42 +08:00
ibuler
627a5825f4 Add user perm 2016-09-10 21:08:10 +08:00
xiaokong1937@gmail.com
899233338d #8 user first login view 2016-09-10 13:20:34 +08:00
xiaokong1937@gmail.com
a7e3f9c465 #8 user first login view 2016-09-10 13:16:58 +08:00
ibuler
6069b8946b Start asset extend 2016-09-10 00:29:57 +08:00
ibuler
c58725dbc0 Finish system user view 2016-09-09 23:19:39 +08:00
ibuler
292179d41d system-user 2016-09-09 21:48:44 +08:00
ibuler
a89ae94d85 Update system user create template script 2016-09-09 00:34:23 +08:00
ibuler
4fc9274e00 Add asset system user 2016-09-09 00:09:49 +08:00
ibuler
5259dd8054 Asset form: Add some comment 2016-09-08 22:54:05 +08:00
xiaokong1937@gmail.com
d8fe59debb temp save for issue 8 2016-09-08 21:51:44 +08:00
ibuler
409fac3ef1 Merge branch 'admin-user' 2016-09-08 20:39:25 +08:00
ibuler
239dd0567f Finish admin user view 2016-09-08 20:39:06 +08:00
xiaokong1937@gmail.com
62cac20ba7 fix #9 2016-09-08 19:30:22 +08:00
ibuler
ff30435eb4 Finish adin user add 2016-09-08 18:12:53 +08:00
ibuler
5a0b119410 Stash it 2016-09-08 00:59:00 +08:00
ibuler
ccfe9b9d08 Merge branch 'admin-user' 2016-09-08 00:41:22 +08:00
ibuler
6f4a832389 Add admin user list view 2016-09-08 00:40:59 +08:00
xiaokong1937@gmail.com
8acbcb2ed2 Merge branch 'master' of code.simcu.com:jumpserver/jumpserver 2016-09-07 21:53:33 +08:00
xiaokong1937@gmail.com
04151b9957 user group quickedit frontend and rest API 2016-09-07 21:53:27 +08:00
ibuler
dfc628a397 modify idc 2016-09-07 21:03:18 +08:00
ibuler
4db352f55b Add idc 2016-09-07 20:51:33 +08:00
ibuler
d8e6433404 Merge branch 'master' of code.simcu.com:jumpserver/jumpserver 2016-09-07 20:06:01 +08:00
ibuler
d0ba17374e Finish asset group create 2016-09-07 20:05:42 +08:00
xiaokong1937@gmail.com
7f09b486d9 user-group edit serializer and url implement 2016-09-06 21:44:23 +08:00
ibuler
30fd51c268 Asset group detail 2016-09-06 21:39:21 +08:00
xiaokong1937@gmail.com
556fb4e09f refactor is_active trigger view and enable_otp trigger view in UserDetail page;trivial changes 2016-09-06 21:03:51 +08:00
xiaokong1937@gmail.com
a3096689b5 Merge branch 'xiaoyu' 2016-09-06 19:14:16 +08:00
wangyong
bc232c4f77 merge cmdb 2016-09-06 18:45:40 +08:00
wangyong
342e4bdee8 change some html 2016-09-06 18:43:13 +08:00
ibuler
dc01833a5b Forget to forgot 2016-09-06 16:55:57 +08:00
ibuler
170b49428c change date_added -> date_created 2016-09-06 16:50:19 +08:00
xiaoyu
c6f875c517 temp commit 2016-09-06 16:40:44 +08:00
ibuler
07dd6d1192 Reslove conflict 2016-09-06 15:56:03 +08:00
ibuler
25d9dbe93c Merge with cmdb 2016-09-06 15:09:00 +08:00
xiaoyu
8cc09f0e5a move login and logout view back to CBV 2016-09-06 15:03:37 +08:00
ibuler
02b5483d81 Update add->create edit->update and assetgrou->asset-grou etc format 2016-09-06 14:38:19 +08:00
ibuler
b8c10a0350 Add assetgroup form save action 2016-09-06 01:09:03 +08:00
ibuler
126d7fd62c Add asset group add Form m2m 2016-09-06 00:49:42 +08:00
ibuler
3c6f50b788 Modify some bug 2016-09-05 23:42:10 +08:00
ibuler
2cb6b15bc3 set textarea rows 2016-09-05 23:12:01 +08:00
ibuler
2f5b7ad654 Modify asset model 2016-09-05 22:57:43 +08:00
xiaokong1937@gmail.com
6aedfb5219 fix small template erros 2016-09-05 22:20:50 +08:00
xiaokong1937@gmail.com
397da7d676 Merge branch 'master' of code.simcu.com:jumpserver/jumpserver 2016-09-05 21:38:37 +08:00
xiaokong1937@gmail.com
e75d33439a fix captcha not valid bug of login view; use django's default login and logout view to enhance robustness 2016-09-05 21:38:21 +08:00
ibuler
7241f7509f Update nav 2016-09-05 20:27:44 +08:00
ibuler
8827fd2d74 Modify translation 2016-09-05 20:17:45 +08:00
ibuler
6fc3bbb97d Update initial data: fixtures dir 2016-09-05 19:41:34 +08:00
ibuler
508bda37f5 Modify form datetime valid error, because form format 2016-09-05 19:37:37 +08:00
ibuler
a0ef3cfc34 Update locale dir name to zh 2016-09-05 19:17:18 +08:00
ibuler
1b9c9c48b6 Merge branch 'cmdb' 2016-09-04 21:54:17 +08:00
ibuler
22e20d29f5 Merge branch 'master' of code.simcu.com:jumpserver/jumpserver 2016-09-04 21:50:39 +08:00
ibuler
0604f76f56 Merge branch 'cmdb' of code.simcu.com:jumpserver/jumpserver into cmdb 2016-09-04 21:47:20 +08:00
ibuler
4855e86a3f Add asset group view 2016-09-04 21:47:10 +08:00
wangyong
69349368bf cmdb merge model and url 2016-09-04 19:15:31 +08:00
wangyong
b531d9eeb2 asset add html 2016-09-04 19:12:31 +08:00
ibuler
246fcb8efa Modify run_server.py 2016-09-04 19:10:28 +08:00
ibuler
3972bd3ff3 Add asset traslation 2016-09-04 19:05:47 +08:00
ibuler
d1c96cd4b2 Merge branch 'cmdb' of code.simcu.com:jumpserver/jumpserver into cmdb 2016-09-04 18:07:19 +08:00
ibuler
154783a974 Modify some and pull from server 2016-09-04 18:07:15 +08:00
wangyong
fa07f4ee8a fix null 2016-09-04 18:06:14 +08:00
wangyong
0e8e88fac0 add admin|sys user model 2016-09-04 17:50:30 +08:00
wangyong
426c3c4062 add asset amdin|sys user model 2016-09-04 17:43:03 +08:00
ibuler
8d0334e003 Add translation 2016-09-04 17:34:48 +08:00
ibuler
0ab015abfd Asset add traslation 2016-09-04 17:15:26 +08:00
ibuler
7350e150c8 Merge branch 'master' into cmdb 2016-09-04 16:39:27 +08:00
ibuler
61f0205529 Modify users url to singular 2016-09-04 16:05:58 +08:00
ibuler
11d06b5e2b Add asset group view and url 2016-09-04 16:02:51 +08:00
ibuler
a9b5762fbc Modify api style guide info 2016-09-04 15:31:10 +08:00
广宏伟
724b1c6fd4 Add new directory logs 2016-09-04 12:37:42 +08:00
ibuler
acd98365c1 Fix translation 2016-09-04 12:31:20 +08:00
liuzheng712
32c49c080c update 2016-09-04 07:04:15 +08:00
liuzheng712
d371c0c5ae mkdirp 2016-09-04 06:55:12 +08:00
ibuler
47171174b5 Merge branch 'master' of code.simcu.com:jumpserver/jumpserver 2016-09-04 00:52:04 +08:00
ibuler
f274684473 Add translation for support i18n 2016-09-04 00:51:36 +08:00
wangyong
d96ac56460 merge master 2016-09-03 23:01:36 +08:00
wangyong
77f3a1f146 add asset add 2016-09-03 19:05:50 +08:00
ibuler
ba3f46fbd6 i18n 2016-09-03 19:02:18 +08:00
ibuler
0b406b6988 Add captch login using 2016-09-03 14:37:01 +08:00
ibuler
6f0cfd23c1 Rm logout.html 2016-09-03 00:39:52 +08:00
ibuler
10d51ada37 Update login error message 2016-09-03 00:39:06 +08:00
lijiejie
9fa70f6cb8 create a restframework api for cmdb models 2016-09-02 22:52:30 +08:00
lijiejie
cec5a7f2c6 create the restframework url 2016-09-02 22:51:10 +08:00
lijiejie
829df26b0f create the api 2016-09-02 22:49:51 +08:00
ibuler
b8bebc9b64 Merge branch 'api' of code.simcu.com:jumpserver/jumpserver into api 2016-09-02 22:24:05 +08:00
ibuler
90ca5a8bb7 start capcha support 2016-09-02 22:23:15 +08:00
ibuler
2c11255828 Add capacha support 2016-09-02 22:21:26 +08:00
ibuler
9803dd9547 Add ico, Modify forget_password 2016-09-02 18:10:26 +08:00
ibuler
dcff84958f Add run development server script: run_server.py 2016-09-01 23:10:28 +08:00
ibuler
3a9cf6c360 Add forget password and reset password 2016-09-01 23:09:58 +08:00
ibuler
8ff872f41d Add ls config 2016-09-01 16:05:14 +08:00
ibuler
5940cec0e6 Add user and send mail 2016-09-01 01:12:02 +08:00
ibuler
5359da3ce2 Finish user login 2016-08-31 23:42:06 +08:00
ibuler
c9369db578 Add new login page 2016-08-31 20:39:25 +08:00
ibuler
d70deaf1ac Modify config-example 2016-08-31 19:48:37 +08:00
ibuler
b31f8d5867 Add user generate reset password token 2016-08-31 19:28:06 +08:00
ibuler
5350f83275 Add celery usage more 2016-08-31 16:48:12 +08:00
广宏伟
dd80b94b43 Update README.md 2016-08-31 16:43:44 +08:00
广宏伟
4cbadbd941 Update README.rst 2016-08-31 16:43:18 +08:00
广宏伟
e61341df79 Update README.md to README.rst 2016-08-31 16:33:05 +08:00
ibuler
f7ab26a5da Add celery broker 2016-08-31 15:54:04 +08:00
ibuler
0d3bde1191 User add confirmed 2016-08-31 12:14:25 +08:00
ibuler
c143735393 Add ssh-key-gen function 2016-08-31 01:00:20 +08:00
ibuler
43af5383e3 Add model user method: get_token set_token 2016-08-30 20:30:47 +08:00
ibuler
ffed46175d update .gitignore 2016-08-30 17:26:33 +08:00
ibuler
31a39be9ea Resolve conflicts 2016-08-30 17:22:06 +08:00
ibuler
2022ca8e10 Add test code 2016-08-30 17:20:39 +08:00
wangyong
d0d433db1a add asset_list 2016-08-30 13:50:30 +08:00
yumaojun03
e8d8f7c406 ops: ansible_api add ansible api 2.0 adhoc runner 2016-08-30 13:00:06 +08:00
yumaojun03
f6b2abb1fb add ansible 2.0 2016-08-30 01:16:14 +08:00
ibuler
011c125564 Modify user active api And Add Token authorization 2016-08-30 00:10:27 +08:00
ibuler
1a9f90a08e Modify api 2016-08-29 21:09:32 +08:00
ibuler
3eb4897bd3 Merge branch 'api' of code.simcu.com:jumpserver/jumpserver into api 2016-08-29 19:27:23 +08:00
ibuler
3c9dbaf860 Fix some bug 2016-08-28 23:58:22 +08:00
ibuler
d918d5b466 Add Logger setting . 2016-08-26 16:56:50 +08:00
ibuler
d95ffdfbf7 Test permmision 2016-08-26 00:51:05 +08:00
yumaojun03
363ddb70e2 add celery support (not use django-celery) 2016-08-25 19:52:03 +08:00
ibuler
bb76f6c652 Add api authentication 2016-08-25 19:29:59 +08:00
ibuler
641e998504 Update context name path1, path2 => app, action 2016-08-25 13:56:49 +08:00
ibuler
0a9e4a5e85 Modify urls.conf: Merge to single 2016-08-25 13:28:02 +08:00
ibuler
8aa92bb688 Add api: UserApi And UserGroupApi 2016-08-24 19:42:16 +08:00
ibuler
1d5faa3101 Delete Model: Role 2016-08-24 17:14:21 +08:00
ibuler
b97b34961c Modify api: complete some setting 2016-08-24 12:29:19 +08:00
liuzheng712
be92ac58ae merge 2016-08-24 06:48:50 +08:00
liuzheng712
1df0def9ea pip freeze 2016-08-24 06:47:14 +08:00
ibuler
a43ac90b21 Modify CRLF to LF 2016-08-24 00:13:58 +08:00
ibuler
bbecbc8578 Merge with webternimal 2016-08-24 00:12:46 +08:00
ibuler
6458231946 Add api file 2016-08-24 00:11:13 +08:00
yumaojun03
97a2e8bb50 test add celery 2016-08-23 23:51:39 +08:00
liuzheng712
400d744938 add api redirect 2016-08-23 23:29:33 +08:00
ibuler
9a6d20b6ec Modify requirement: add some module 2016-08-23 23:00:55 +08:00
liuzheng712
7cebc4efe8 Merge branch 'master' of code.simcu.com:jumpserver/jumpserver 2016-08-23 22:35:00 +08:00
liuzheng712
91f0800e26 add webterminal 2016-08-23 22:34:52 +08:00
ibuler
a99a6b6946 Split tests file in tests module 2016-08-23 22:19:39 +08:00
ibuler
c37dea2079 Add user add view Test Case 2016-08-23 19:36:15 +08:00
ibuler
b55b516fc3 Update foot_js.html add active 2016-08-23 17:34:52 +08:00
ibuler
1f3c0d004f Change update batch, add _list_base.html 2016-08-23 17:14:08 +08:00
ibuler
3c3fda8064 Change chosen To select2 lib 2016-08-23 14:44:06 +08:00
ibuler
ae9bbb40fd Add flash message template 2016-08-23 12:18:19 +08:00
ibuler
8e5e788bcd Test case added 2016-08-23 00:39:07 +08:00
liuzheng712
c50cdd2976 add webterminal 2016-08-22 23:53:14 +08:00
ibuler
f45690b34f Add test case 2016-08-22 19:53:01 +08:00
ibuler
f0b0e41d33 Update user edit 2016-08-21 22:37:55 +08:00
ibuler
308aa2eca2 Modify README.md 2016-08-21 22:26:50 +08:00
wangyong
3771b2ff70 add asset detail and list 2016-08-21 22:12:17 +08:00
ibuler
8b0f31c43a modify list, edit url error 2016-08-21 21:32:20 +08:00
ibuler
8ba7b078fd Finish expaire date 2016-08-21 21:31:25 +08:00
ibuler
57f0b04387 Modify project statucure guide and init data 2016-08-21 20:59:39 +08:00
ibuler
24d7cb3d5d add init data dir fixture 2016-08-21 15:03:57 +08:00
ibuler
85cf2169d4 Modify readme 2016-08-21 13:59:44 +08:00
ibuler
0427d406b9 Add init data file 2016-08-21 01:16:30 +08:00
ibuler
96dd2f5c85 add config 2016-08-20 20:35:58 +08:00
ibuler
e355c7b8ef Modify pagination and Role model 2016-08-20 18:33:18 +08:00
xRain
7789c8d13d 增加了缺失的必需组件 2016-08-20 09:44:40 +08:00
ibuler
6e46a17d98 Useradd group change 2016-08-20 00:42:50 +08:00
ibuler
e48f36397e Modify url 2016-08-20 00:18:44 +08:00
ibuler
cf15b7eaff Add UserGroup some View 2016-08-19 01:39:08 +08:00
ibuler
651e89994e modify list and sort 2016-08-18 22:29:35 +08:00
ibuler
342298ad3e reslove conflict 2016-08-18 14:43:10 +08:00
ibuler
a76191ffdc Change view detail template 2016-08-18 10:20:37 +08:00
ibuler
279987925a Modify user detail of usergroup 2016-08-18 00:47:34 +08:00
ibuler
824b1c7f6f update user update view 2016-08-17 22:17:16 +08:00
ibuler
bcae7beae6 upload avatar 2016-08-17 00:36:54 +08:00
ibuler
9353022627 add context 2016-08-17 00:23:52 +08:00
ibuler
35abf16f7d Modify panel color 2016-08-17 00:22:33 +08:00
ibuler
c14eb42186 reslove conflict add user detail template 2016-08-17 00:14:56 +08:00
ibuler
98d6043f2d merged 2016-08-16 23:25:30 +08:00
ibuler
7a8d4a3a59 Add delete view 2016-08-16 22:13:06 +08:00
ibuler
3bf0d4aabb Add common app
common app used as write public api or templatetags

modify user list view and user edit view
2016-08-16 00:09:48 +08:00
ibuler
edd60cad11 Merge branch 'master' of code.simcu.com:jumpserver/jumpserver 2016-08-15 22:54:56 +08:00
广宏伟
e3223f745a app users
Modify user list view
Modify user add view
2016-08-15 21:10:30 +08:00
wangyong
fcd39370ed add asset some cbv 2016-08-14 22:10:10 +08:00
广宏伟
86ffcc973c Add user template 2016-08-14 19:18:41 +08:00
ibuler
9493fb07cb Add user list view 2016-08-14 17:21:04 +08:00
ibuler
9303415b89 modify some issues 2016-08-14 00:40:21 +08:00
ibuler
8b8e391feb Modify project structure design 2016-08-13 22:02:05 +08:00
ibuler
234684c875 Rename dir name dashboard to apps
It's may be well
2016-08-13 21:59:08 +08:00
ibuler
25f1c9ccf3 Merge branch 'master' of code.simcu.com:jumpserver/jumpserver 2016-08-13 16:17:42 +08:00
ibuler
1dd17b1814 Add django CBV interitance 2016-08-13 16:17:30 +08:00
广宏伟
089b54986a Update README.md 2016-08-13 01:16:52 +08:00
ibuler
224797e5e7 Merge branch 'master' of code.simcu.com:jumpserver/jumpserver 2016-08-13 01:11:58 +08:00
ibuler
346fbcc286 add logs,install dir, modify table_design 2016-08-13 01:11:46 +08:00
广宏伟
1ee53c6877 Update README.md 2016-08-13 01:09:33 +08:00
广宏伟
7f4d737503 Update README.md 2016-08-13 01:03:51 +08:00
ibuler
551d3df892 Finish table design 2016-08-13 00:18:34 +08:00
ibuler
b2bfdb097b Modify table design 2016-08-12 19:34:26 +08:00
ibuler
40572cdc00 Modify table design 2016-08-11 15:49:24 +08:00
ibuler
46f0d17da7 update table_design.xml 2016-08-10 23:54:31 +08:00
ibuler
bc474c6c06 Add table design 2016-08-10 23:03:01 +08:00
ibuler
31a41000bf 添加 user 表结构,简单思考 2016-08-10 01:30:19 +08:00
ibuler
a5621a4178 修改README.md, 采用绝对地址 2016-08-09 19:43:20 +08:00
ibuler
17869dc5d8 修改README.md, 采用相对地址 2016-08-09 19:41:58 +08:00
ibuler
69c1639e46 修改 README.md 采用相对路径 2016-08-09 19:39:18 +08:00
ibuler
ffcd669898 修改readme显示,采用相对路径 2016-08-09 19:37:55 +08:00
ibuler
8121e48825 添加 api设计风格文档
详细描述了设计原则和遵守风格, 还不够完善,会随着开发进程逐渐完善它
2016-08-09 19:29:52 +08:00
ibuler
aed18698a3 修改python编码风格指导
添加api风格约定, python风格添加了详细说明,更改project的骨架说明
2016-08-09 18:36:13 +08:00
ibuler
e1d5cbd06e 添加 utils和api样例文件 2016-08-09 17:27:37 +08:00
ibuler
964247b1f2 修改 nav导航显示 2016-08-09 16:12:11 +08:00
ibuler
ef604615ec 修改项目骨架,添加前端框架 base.html 2016-08-09 14:42:21 +08:00
ibuler
bd3735e755 Change some word 2016-08-09 01:10:23 +08:00
ibuler
7bb6890370 Change content of README.md 2016-08-09 01:05:57 +08:00
ibuler
734f6564fa Change project_structure content to a new file 2016-08-09 00:57:31 +08:00
ibuler
b5c159c967 Init project structure 2016-08-09 00:43:11 +08:00
ibuler
ba215335bf Add .gitignore 2016-08-09 00:42:33 +08:00
ibuler
da4bd937a8 Merge branch 'dev' 2016-07-26 18:43:26 +08:00
ibuler
b5b14373d0 Merge branch 'master' of github.com:jumpserver/jumpserver 2016-07-26 18:42:40 +08:00
老广
270499a4fd 修改删除系统用户的交互 (#276)
* fix(api) 修改建立目录的bug

使用bash代替python完成建立777目录的功能

* fix passwd input

* fix(mkdir) 修改mkdirs策略

修改原来导致的bug

* fix passwd input (#232)

修复记录敏感密码bug

* fix passwd input

* fix passwd input

* fix passwd input

* fix(connect) 输入role id时,输入了role名称异常

抓取后并处理

* Update perm_role_edit.html

* fix role delete
2016-07-26 18:41:58 +08:00
ibuler
26ad623d0e fix role delete 2016-07-26 11:59:04 +08:00
ibuler
f8eedc8650 Merge branch 'dev' 2016-07-26 10:20:39 +08:00
ibuler
89e32b327d Merge branch 'master' of github.com:jumpserver/jumpserver 2016-07-26 10:20:20 +08:00
假想控
27223a1883 Update next.py
修改安装成功访问web提示信息
2016-07-23 18:14:26 +08:00
老广
e0ae7eeee7 Update user_api.py 2016-07-12 17:08:26 +08:00
老广
b5fefb4687 Update user_api.py 2016-07-12 17:00:20 +08:00
chnliyong
ecfb2f3d40 fix reset password bug (#274)
感谢PR
2016-07-08 03:22:38 -05:00
老广
1614dd5a4d Update README.md 2016-06-20 10:01:35 +08:00
liuzheng
cf3e89c374 Update run_server.py
because if someone need run jumpserver at 127.0.0.1 need this fix
2016-06-15 22:06:26 +08:00
老广
0f0908d3f3 Update jumpserver.conf 2016-06-14 17:32:28 +08:00
ibuler
31c3def1a8 Update group_add.html 2016-06-07 17:34:08 +08:00
ibuler
f75003461a Update settings.py 2016-06-07 16:39:35 +08:00
ibuler
49504e46ee Update docker-compose.yaml 2016-06-07 16:37:06 +08:00
ibuler
699bdd1348 Update config_tmpl.conf 2016-06-07 16:35:48 +08:00
ibuler
e608fbaad5 Update settings.py 2016-06-07 16:17:10 +08:00
ibuler
6a7d105484 Update jumpserver.conf 2016-06-07 16:12:38 +08:00
xRain
6acda5fcd1 add docker support and update the locally 2016-06-07 11:51:16 +08:00
xRain
57ba8ed2fb add docker support 2016-06-07 11:49:59 +08:00
kikiyou
d6c4017a2e 使示例可以正确运行 (#237)
为了安全pattern=空,后示例代码无法使用,运行示例时加上pattern='*',使示例可以返回正确的结果
2016-06-06 21:55:54 -05:00
lrqrun
b7be5d14e0 User object is not the user, must update at the user (#254)
修改信息保存后数据不是最新的而是之前的数据,因为在object的惰性查询不会获取到最新的数据,因此需要在缓存的对象基础上修改save后commit到数据库。
2016-06-06 21:55:28 -05:00
lrqrun
f130a78f0a users_selected keep new (#255)
用户组保存后数据显示的问题,在已选用户处显示选择的数据
2016-06-06 21:54:11 -05:00
__YoYO
a077053b68 Dev (#250)
* Update perm_role_edit.html
2016-06-06 21:52:41 -05:00
Kallen Ding
c93c8de7fe Replace os.makedirs to mkdir. (#251)
解决Tty Logs 日志跨天后目录权限不对的问题
2016-06-06 21:50:38 -05:00
ibuler
3176156639 Update connect.py 2016-06-03 10:03:12 +08:00
ibuler
7072d16f00 Update install.py 2016-05-31 19:25:10 +08:00
ibuler
f2dda35f28 Update footer.html 2016-05-19 17:47:50 +08:00
zheng
0cc04ee20d Group edit (#241)
* 修复主机组编辑时回车导致主机丢失问题

在主机组编辑页面,如果直接执行回车会导致主机组中主机信息丢失。
本修复方法是关闭回车提交

* 编辑主机组在移除过滤保存时数据会丢失

现象:在反向移除选择的主机时,用过滤框搜索移除主机此时保存的数据是当前过滤显示的数据
后果:会造成原有主机组数据丢失
修复:在保存之前触发一次空值搜索
2016-05-18 05:14:18 -05:00
ibuler
7531a3ada7 修复编辑系统用户,用户名jsbug (#240)
* Update perm_role_edit.html
2016-05-18 04:08:12 -05:00
ibuler
08717d196f Update perm_role_edit.html 2016-05-18 17:00:35 +08:00
ibuler
a1859676e4 fix(connect) 输入role id时,输入了role名称异常
抓取后并处理
2016-05-11 19:13:38 +08:00
ibuler
e4a54ddbf8 Merge branch 'master' into dev 2016-05-11 19:10:59 +08:00
ibuler
5b9a9779c8 Merge branch 'master' of github.com:jumpserver/jumpserver 2016-05-11 19:10:45 +08:00
ibuler
32ab8a1646 完美修复vim等交互式命令记录 (#236)
* fix(api) 修改建立目录的bug

使用bash代替python完成建立777目录的功能

* fix passwd input

* fix(mkdir) 修改mkdirs策略

修改原来导致的bug

* fix passwd input (#232)

修复记录敏感密码bug

* fix passwd input

* fix passwd input

* fix passwd input
2016-05-11 18:48:38 +08:00
kelianchun
f0e943ebcc Merge pull request #235 from jumpserver/fix_passwd_input
fix passwd input
2016-05-11 18:29:21 +08:00
kelianchun_miller
c0e91896df fix passwd input 2016-05-11 18:27:26 +08:00
ibuler
d60562a034 修复交互式记录密码bug,merge to master (#234)
* fix(api) 修改建立目录的bug

使用bash代替python完成建立777目录的功能

* fix passwd input

* fix(mkdir) 修改mkdirs策略

修改原来导致的bug

* fix passwd input (#232)

修复记录敏感密码bug

* fix passwd input

* fix passwd input
2016-05-11 17:44:22 +08:00
ibuler
93e08a6e29 修复创建tty日志文件失败, 请修改目录 bug (#231)
* fix(api) 修改建立目录的bug

使用bash代替python完成建立777目录的功能

* fix passwd input

* fix(mkdir) 修改mkdirs策略

修改原来导致的bug

* fix passwd input (#232)

修复记录敏感密码bug

* fix passwd input

* fix passwd input
2016-05-11 17:41:46 +08:00
ibuler
0f09172ed0 conflict reslove 2016-05-11 17:38:38 +08:00
ibuler
948763443e Merge branch 'fix_passwd_input' of github.com:jumpserver/jumpserver into fix_passwd_input 2016-05-11 17:21:41 +08:00
kelianchun_miller
3ef3b452e2 fix passwd input 2016-05-11 17:21:15 +08:00
ibuler
39ebdb2f61 Merge branch 'fix_passwd_input' of github.com:jumpserver/jumpserver into fix_passwd_input 2016-05-11 17:20:47 +08:00
kelianchun_miller
dff50305de fix passwd input 2016-05-11 17:18:40 +08:00
ibuler
f71c8551e8 fix passwd input (#232)
修复记录敏感密码bug
2016-05-11 11:31:53 +08:00
ibuler
d63d4eb019 Merge branch 'dev' into fix_passwd_input 2016-05-11 11:25:43 +08:00
zheng
d66ba9d6c6 修复主机组编辑时回车导致主机丢失问题 (#230)
在主机组编辑页面,如果直接执行回车会导致主机组中主机信息丢失。
本修复方法是关闭回车提交
2016-05-11 11:22:08 +08:00
ibuler
8526437c88 fix(mkdir) 修改mkdirs策略
修改原来导致的bug
2016-05-11 11:19:32 +08:00
kelianchun_miller
987b1c2c36 fix passwd input 2016-05-11 11:10:48 +08:00
ibuler
18e159350b fix(api) 修改建立目录的bug
使用bash代替python完成建立777目录的功能
2016-05-11 11:10:02 +08:00
ibuler
1338d25b4e fix bug 2016-05-10 13:55:06 +08:00
ibuler
f994c4d1da fix(connect.py) 修复max引起的异常
已经修复
2016-05-10 13:48:55 +08:00
ibuler
5fab276c26 fix(jperm) fix jperm role detail list.
* 1. Add a window to list pushed error asset
* 2. Fix old bug for pagninator
2016-05-10 12:19:54 +08:00
ibuler
9f171da570 修复cli 端资产列表显示 (#226)
* modify(jperm) 授权列表模糊搜索

修改授权规则搜索为模糊搜索

* fix(cli nav align) Max Hostname length 30, else will be truncate.
2016-05-10 10:11:32 +08:00
ibuler
fed00d04a6 fix(cli nav align) Max Hostname length 30, else will be truncate. 2016-05-09 20:19:01 +08:00
ibuler
ecfaf9f02d modify(jperm) 授权列表模糊搜索 (#225)
修改授权规则搜索为模糊搜索
2016-05-09 18:54:33 +08:00
ibuler
d05e9d0b45 modify(jperm) 授权列表模糊搜索
修改授权规则搜索为模糊搜索
2016-05-09 18:53:04 +08:00
ibuler
d4385c7e43 Merge branch 'master' into dev 2016-04-28 15:56:22 +08:00
ibuler
04fc9962ff Merge branch 'dev' 2016-04-28 15:55:08 +08:00
ibuler
2fd68845ce Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2016-04-28 15:54:33 +08:00
yumaojun03
bd69339e22 Bug fix hostname (#216)
* fix (jasset):   修复资产hostname过长和密码过长引起的bug

1. 修改password字段的长度,对称加密过后的字符串会变长,所有设置得比较大(256)
2. 添加check hostname 和 password的 长度校验

* fix (jumpserver/jasset):   修复setting时,秘密过长问题。

1. 修改password字段的长度,对称加密过后的字符串会变长,所有设置得比较大(256)
2. 后端修复views秘密超过30位不保存
3.前段使用js限制秘密长多不能超过30位

* fix (jumpserver/jasset):   setting and asset hostname password  too long.

1. 添加setting password字段长度验证
2. 添加资产主机名和密码长度验证

* fix (jumpserver/jasset):   setting and asset hostname password  too long.

1. 修正setting时的 输入密码的提示错误.
2016-04-28 15:44:48 +08:00
liuzheng
c6404f7ed6 Static bug (#208)
* 紧急修复下载文件后静态文件404问题

* 紧急修复监控白屏问题

* 紧急修复下载文件后静态文件404问题

* 紧急修复下载文件后静态文件404问题

* 修复zip包为空问题
2016-04-24 20:42:02 +08:00
huangguozhen
6ce948366d Update base.html (#213)
fix(frontend): use webkit default in multi kernel browsers
2016-04-23 10:35:28 +08:00
ibuler
9e78fd3651 Merge master to dev (#212)
* Update install.py

修改centos7支持

* Update install.py

* Support resize web terminal size

Support resize web terminal size.

Change new windows to a new tab.
May be more hommization

* Static bug (#204)

* 紧急修复下载文件后静态文件404问题

* 紧急修复监控白屏问题

* bugfix(upload web) When download file, static file will unreachable.

Didn't change dir

fixed

* bugfix(upload web) When download file, static file will unreachable. (#206) (#207)

* Update install.py

修改centos7支持

* Update install.py

* Support resize web terminal size

Support resize web terminal size.

Change new windows to a new tab.
May be more hommization

* Static bug (#204)

* 紧急修复下载文件后静态文件404问题

* 紧急修复监控白屏问题

* bugfix(upload web) When download file, static file will unreachable.

Didn't change dir

fixed

* fix bug index out of range (#210)
2016-04-22 11:51:36 +08:00
kelianchun
5afd135967 fix bug index out of range (#210) 2016-04-22 11:49:50 +08:00
ibuler
d5aa9324fa Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2016-04-21 18:12:58 +08:00
ibuler
9096a6e5b8 bugfix(upload web) When download file, static file will unreachable. (#206) (#207)
* Update install.py

修改centos7支持

* Update install.py

* Support resize web terminal size

Support resize web terminal size.

Change new windows to a new tab.
May be more hommization

* Static bug (#204)

* 紧急修复下载文件后静态文件404问题

* 紧急修复监控白屏问题

* bugfix(upload web) When download file, static file will unreachable.

Didn't change dir

fixed
2016-04-20 15:21:55 +08:00
ibuler
cb58012a82 bugfix(upload web) When download file, static file will unreachable. (#206)
* Update install.py

修改centos7支持

* Update install.py

* Support resize web terminal size

Support resize web terminal size.

Change new windows to a new tab.
May be more hommization

* Static bug (#204)

* 紧急修复下载文件后静态文件404问题

* 紧急修复监控白屏问题

* bugfix(upload web) When download file, static file will unreachable.

Didn't change dir

fixed
2016-04-20 15:20:11 +08:00
ibuler
58bb3cc84f Merge branch 'dev'
fix download error

static file lost
2016-04-20 15:17:04 +08:00
ibuler
c2ff05201a Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2016-04-20 14:44:06 +08:00
yumaojun03
9be13cf08f fix (jperm): 修复密码添加和更新 role时 密码过长引起的bug (#202)
1. 修改password字段的长度,对称加密过后的字符串会变长,所有设置得比较大(512)
2. 修改后端检查密码长度,并触发异常。
2016-04-20 14:31:52 +08:00
ibuler
eb4ec47f7a bugfix(upload web) When download file, static file will unreachable.
Didn't change dir

fixed
2016-04-20 14:29:11 +08:00
liuzheng
e2eb9b72f8 Static bug (#204)
* 紧急修复下载文件后静态文件404问题

* 紧急修复监控白屏问题
2016-04-20 13:08:26 +08:00
ibuler
c9ff235089 fix(connect) input exact ip for connect
modify search strategy

if some ip match pass
2016-04-16 16:27:15 +08:00
ibuler
9af809a4f0 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2016-04-12 11:12:32 +08:00
Astraeux
288a42663a TTY nav sort by ip / hostname / none (#198)
* TTY nav sort by ip / hostname / none

* add newline at end of file
2016-04-12 11:11:19 +08:00
liuzheng
cca15d4211 Support resize web terminal size
Support resize web terminal size.

Change new windows to a new tab.
May be more hommization
2016-04-07 15:45:48 +08:00
jiaxiangkong
fe2081b407 Update install.py 2016-04-07 15:07:03 +08:00
ibuler
fa323d4987 Update install.py
修改centos7支持
2016-04-07 11:56:00 +08:00
ibuler
eeef4a2f95 Merge branch 'windowResize' of github.com:jumpserver/jumpserver into windowResize 2016-04-06 13:04:47 +08:00
liuzheng712
2e49f51093 update 2016-04-06 12:58:00 +08:00
ibuler
0481e83ec4 Merge branch 'dev' into windowResize 2016-04-06 12:57:54 +08:00
liuzheng712
ff8b5bd6c0 默认terminal100x35 2016-04-06 10:26:30 +08:00
liuzheng712
aabab653d3 默认terminal大小100x35 2016-04-06 10:24:18 +08:00
ibuler
efe0b3acc0 Fix nav info and delete user key when delete a user
Fix nav info and delete user key when delete a user

reviewd by ibuler <ibuler@qq.com>
2016-04-05 23:47:27 +08:00
ibuler
35cc966132 change(info) Modify some nav info
May be clearly.
2016-04-05 23:44:01 +08:00
ibuler
777997202b patch again with 1f09a40c77
print => debug
2016-04-05 22:37:55 +08:00
ibuler
1f09a40c77 fix(user manage and connect first login)
When delete a user, but didn't delete the user sysuser key. When create
a user with same username, error occur.

When user login tty, and type a num first, it will search a host, but
login the asset with the id.

fixed
2016-04-05 22:34:37 +08:00
ibuler
d20fecadac Merge branch 'dev' 2016-04-05 16:53:06 +08:00
ibuler
fa430bf104 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2016-04-05 16:52:44 +08:00
ibuler
8bb66ac254 fix(install.py) delete old pycrypto module
fixed
2016-04-05 16:51:58 +08:00
liuzheng712
6518aa3670 bugfix 2016-04-05 13:05:07 +08:00
liuzheng712
c76d9ebd88 bugfix 2016-04-05 12:56:37 +08:00
liuzheng712
7f4d3ffdbc bug_fix 2016-04-05 12:54:37 +08:00
liuzheng712
b908fdafc6 udpate 2016-04-05 12:50:09 +08:00
liuzheng712
ef59cff44b bug_fix 2016-04-05 12:42:44 +08:00
ibuler
f511802db5 fix asset group judge
reviewed by: ibuler <ibuler@qq.com>
2016-04-05 11:22:31 +08:00
ibuler
30c74b8427 Merge pull request #192 from jumpserver/group_judge
new feature (connect) Ignore case in searching
2016-04-05 11:19:55 +08:00
ibuler
2dd16b91ec Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2016-04-05 11:18:54 +08:00
liuzheng712
bb7a3ea053 Merge branch 'master' of github.com:jumpserver/jumpserver 2016-04-05 10:53:47 +08:00
liuzheng712
0499a7265a 手动修改窗口大小问题 2016-04-05 10:53:35 +08:00
ibuler
1959c685b9 new feature (connect) Ignore case in searching
finshed
2016-04-01 17:52:47 +08:00
ibuler
cd80fbcdbb Merge pull request #190 from jumpserver/dev
Dev
2016-04-01 17:24:45 +08:00
ibuler
5814b833ed Merge pull request #189 from jumpserver/webexec_log
Webexec log
2016-04-01 16:44:35 +08:00
ibuler
cda0b9c90a fix(web exec) Web execute command log didn't get the real ip if behind the lb proxy.
fixed
2016-04-01 16:42:01 +08:00
ibuler
609bba569e fix(web exec) Web execute command log didn't get the real ip if behind the lb proxy.
fixed
2016-04-01 16:40:53 +08:00
ibuler
77c7f8fb54 fix(web exec) Web execute command log didn't get the real ip if behind the lb proxy.
fixed
2016-04-01 16:38:37 +08:00
ibuler
f65290ef38 Merge pull request #188 from jumpserver/dev
新增功能
2016-04-01 13:49:28 +08:00
ibuler
de594aebd5 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2016-04-01 12:12:34 +08:00
ibuler
2be81c0322 fix(connect) delete debug
fixed
2016-04-01 12:11:31 +08:00
ibuler
e902fccd39 Merge pull request #187 from jumpserver/search_sort
feat (sort search) 对搜索结果排序
2016-04-01 11:54:28 +08:00
ibuler
61d162312f Merge pull request #185 from Astraeux/support_amazon_linux
支持 Amazon Linux
2016-04-01 11:53:55 +08:00
ibuler
30a3cd2911 feat (sort search) 对搜索结果排序
tty登陆连接,搜索时对结果进行排序

finished
2016-04-01 11:50:35 +08:00
ibuler
2dfe9337a2 Merge pull request #186 from jumpserver/conn_search
fix(connect) 增加模糊搜索
2016-03-31 23:50:37 +08:00
ibuler
c188696328 fix(connect) 增加模糊搜索
之前只是输入id登陆,增加了模糊搜索登陆

如果搜索唯一则登陆
2016-03-31 23:45:01 +08:00
Astraeux
f8fac06e1b 支持 Amazon Linux 2016-03-31 15:14:41 +08:00
796 changed files with 44139 additions and 29963 deletions

7
.dockerignore Normal file
View File

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

16
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,16 @@
[简述你的问题]
##### 使用版本
[请提供你使用的Jumpserver版本 0.3.2 或 0.5.0]
##### 问题复现步骤
1. [步骤1]
2. [步骤2]
##### 具体表现[截图可能会更好些,最好能截全]
##### 其他
[注:] 完成后请关闭 issue

68
.gitignore vendored
View File

@@ -1,46 +1,34 @@
*.py[cod]
.idea
test.py
.DS_Store
db.sqlite3
# C extensions
*.so
# Packages
*.egg
*.egg-info
*.pyc
*.pyo
*.swp
.env
env
env*
venv
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
lib
lib64
__pycache__
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
*.egg
*.egg-info
_mailinglist
dump.rdb
.tox
nosetests.xml
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
.settings
.cache/
.idea/
db.sqlite3
config.py
migrations/
*.log
logs/*
keys/*
jumpserver.conf
nohup.out
host_rsa_key
*.bat
tags
jumpserver.iml
.python-version
tmp/*
sessions/*
media
celerybeat.pid
django.db
celerybeat-schedule.db
data/static
docs/_build/

View File

@@ -1,4 +1,4 @@
GNU GENERAL PUBLIC LICENSE
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
@@ -336,4 +336,4 @@ This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
Public License instead of this License.

131
README.md
View File

@@ -1,80 +1,61 @@
## 写在前面
- 版本号变更 2.0 -> 0.2版本 3.0 -> 0.3版本
## Jumpserver
#欢迎使用Jumpserver
**Jumpserver** 是一款由python编写开源的跳板机(堡垒机)系统实现了跳板机应有的功能。基于ssh协议来管理客户端无需安装agent。
支持常见系统:
1. redhat centos
2. debian
3. suse ubuntu
4. freebsd
5. 其他ssh协议硬件设备
###截图:
首页
![webterminal](https://github.com/ibuler/static/raw/master/jumpserver3/index.jpg)
WebTerminal:
![webterminal](https://github.com/ibuler/static/raw/master/jumpserver3/webTerminal.gif)
Web批量执行命令
![WebExecCommand](https://github.com/ibuler/static/raw/master/jumpserver3/webExec.gif)
录像回放
![录像](https://github.com/ibuler/static/raw/master/jumpserver3/record.gif)
跳转和批量命令
![跳转](https://github.com/ibuler/static/raw/master/jumpserver3/connect.gif)
命令统计
![跳转](https://github.com/ibuler/static/raw/master/jumpserver3/command.jpg)
### 文档
* [访问wiki](https://github.com/jumpserver/jumpserver/wiki)
* [概览](https://github.com/jumpserver/jumpserver/wiki/%E6%A6%82%E8%A7%88)
* [名词解释](https://github.com/jumpserver/jumpserver/wiki/%E5%90%8D%E8%AF%8D%E8%A7%A3%E9%87%8A)
* [常见问题](https://github.com/jumpserver/jumpserver/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)
* 安装基于:[RedHat 的系统](https://github.com/jumpserver/jumpserver/wiki/%E5%9F%BA%E4%BA%8E-RedHat-%E7%9A%84%E7%B3%BB%E7%BB%9F)[Debian 的系统](https://github.com/jumpserver/jumpserver/wiki/%E5%9F%BA%E4%BA%8E-Debian-%E7%9A%84%E7%B3%BB%E7%BB%9F)
* [快速开始](https://github.com/jumpserver/jumpserver/wiki/%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B)
* [安装图解](https://github.com/jumpserver/jumpserver/wiki/%E5%AE%89%E8%A3%85%E5%9B%BE%E8%A7%A3)
* [应用图解](https://github.com/jumpserver/jumpserver/wiki/%E5%BA%94%E7%94%A8%E5%9B%BE%E8%A7%A3)
### 特点
* 完全开源GPL授权
* Python编写容易再次开发
* 实现了跳板机基本功能,认证、授权、审计
* 集成了Ansible批量命令等
* 支持WebTerminal
* Bootstrap编写界面美观
* 自动收集硬件信息
* 录像回放
* 命令搜索
* 实时监控
* 批量上传下载
### 其它
[Jumpserver官网](http://www.jumpserver.org)
[论坛](http://bbs.jumpserver.org)
[demo站点](http://demo.jumpserver.org)
交流群: 399218702
### 团队
![](https://github.com/ibuler/static/raw/master/jumpserver3/team.jpg)
[![Python3](https://img.shields.io/badge/python-3.6-green.svg?style=plastic)](https://www.python.org/)
[![Django](https://img.shields.io/badge/django-1.11-brightgreen.svg?style=plastic)](https://www.djangoproject.com/)
[![Ansible](https://img.shields.io/badge/ansible-2.2.2.0-blue.svg?style=plastic)](https://www.ansible.com/)
[![Paramiko](https://img.shields.io/badge/paramiko-2.1.2-green.svg?style=plastic)](http://www.paramiko.org/)
----
Jumpserver是全球首款完全开源的堡垒机使用GNU GPL v2.0开源协议,是符合 4A 的专业运维审计系统。
Jumpserver使用Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 解决方案,交互界面美观、用户体验好。
Jumpserver采纳分布式架构支持多机房跨区域部署中心节点提供 API各机房部署登录节点可横向扩展、无并发限制。
改变世界,从一点点开始。
----
### 功能
- 统一认证
- 资产管理
- 统一授权
- 审计
- 支持LDAP认证
- Web terminal
- SSH Server
- 支持Windows RDP
### 开始使用
快速开始文档 [Docker安装](http://docs.jumpserver.org/zh/latest/quickstart.html)
一步一步安装文档 [详细部署](http://docs.jumpserver.org/zh/latest/step_by_step.html)
也可以查看我们完整文档包括了使用和开发 [文档](http://docs.jumpserver.org)
### Demo 和 截图
我们提供了DEMO和截图可以让你快速了解Jumpserver
[DEMO](http://demo.jumpserver.org)
[截图](http://docs.jumpserver.org/zh/docs/snapshot.html)
### SDK
我们还编写了一些SDK供你其它系统快速和Jumpserver APi交互
- [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.
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.

5
apps/__init__.py Normal file
View File

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

View File

@@ -0,0 +1,5 @@
from .admin_user import *
from .asset import *
from .label import *
from .system_user import *
from .node import *

View File

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

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

@@ -0,0 +1,112 @@
# -*- coding: utf-8 -*-
#
from rest_framework import generics
from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
from rest_framework.pagination import LimitOffsetPagination
from django.shortcuts import get_object_or_404
from django.db.models import Q
from common.mixins import IDInFilterMixin
from common.utils import get_logger
from ..hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser, \
NodePermissionUtil
from ..models import Asset, SystemUser, AdminUser, Node
from .. import serializers
from ..tasks import update_asset_hardware_info_manual, \
test_asset_connectability_manual
from ..utils import LabelFilter
logger = get_logger(__file__)
__all__ = [
'AssetViewSet', 'UserAssetListView', 'AssetListUpdateApi',
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi'
]
class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
"""
API endpoint that allows Asset to be viewed or edited.
"""
filter_fields = ("hostname", "ip")
search_fields = filter_fields
ordering_fields = ("hostname", "ip", "port", "cpu_cores")
queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer
pagination_class = LimitOffsetPagination
permission_classes = (IsSuperUserOrAppUser,)
def get_queryset(self):
queryset = super().get_queryset()
admin_user_id = self.request.query_params.get('admin_user_id')
node_id = self.request.query_params.get("node_id")
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()
return queryset
class UserAssetListView(generics.ListAPIView):
queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer
permission_classes = (IsValidUser,)
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]
)
return queryset
class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
"""
Asset bulk update api
"""
queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer
permission_classes = (IsSuperUser,)
class AssetRefreshHardwareApi(generics.RetrieveAPIView):
"""
Refresh asset hardware info
"""
queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer
permission_classes = (IsSuperUser,)
def retrieve(self, request, *args, **kwargs):
asset_id = kwargs.get('pk')
asset = get_object_or_404(Asset, pk=asset_id)
summary = update_asset_hardware_info_manual(asset)[1]
logger.debug("Refresh summary: {}".format(summary))
if summary.get('dark'):
return Response(summary['dark'].values(), status=501)
else:
return Response({"msg": "ok"})
class AssetAdminUserTestApi(generics.RetrieveAPIView):
"""
Test asset admin user connectivity
"""
queryset = Asset.objects.all()
permission_classes = (IsSuperUser,)
def retrieve(self, request, *args, **kwargs):
asset_id = kwargs.get('pk')
asset = get_object_or_404(Asset, pk=asset_id)
ok, msg = test_asset_connectability_manual(asset)
if ok:
return Response({"msg": "pong"})
else:
return Response({"error": msg}, status=502)

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

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

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

@@ -0,0 +1,150 @@
# ~*~ coding: utf-8 ~*~
# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
#
# Licensed under the GNU General Public License v2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.gnu.org/licenses/gpl-2.0.html
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from rest_framework import generics, mixins
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 ..models import Node
from ..tasks import update_assets_hardware_info_util, test_asset_connectability_util
from .. import serializers
logger = get_logger(__file__)
__all__ = [
'NodeViewSet', 'NodeChildrenApi',
'NodeAddAssetsApi', 'NodeRemoveAssetsApi',
'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi',
'TestNodeConnectiveApi'
]
class NodeViewSet(BulkModelViewSet):
queryset = Node.objects.all()
permission_classes = (IsSuperUser,)
serializer_class = serializers.NodeSerializer
def perform_create(self, serializer):
child_key = Node.root().get_next_child_key()
serializer.validated_data["key"] = child_key
serializer.save()
class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
queryset = Node.objects.all()
permission_classes = (IsSuperUser,)
serializer_class = serializers.NodeSerializer
instance = None
def post(self, request, *args, **kwargs):
if not request.data.get("value"):
request.data["value"] = _("New node {}").format(
Node.root().get_next_child_key().split(":")[-1]
)
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,
)
def get(self, request, *args, **kwargs):
instance = self.get_object()
if self.request.query_params.get("all"):
children = instance.get_all_children()
else:
children = instance.get_children()
response = [{"id": node.id, "key": node.key, "value": node.value} for node in children]
return Response(response, status=200)
class NodeAddChildrenApi(generics.UpdateAPIView):
queryset = Node.objects.all()
permission_classes = (IsSuperUser,)
serializer_class = serializers.NodeAddChildrenSerializer
instance = None
def put(self, request, *args, **kwargs):
instance = self.get_object()
nodes_id = request.data.get("nodes")
children = [get_object_or_none(Node, id=pk) for pk in nodes_id]
for node in children:
if not node:
continue
node.parent = instance
node.save()
return Response("OK")
class NodeAddAssetsApi(generics.UpdateAPIView):
serializer_class = serializers.NodeAssetsSerializer
queryset = Node.objects.all()
permission_classes = (IsSuperUser,)
instance = None
def perform_update(self, serializer):
assets = serializer.validated_data.get('assets')
instance = self.get_object()
instance.assets.add(*tuple(assets))
class NodeRemoveAssetsApi(generics.UpdateAPIView):
serializer_class = serializers.NodeAssetsSerializer
queryset = Node.objects.all()
permission_classes = (IsSuperUser,)
instance = None
def perform_update(self, serializer):
assets = serializer.validated_data.get('assets')
instance = self.get_object()
if instance != Node.root():
instance.assets.remove(*tuple(assets))
class RefreshNodeHardwareInfoApi(APIView):
permission_classes = (IsSuperUser,)
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 = _("Refresh node assets hardware info: {}".format(node.name))
task_name = _("更新节点资产硬件信息: {}".format(node.name))
update_assets_hardware_info_util.delay(assets, task_name=task_name)
return Response({"msg": "Task created"})
class TestNodeConnectiveApi(APIView):
permission_classes = (IsSuperUser,)
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))
test_asset_connectability_util.delay(assets, task_name=task_name)
return Response({"msg": "Task created"})

View File

@@ -0,0 +1,84 @@
# ~*~ coding: utf-8 ~*~
# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
#
# Licensed under the GNU General Public License v2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.gnu.org/licenses/gpl-2.0.html
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from rest_framework import generics
from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
from common.utils import get_logger
from ..hands import IsSuperUser, IsSuperUserOrAppUser
from ..models import SystemUser
from .. import serializers
from ..tasks import push_system_user_to_assets_manual, \
test_system_user_connectability_manual
logger = get_logger(__file__)
__all__ = [
'SystemUserViewSet', 'SystemUserAuthInfoApi',
'SystemUserPushApi', 'SystemUserTestConnectiveApi'
]
class SystemUserViewSet(BulkModelViewSet):
"""
System user api set, for add,delete,update,list,retrieve resource
"""
queryset = SystemUser.objects.all()
serializer_class = serializers.SystemUserSerializer
permission_classes = (IsSuperUserOrAppUser,)
class SystemUserAuthInfoApi(generics.RetrieveUpdateAPIView):
"""
Get system user auth info
"""
queryset = SystemUser.objects.all()
permission_classes = (IsSuperUserOrAppUser,)
serializer_class = serializers.SystemUserAuthSerializer
def update(self, request, *args, **kwargs):
password = request.data.pop("password", None)
private_key = request.data.pop("private_key", None)
instance = self.get_object()
if password or private_key:
instance.set_auth(password=password, private_key=private_key)
return super().update(request, *args, **kwargs)
class SystemUserPushApi(generics.RetrieveAPIView):
"""
Push system user to cluster assets api
"""
queryset = SystemUser.objects.all()
permission_classes = (IsSuperUser,)
def retrieve(self, request, *args, **kwargs):
system_user = self.get_object()
push_system_user_to_assets_manual.delay(system_user)
return Response({"msg": "Task created"})
class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
"""
Push system user to cluster assets api
"""
queryset = SystemUser.objects.all()
permission_classes = (IsSuperUser,)
def retrieve(self, request, *args, **kwargs):
system_user = self.get_object()
test_system_user_connectability_manual.delay(system_user)
return Response({"msg": "Task created"})

11
apps/assets/apps.py Normal file
View File

@@ -0,0 +1,11 @@
from __future__ import unicode_literals
from django.apps import AppConfig
class AssetsConfig(AppConfig):
name = 'assets'
def ready(self):
from . import signals_handler
super().ready()

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

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

View File

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

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

@@ -0,0 +1,136 @@
# -*- coding: utf-8 -*-
#
from django import forms
from django.utils.translation import gettext_lazy as _
from ..models import Asset, AdminUser
from common.utils import get_logger
logger = get_logger(__file__)
__all__ = ['AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm']
class AssetCreateForm(forms.ModelForm):
class Meta:
model = Asset
fields = [
'hostname', 'ip', 'public_ip', 'port', 'comment',
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
]
widgets = {
'nodes': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Nodes')
}),
'admin_user': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Admin user')
}),
'labels': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Labels')
}),
'port': forms.TextInput(),
}
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 ...")
}
class AssetUpdateForm(forms.ModelForm):
class Meta:
model = Asset
fields = [
'hostname', 'ip', 'port', 'nodes', 'is_active', 'platform',
'public_ip', 'number', 'comment', 'admin_user', 'labels',
]
widgets = {
'nodes': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Nodes')
}),
'admin_user': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Admin user')
}),
'labels': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Labels')
}),
'port': forms.TextInput(),
}
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 ...")
}
class AssetBulkUpdateForm(forms.ModelForm):
assets = forms.ModelMultipleChoiceField(
required=True, help_text='* required',
label=_('Select assets'), queryset=Asset.objects.all(),
widget=forms.SelectMultiple(
attrs={
'class': 'select2',
'data-placeholder': _('Select assets')
}
)
)
port = forms.IntegerField(
label=_('Port'), required=False, min_value=1, max_value=65535,
)
admin_user = forms.ModelChoiceField(
required=False, queryset=AdminUser.objects.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'
]
widgets = {
'labels': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Select labels')}
),
'nodes': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Select nodes')}
),
}
def save(self, commit=True):
changed_fields = []
for field in self._meta.fields:
if self.data.get(field) not in [None, '']:
changed_fields.append(field)
cleaned_data = {k: v for k, v in self.cleaned_data.items()
if k in changed_fields}
assets = cleaned_data.pop('assets')
labels = cleaned_data.pop('labels', [])
nodes = cleaned_data.pop('nodes')
assets = Asset.objects.filter(id__in=[asset.id for asset in assets])
assets.update(**cleaned_data)
if labels:
for label in labels:
label.assets.add(*tuple(assets))
if nodes:
for node in nodes:
node.assets.add(*tuple(assets))
return assets

View File

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

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

@@ -0,0 +1,135 @@
# -*- coding: utf-8 -*-
#
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
logger = get_logger(__file__)
__all__ = [
'FileForm', 'SystemUserForm', 'AdminUserForm',
]
class FileForm(forms.Form):
file = forms.FileField()
class PasswordAndKeyAuthForm(forms.ModelForm):
# Form field name can not start with `_`, so redefine it,
password = forms.CharField(
widget=forms.PasswordInput, max_length=128,
strip=True, required=False,
help_text=_('Password or private key passphrase'),
label=_("Password"),
)
# Need use upload private key file except paste private key content
private_key_file = forms.FileField(required=False, label=_("Private key"))
def clean_private_key_file(self):
private_key_file = self.cleaned_data['private_key_file']
password = self.cleaned_data['password']
if private_key_file:
key_string = private_key_file.read()
private_key_file.seek(0)
if not validate_ssh_private_key(key_string, password):
raise forms.ValidationError(_('Invalid private key'))
return private_key_file
def validate_password_key(self):
password = self.cleaned_data['password']
private_key_file = self.cleaned_data.get('private_key_file', '')
if not password and not private_key_file:
raise forms.ValidationError(_(
'Password and private key file must be input one'
))
def gen_keys(self):
password = self.cleaned_data.get('password', '') or None
private_key_file = self.cleaned_data['private_key_file']
public_key = private_key = None
if private_key_file:
private_key = private_key_file.read().strip().decode('utf-8')
public_key = ssh_pubkey_gen(private_key=private_key, password=password)
return private_key, public_key
class AdminUserForm(PasswordAndKeyAuthForm):
def save(self, commit=True):
# Because we define custom field, so we need rewrite :method: `save`
admin_user = super().save(commit=commit)
password = self.cleaned_data.get('password', '') or None
private_key, public_key = super().gen_keys()
admin_user.set_auth(password=password, public_key=public_key, private_key=private_key)
return admin_user
def clean(self):
super().clean()
if not self.instance:
super().validate_password_key()
class Meta:
model = AdminUser
fields = ['name', 'username', 'password', 'private_key_file', 'comment']
widgets = {
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
}
help_texts = {
'name': '* required',
'username': '* required',
}
class SystemUserForm(PasswordAndKeyAuthForm):
# Admin user assets define, let user select, save it in form not in view
auto_generate_key = forms.BooleanField(initial=True, required=False)
def save(self, commit=True):
# Because we define custom field, so we need rewrite :method: `save`
system_user = super().save()
password = self.cleaned_data.get('password', '') or None
auto_generate_key = self.cleaned_data.get('auto_generate_key', False)
private_key, public_key = super().gen_keys()
if auto_generate_key:
logger.info('Auto generate key and set system user auth')
system_user.auto_gen_auth()
else:
system_user.set_auth(password=password, private_key=private_key, public_key=public_key)
return system_user
def clean(self):
super().clean()
auto_generate = self.cleaned_data.get('auto_generate_key')
if not self.instance and not auto_generate:
super().validate_password_key()
class Meta:
model = SystemUser
fields = [
'name', 'username', 'protocol', 'auto_generate_key',
'password', 'private_key_file', 'auto_push', 'sudo',
'comment', 'shell', 'nodes', 'priority',
]
widgets = {
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
'nodes': forms.SelectMultiple(
attrs={
'class': 'select2',
'data-placeholder': _('Nodes')
}
),
}
help_texts = {
'name': '* required',
'username': '* required',
'nodes': _('If auto push checked, system user will be create at node assets'),
'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'),
}

17
apps/assets/hands.py Normal file
View File

@@ -0,0 +1,17 @@
"""
jumpserver.__app__.hands.py
~~~~~~~~~~~~~~~~~
This app depends other apps api, function .. should be import or write mack here.
Other module of this app shouldn't connect with other app.
:copyright: (c) 2014-2018 by Jumpserver Team.
:license: GPL v2, see LICENSE for more details.
"""
from common.mixins import AdminUserRequiredMixin
from common.permissions import IsAppUser, IsSuperUser, IsValidUser, IsSuperUserOrAppUser
from users.models import User, UserGroup
from perms.utils import NodePermissionUtil

View File

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

View File

@@ -0,0 +1,10 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from .user import AdminUser, SystemUser
from .label import Label
from .cluster import *
from .group import *
from .node import *
from .asset import *
from .utils import *

176
apps/assets/models/asset.py Normal file
View File

@@ -0,0 +1,176 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
import uuid
import logging
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.core.cache import cache
from ..const import ASSET_ADMIN_CONN_CACHE_KEY
from .cluster import Cluster
from .group import AssetGroup
from .user import AdminUser, SystemUser
__all__ = ['Asset']
logger = logging.getLogger(__name__)
def default_cluster():
from .cluster import Cluster
name = "Default"
defaults = {"name": name}
cluster, created = Cluster.objects.get_or_create(
defaults=defaults, name=name
)
return cluster.id
def default_node():
try:
from .node import Node
return Node.root()
except:
return None
class Asset(models.Model):
# Important
PLATFORM_CHOICES = (
('Linux', 'Linux'),
('Unix', 'Unix'),
('MacOS', 'MacOS'),
('BSD', 'BSD'),
('Windows', 'Windows'),
('Other', 'Other'),
)
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'))
port = models.IntegerField(default=22, verbose_name=_('Port'))
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
# Auth
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"))
# Some information
public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP'))
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
# Collect
vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor'))
model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model'))
sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number'))
cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model'))
cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count'))
cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores'))
memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory'))
disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total'))
disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info'))
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'))
hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw'))
labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels"))
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
def __str__(self):
return self.hostname
@property
def is_valid(self):
warning = ''
if not self.is_active:
warning += ' inactive'
else:
return True, ''
return False, warning
def is_unixlike(self):
if self.platform not in ("Windows", "Other"):
return True
else:
return False
@property
def hardware_info(self):
if self.cpu_count:
return '{} Core {} {}'.format(
self.cpu_count * self.cpu_cores,
self.memory, self.disk_total
)
else:
return ''
@property
def is_connective(self):
if not self.is_unixlike():
return True
val = cache.get(ASSET_ADMIN_CONN_CACHE_KEY.format(self.hostname))
if val == 1:
return True
else:
return False
def to_json(self):
return {
'id': self.id,
'hostname': self.hostname,
'ip': self.ip,
'port': self.port,
}
def _to_secret_json(self):
"""
Ansible use it create inventory, First using asset user,
otherwise using cluster admin user
Todo: May be move to ops implements it
"""
data = self.to_json()
if self.admin_user:
admin_user = self.admin_user
data.update({
'username': admin_user.username,
'password': admin_user.password,
'private_key': admin_user.private_key_file,
'become': admin_user.become_info,
'groups': [node.value for node in self.nodes.all()],
})
return data
class Meta:
unique_together = ('ip', 'port')
verbose_name = _("Asset")
@classmethod
def generate_fake(cls, count=100):
from random import seed, choice
import forgery_py
from django.db import IntegrityError
seed()
for i in range(count):
asset = cls(ip='%s.%s.%s.%s' % (i, i, i, i),
hostname=forgery_py.internet.user_name(True),
admin_user=choice(AdminUser.objects.all()),
port=22,
created_by='Fake')
try:
asset.save()
asset.system_users = [choice(SystemUser.objects.all()) for i in range(3)]
asset.groups = [choice(AssetGroup.objects.all()) for i in range(3)]
logger.debug('Generate fake asset : %s' % asset.ip)
except IntegrityError:
print('Error continue')
continue

View File

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

View File

@@ -0,0 +1,53 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from __future__ import unicode_literals
import uuid
from django.db import models
import logging
from django.utils.translation import ugettext_lazy as _
__all__ = ['AssetGroup']
logger = logging.getLogger(__name__)
class AssetGroup(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=64, unique=True, verbose_name=_('Name'))
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
def __str__(self):
return self.name
class Meta:
ordering = ['name']
verbose_name = _("Asset group")
@classmethod
def initial(cls):
asset_group = cls(name=_('Default'), comment=_('Default asset group'))
asset_group.save()
@classmethod
def generate_fake(cls, count=100):
from random import seed
import forgery_py
from django.db import IntegrityError
seed()
for i in range(count):
group = cls(name=forgery_py.name.full_name(),
comment=forgery_py.lorem_ipsum.sentence(),
created_by='Fake')
try:
group.save()
logger.debug('Generate fake asset group: %s' % group.name)
except IntegrityError:
print('Error continue')
continue

View File

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

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

@@ -0,0 +1,119 @@
# -*- coding: utf-8 -*-
#
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
__all__ = ['Node']
class Node(models.Model):
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"))
child_mark = models.IntegerField(default=0)
date_create = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.value
@property
def name(self):
return self.value
@property
def full_value(self):
if self == self.__class__.root():
return self.value
else:
return '{}/{}'.format(self.value, self.parent.full_value)
@property
def level(self):
return len(self.key.split(':'))
def get_next_child_key(self):
mark = self.child_mark
self.child_mark += 1
self.save()
return "{}:{}".format(self.key, mark)
def create_child(self, value):
child_key = self.get_next_child_key()
child = self.__class__.objects.create(key=child_key, value=value)
return child
def get_children(self):
return self.__class__.objects.filter(key__regex=r'{}:[0-9]+$'.format(self.key))
def get_all_children(self):
return self.__class__.objects.filter(key__startswith='{}:'.format(self.key))
def get_family(self):
children = list(self.get_all_children())
children.append(self)
return children
def get_assets(self):
from .asset import Asset
assets = Asset.objects.filter(nodes__id=self.id)
return assets
def get_active_assets(self):
return self.get_assets().filter(is_active=True)
def get_all_assets(self):
from .asset import Asset
if self.is_root():
assets = Asset.objects.all()
else:
nodes = self.get_family()
assets = Asset.objects.filter(nodes__in=nodes)
return assets
def get_all_active_assets(self):
return self.get_all_assets().filter(is_active=True)
def is_root(self):
return self.key == '0'
@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])
try:
parent = self.__class__.objects.get(key=parent_key)
except Node.DoesNotExist:
return self.__class__.root()
else:
return parent
@parent.setter
def parent(self, parent):
self.key = parent.get_next_child_key()
@property
def ancestor(self):
if self.parent == self.__class__.root():
return [self.__class__.root()]
else:
return [self.parent, *tuple(self.parent.ancestor)]
@property
def ancestor_with_node(self):
ancestor = self.ancestor
ancestor.insert(0, self)
return ancestor
@classmethod
def root(cls):
obj, created = cls.objects.get_or_create(
key='0', defaults={"key": '0', 'value': "ROOT"}
)
return obj

289
apps/assets/models/user.py Normal file
View File

@@ -0,0 +1,289 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
import os
import logging
import uuid
from hashlib import md5
import sshpubkeys
from django.core.cache import cache
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from common.utils import get_signer, ssh_key_string_to_obj, ssh_key_gen
from .utils import private_key_validator
from ..const import SYSTEM_USER_CONN_CACHE_KEY
__all__ = ['AdminUser', 'SystemUser',]
logger = logging.getLogger(__name__)
signer = get_signer()
class AssetUser(models.Model):
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'))
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
@property
def password(self):
if self._password:
return signer.unsign(self._password)
else:
return None
@password.setter
def password(self, password_raw):
raise AttributeError("Using set_auth do that")
# self._password = signer.sign(password_raw)
@property
def private_key(self):
if self._private_key:
return signer.unsign(self._private_key)
@private_key.setter
def private_key(self, private_key_raw):
raise AttributeError("Using set_auth do that")
# self._private_key = signer.sign(private_key_raw)
@property
def private_key_obj(self):
if self._private_key:
key_str = signer.unsign(self._private_key)
return ssh_key_string_to_obj(key_str, password=self.password)
else:
return None
@property
def private_key_file(self):
if not self.private_key_obj:
return None
project_dir = settings.PROJECT_DIR
tmp_dir = os.path.join(project_dir, 'tmp')
key_str = signer.unsign(self._private_key)
key_name = '.' + md5(key_str.encode('utf-8')).hexdigest()
key_path = os.path.join(tmp_dir, key_name)
if not os.path.exists(key_path):
self.private_key_obj.write_private_key_file(key_path)
os.chmod(key_path, 0o400)
return key_path
@property
def public_key(self):
key = signer.unsign(self._public_key)
if key:
return key
else:
return None
@property
def public_key_obj(self):
if self.public_key:
try:
return sshpubkeys.SSHKey(self.public_key)
except TabError:
pass
return None
def set_auth(self, password=None, private_key=None, public_key=None):
update_fields = []
if password:
self._password = signer.sign(password)
update_fields.append('_password')
if private_key:
self._private_key = signer.sign(private_key)
update_fields.append('_private_key')
if public_key:
self._public_key = signer.sign(public_key)
update_fields.append('_public_key')
if update_fields:
self.save(update_fields=update_fields)
def auto_gen_auth(self):
password = str(uuid.uuid4())
private_key, public_key = ssh_key_gen(
username=self.name, password=password
)
self.set_auth(password=password,
private_key=private_key,
public_key=public_key)
def _to_secret_json(self):
"""Push system user use it"""
return {
'name': self.name,
'username': self.username,
'password': self.password,
'public_key': self.public_key,
'private_key': self.private_key_file,
}
class Meta:
abstract = True
class AdminUser(AssetUser):
"""
A privileged user that ansible can use it to push system user and so on
"""
BECOME_METHOD_CHOICES = (
('sudo', 'sudo'),
('su', 'su'),
)
become = models.BooleanField(default=True)
become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4)
become_user = models.CharField(default='root', max_length=64)
_become_pass = models.CharField(default='', max_length=128)
def __str__(self):
return self.name
@property
def become_pass(self):
password = signer.unsign(self._become_pass)
if password:
return password
else:
return ""
@become_pass.setter
def become_pass(self, password):
self._become_pass = signer.sign(password)
@property
def become_info(self):
if self.become:
info = {
"method": self.become_method,
"user": self.become_user,
"pass": self.become_pass,
}
else:
info = None
return info
def get_related_assets(self):
assets = self.asset_set.all()
return assets
@property
def assets_amount(self):
return self.get_related_assets().count()
class Meta:
ordering = ['name']
verbose_name = _("Admin user")
@classmethod
def generate_fake(cls, count=10):
from random import seed
import forgery_py
from django.db import IntegrityError
seed()
for i in range(count):
obj = cls(name=forgery_py.name.full_name(),
username=forgery_py.internet.user_name(),
password=forgery_py.lorem_ipsum.word(),
comment=forgery_py.lorem_ipsum.sentence(),
created_by='Fake')
try:
obj.save()
logger.debug('Generate fake asset group: %s' % obj.name)
except IntegrityError:
print('Error continue')
continue
class SystemUser(AssetUser):
SSH_PROTOCOL = 'ssh'
RDP_PROTOCOL = 'rdp'
PROTOCOL_CHOICES = (
(SSH_PROTOCOL, 'ssh'),
(RDP_PROTOCOL, 'rdp'),
)
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
priority = models.IntegerField(default=10, verbose_name=_("Priority"))
protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
sudo = models.TextField(default='/sbin/ifconfig', verbose_name=_('Sudo'))
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
def __str__(self):
return self.name
def to_json(self):
return {
'id': self.id,
'name': self.name,
'username': self.username,
'protocol': self.protocol,
'priority': self.priority,
'auto_push': self.auto_push,
}
@property
def assets(self):
assets = set()
for node in self.nodes.all():
assets.update(set(node.get_all_assets()))
return assets
@property
def assets_connective(self):
_result = cache.get(SYSTEM_USER_CONN_CACHE_KEY.format(self.name), {})
return _result
@property
def unreachable_assets(self):
return list(self.assets_connective.get('dark', {}).keys())
@property
def reachable_assets(self):
return self.assets_connective.get('contacted', [])
def is_need_push(self):
if self.auto_push and self.protocol == self.__class__.SSH_PROTOCOL:
return True
else:
return False
class Meta:
ordering = ['name']
verbose_name = _("System user")
@classmethod
def generate_fake(cls, count=10):
from random import seed
import forgery_py
from django.db import IntegrityError
seed()
for i in range(count):
obj = cls(name=forgery_py.name.full_name(),
username=forgery_py.internet.user_name(),
password=forgery_py.lorem_ipsum.word(),
comment=forgery_py.lorem_ipsum.sentence(),
created_by='Fake')
try:
obj.save()
logger.debug('Generate fake asset group: %s' % obj.name)
except IntegrityError:
print('Error continue')
continue

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
#
from rest_framework import serializers
from common.mixins import BulkSerializerMixin
from ..models import Asset, Cluster
class ClusterUpdateAssetsSerializer(serializers.ModelSerializer):
"""
集群更新资产数据结构
"""
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
class Meta:
model = Cluster
fields = ['id', 'assets']
class ClusterSerializer(BulkSerializerMixin, serializers.ModelSerializer):
"""
cluster
"""
assets_amount = serializers.SerializerMethodField()
admin_user_name = serializers.SerializerMethodField()
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
system_users = serializers.SerializerMethodField()
class Meta:
model = Cluster
fields = '__all__'
@staticmethod
def get_assets_amount(obj):
return obj.assets.count()
@staticmethod
def get_admin_user_name(obj):
try:
return obj.admin_user.name
except AttributeError:
return ''
@staticmethod
def get_system_users(obj):
return ', '.join(obj.name for obj in obj.systemuser_set.all())

View File

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

View File

@@ -0,0 +1,72 @@
# -*- 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()
class Meta:
model = Node
fields = [
'id', 'key', 'name', 'value', 'parent',
'assets_granted', 'assets_amount',
]
@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
class NodeAssetsSerializer(serializers.ModelSerializer):
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
class Meta:
model = Node
fields = ['assets']
class NodeAddChildrenSerializer(serializers.Serializer):
nodes = serializers.ListField()

View File

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

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

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

View File

@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
#
from django.db.models.signals import post_save, m2m_changed
from django.dispatch import receiver
from common.utils import get_logger
from .models import Asset, SystemUser, Node
from .tasks import update_assets_hardware_info_util, \
test_asset_connectability_util, push_system_user_to_node, \
push_node_system_users_to_asset
logger = get_logger(__file__)
def update_asset_hardware_info_on_created(asset):
logger.debug("Update asset `{}` hardware info".format(asset))
update_assets_hardware_info_util.delay([asset])
def test_asset_conn_on_created(asset):
logger.debug("Test asset `{}` connectability".format(asset))
test_asset_connectability_util.delay([asset])
def set_asset_root_node(asset):
logger.debug("Set asset default node: {}".format(Node.root()))
asset.nodes.add(Node.root())
@receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier")
def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
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)
@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)
@receiver(m2m_changed, sender=SystemUser.nodes.through)
def on_system_user_node_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)
@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])
@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")
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
push_node_system_users_to_asset(instance, assets)

440
apps/assets/tasks.py Normal file
View File

@@ -0,0 +1,440 @@
# ~*~ coding: utf-8 ~*~
import json
import re
import os
from celery import shared_task
from django.core.cache import cache
from django.utils.translation import ugettext as _
from common.utils import get_object_or_none, capacity_convert, \
sum_capacity, encrypt_password, get_logger
from common.celery import register_as_period_task, after_app_shutdown_clean, \
after_app_ready_start, app as celery_app
from .models import SystemUser, AdminUser, Asset, Cluster
from . import const
FORKS = 10
TIMEOUT = 60
logger = get_logger(__file__)
CACHE_MAX_TIME = 60*60*60
disk_pattern = re.compile(r'^hd|sd|xvd|vd')
PERIOD_TASK = os.environ.get("PERIOD_TASK", "on")
@shared_task
def set_assets_hardware_info(result, **kwargs):
"""
Using ops task run result, to update asset info
@shared_task must be exit, because we using it as a task callback, is must
be a celery task also
:param result:
:param kwargs: {task_name: ""}
:return:
"""
result_raw = result[0]
assets_updated = []
for hostname, info in result_raw.get('ok', {}).items():
info = info.get('setup', {}).get('ansible_facts', {})
if not info:
logger.error("Get asset info failed: {}".format(hostname))
continue
asset = get_object_or_none(Asset, hostname=hostname)
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')
for ___cpu_model in info.get('ansible_processor', []):
if ___cpu_model.endswith('GHz') or ___cpu_model.startswith("Intel"):
break
else:
___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')))
disk_info = {}
for dev, dev_info in info.get('ansible_devices', {}).items():
if disk_pattern.match(dev) and dev_info['removable'] == '0':
disk_info[dev] = dev_info['size']
___disk_total = '%s %s' % sum_capacity(disk_info.values())
___disk_info = json.dumps(disk_info)
___platform = info.get('ansible_system', 'Unknown')
___os = info.get('ansible_distribution', 'Unknown')
___os_version = info.get('ansible_distribution_version', 'Unknown')
___os_arch = info.get('ansible_architecture', 'Unknown')
___hostname_raw = info.get('ansible_hostname', 'Unknown')
for k, v in locals().items():
if k.startswith('___'):
setattr(asset, k.strip('_'), v)
asset.save()
assets_updated.append(asset)
return assets_updated
@shared_task
def update_assets_hardware_info_util(assets, task_name=None):
"""
Using ansible api to update asset hardware info
:param assets: asset seq
:param task_name: task_name running
:return: result summary ['contacted': {}, 'dark': {}]
"""
from ops.utils import update_or_create_ansible_task
if task_name is None:
# task_name = _("Update some assets hardware info")
task_name = _("更新资产硬件信息")
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
hostname_list = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()]
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',
)
result = task.run()
# Todo: may be somewhere using
# Manual run callback function
set_assets_hardware_info(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)
@celery_app.task
@register_as_period_task(interval=3600)
@after_app_ready_start
@after_app_shutdown_clean
def update_assets_hardware_info_period():
"""
Update asset hardware period task
:return:
"""
if PERIOD_TASK != "on":
logger.debug("Period task disabled, update assets hardware info pass")
return
from ops.utils import update_or_create_ansible_task
# task_name = _("Update assets hardware info period")
task_name = _("定期更新资产硬件信息")
hostname_list = [
asset.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.delay(admin_user, task_name)
@shared_task
def test_asset_connectability_util(assets, task_name=None):
from ops.utils import update_or_create_ansible_task
if task_name is None:
# task_name = _("Test assets connectability")
task_name = _("测试资产可连接性")
hosts = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()]
if not hosts:
logger.info("No hosts, passed")
return {}
tasks = const.TEST_ADMIN_USER_CONN_TASKS
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
options=const.TASK_OPTIONS, run_as_admin=True, created_by='System',
)
result = task.run()
summary = result[1]
for k in summary.get('dark'):
cache.set(const.ASSET_ADMIN_CONN_CACHE_KEY.format(k), 0, CACHE_MAX_TIME)
for k in summary.get('contacted'):
cache.set(const.ASSET_ADMIN_CONN_CACHE_KEY.format(k), 1, CACHE_MAX_TIME)
return summary
@shared_task
def test_asset_connectability_manual(asset):
summary = test_asset_connectability_util([asset])
if summary.get('dark'):
return False, summary['dark']
else:
return True, ""
## System user connective ##
@shared_task
def set_system_user_connectablity_info(result, **kwargs):
summary = result[1]
task_name = kwargs.get("task_name")
system_user = kwargs.get("system_user")
if system_user is None:
system_user = task_name.split(":")[-1]
cache_key = const.SYSTEM_USER_CONN_CACHE_KEY.format(system_user)
cache.set(cache_key, summary, CACHE_MAX_TIME)
@shared_task
def test_system_user_connectability_util(system_user, task_name):
"""
Test system cant connect his assets or not.
:param system_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
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)
@shared_task
@register_as_period_task(interval=3600)
@after_app_ready_start
@after_app_shutdown_clean
def test_system_user_connectability_period():
if PERIOD_TASK != "on":
logger.debug("Period task disabled, test system user connectability pass")
return
system_users = SystemUser.objects.all()
for system_user in system_users:
# task_name = _("Test system user connectability period: {}".format(system_user))
task_name = _("定期测试系统用户可连接性: {}".format(system_user))
test_system_user_connectability_util(system_user, task_name)
#### Push system user tasks ####
def get_push_system_user_tasks(system_user):
# Set root as system user is dangerous
if system_user.username == "root":
return []
tasks = []
if system_user.password:
tasks.append({
'name': 'Add user {}'.format(system_user.username),
'action': {
'module': 'user',
'args': 'name={} shell={} state=present password={}'.format(
system_user.username, system_user.shell,
encrypt_password(system_user.password, salt="K3mIlKK"),
),
}
})
if system_user.public_key:
tasks.append({
'name': 'Set {} authorized key'.format(system_user.username),
'action': {
'module': 'authorized_key',
'args': "user={} state=present key='{}'".format(
system_user.username, system_user.public_key
)
}
})
if system_user.sudo:
tasks.append({
'name': 'Set {} sudo setting'.format(system_user.username),
'action': {
'module': 'lineinfile',
'args': "dest=/etc/sudoers state=present regexp='^{0} ALL=' "
"line='{0} ALL=(ALL) NOPASSWD: {1}' "
"validate='visudo -cf %s'".format(
system_user.username,
system_user.sudo,
)
}
})
return tasks
@shared_task
def push_system_user_util(system_users, assets, task_name):
from ops.utils import update_or_create_ansible_task
tasks = []
for system_user in system_users:
if not system_user.is_need_push():
msg = "push system user `{}` passed, may be not auto push or ssh " \
"protocol is not ssh".format(system_user.name)
logger.info(msg)
continue
tasks.extend(get_push_system_user_tasks(system_user))
if not tasks:
logger.info("Not tasks, passed")
return {}
hosts = [asset.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
)
def push_system_user_to_node(system_user, node):
assets = node.get_all_assets()
task_name = get_node_push_system_user_task_name(system_user, node)
push_system_user_util.delay([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)
logger.info(msg)
return
nodes = system_user.nodes.all()
for node in nodes:
push_system_user_to_node(system_user, node)
@shared_task
def push_system_user_to_assets_manual(system_user):
push_system_user_related_nodes(system_user)
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()))
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
# @register_as_period_task(interval=3600)
# @after_app_ready_start
# # @after_app_shutdown_clean
# def push_system_user_period():
# for system_user in SystemUser.objects.all():
# push_system_user_related_nodes(system_user)

View File

@@ -0,0 +1,41 @@
{% extends '_modal.html' %}
{% load i18n %}
{% block modal_id %}asset_group_bulk_update_modal{% endblock %}
{% block modal_class %}modal-lg{% endblock %}
{% block modal_title%}{% trans "Update asset group" %}{% endblock %}
{% block modal_body %}
{% load bootstrap3 %}
<p class="text-success text-center">{% trans "Hint: only change the field you want to update." %}</p>
<form method="post" class="form-horizontal" action="" id="fm_asset_group_bulk_update">
<div class="form-group">
<label for="assets" class="col-sm-2 control-label">{% trans 'Assets' %}</label>
<div class="col-sm-9" id="select2-container">
<select name="assets" id="select2_groups" data-placeholder="{% trans 'Select Asset' %}" class="select2 form-control m-b" multiple>
{% for asset in assets %}
<option value="{{ asset.id }}">{{ asset.ip }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<label for="system_users" class="col-sm-2 control-label">{% trans 'System users' %}</label>
<div class="col-sm-9" id="select2-container">
<select name="system_users" id="select2_groups" data-placeholder="{% trans 'Select System Users' %}" class="select2 form-control m-b" multiple>
{% for system_user in system_users %}
<option value="{{ system_user.id }}">{{ system_user.name }}</option>
{% endfor %}
</select>
</div>
</div>
<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>
</div>
</div>
</div>
</form>
{% endblock %}
{% block modal_confirm_id %}btn_asset_group_bulk_update{% endblock %}

View File

@@ -0,0 +1,29 @@
{% extends '_modal.html' %}
{% load i18n %}
{% block modal_id %}asset_import_modal{% endblock %}
{% block modal_title%}{% trans "Import asset" %}{% endblock %}
{% block modal_body %}
<form method="post" action="{% url 'assets:asset-import' %}" id="fm_asset_import" enctype="multipart/form-data">
{% csrf_token %}
<div class="form-group">
<label class="control-label" for="id_assets">{% trans "Template" %}</label>
<a href="{% url 'assets:asset-export' %}" style="display: block">{% trans 'Download' %}</a>
</div>
<div class="form-group">
<label class="control-label" for="id_users">{% trans "Asset csv file" %}</label>
<input id="id_assets" type="file" name="file" />
<span class="help-block red-fonts">
{% trans 'If set id, will use this id update asset existed' %}
</span>
</div>
</form>
<p>
<p class="text-success" id="id_created"></p>
<p id="id_created_detail"></p>
<p class="text-warning" id="id_updated"></p>
<p id="id_updated_detail"></p>
<p class="text-danger" id="id_failed"></p>
<p id="id_failed_detail"></p>
</p>
{% endblock %}
{% block modal_confirm_id %}btn_asset_import{% endblock %}

View File

@@ -0,0 +1,132 @@
{% extends '_modal.html' %}
{% load i18n %}
{% block modal_class %}modal-lg{% endblock %}
{% block modal_id %}asset_list_modal{% endblock %}
{#{% block modal_title%}{% trans "Please select assets" %}{% 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>
</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 {
return
}
var data = {
'assets': id_list
};
var success = function () {
modal_table.ajax.reload()
};
APIUpdateAttr({
'url': '/api/assets/v1/nodes/' + current_node.id + '/assets/add/',
'method': 'PUT',
'body': JSON.stringify(data),
'success': success
})
})
</script>
{% endblock %}
{% block modal_confirm_id %}btn_select_assets{% endblock %}

View File

@@ -0,0 +1,121 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% load bootstrap3 %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5>{{ action }}</h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<form enctype="multipart/form-data" method="post" class="form-horizontal" action="" >
{% csrf_token %}
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
<h3>{% trans 'Basic' %}</h3>
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.username layout="horizontal" %}
{% bootstrap_field form.priority layout="horizontal" %}
{% bootstrap_field form.protocol layout="horizontal" %}
{% 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>
<div class="col-sm-8">
{{ form.auto_generate_key}}
</div>
</div>
</div>
<div class="auth-fields">
{% bootstrap_field form.password layout="horizontal" %}
{% bootstrap_field form.private_key_file layout="horizontal" %}
</div>
<div class="form-group">
<label for="{{ form.as_push.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto push' %}</label>
<div class="col-sm-8">
{{ form.auto_push}}
</div>
</div>
{% endblock %}
<h3>{% trans 'Other' %}</h3>
{% bootstrap_field form.sudo layout="horizontal" %}
{% bootstrap_field form.shell layout="horizontal" %}
{% bootstrap_field form.comment layout="horizontal" %}
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-white" type="reset">{% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
var 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 }}';
var need_change_field = [auto_generate_key, private_key_id, sudo_id, shell_id] ;
function authFieldsDisplay() {
if ($(auto_generate_key).prop('checked')) {
$('.auth-fields').addClass('hidden');
} else {
$('.auth-fields').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();
});
})
</script>
{% endblock %}

View File

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

View File

@@ -0,0 +1,62 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% load bootstrap3 %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5>{{ action }}</h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
<form enctype="multipart/form-data" method="post" class="form-horizontal" action="" >
{% csrf_token %}
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.username layout="horizontal" %}
{% bootstrap_field form.password layout="horizontal" %}
{% bootstrap_field form.private_key_file layout="horizontal" %}
{% bootstrap_field form.comment layout="horizontal" %}
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-white" type="reset">{% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function () {
$('.select2').select2();
})
</script>
{% endblock %}

View File

@@ -0,0 +1,171 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li class="active">
<a href="{% url 'assets:admin-user-detail' pk=admin_user.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
</li>
<li>
<a href="{% url 'assets:admin-user-assets' pk=admin_user.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Assets list' %} </a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'assets:admin-user-update' pk=admin_user.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-danger btn-delete-admin-user">
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-8" style="padding-left: 0;">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label"><b>{{ admin_user.name }}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table">
<tbody>
<tr class="no-borders-tr">
<td>{% trans 'Name' %}:</td>
<td><b>{{ admin_user.name }}</b></td>
</tr>
<tr>
<td>{% trans 'Username' %}:</td>
<td><b>{{ admin_user.username }}</b></td>
</tr>
<tr>
<td>{% trans 'Date created' %}:</td>
<td><b>{{ admin_user.date_created }}</b></td>
</tr>
<tr>
<td>{% trans 'Created by' %}:</td>
<td><b>{{ asset_group.created_by }}</b></td>
</tr>
<tr>
<td>{% trans 'Comment' %}:</td>
<td><b>{{ admin_user.comment }}</b></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Replace node assets admin user with this' %}
</div>
<div class="panel-body">
<table class="table group_edit" id="table-clusters">
<tbody>
<form>
<tr>
<td colspan="2" class="no-borders">
<select data-placeholder="{% trans '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>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td colspan="2" class="no-borders">
<button type="button" class="btn btn-primary btn-sm" id="btn-change-admin-user">{% trans 'Confirm' %}</button>
</td>
</tr>
</form>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
function replaceNodeAssetsAdminUser(nodes) {
var the_url = "{% url 'api-assets:replace-nodes-admin-user' pk=admin_user.id %}";
var body = {
nodes: nodes
};
var success = function(data) {
// remove all the selected groups from select > option and rendered ul element;
$('.select2-selection__rendered').empty();
$('#nodes_selected').val('');
$.map(jumpserver.nodes_selected, function(value, index) {
$('#opt_' + index).remove();
});
// clear jumpserver.groups_selected
jumpserver.nodes_selected = {};
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
success: success
});
}
jumpserver.nodes_selected = {};
$(document).ready(function () {
$('.select2').select2()
.on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.nodes_selected[data.id] = data.text;
}).on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.nodes_selected[data.id]
});
})
.on('click', '.btn-delete-admin-user', function () {
var $this = $(this);
var name = "{{ admin_user.name }}";
var uid = "{{ admin_user.id }}";
var the_url = '{% url "api-assets:admin-user-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
var redirect_url = "{% url 'assets:admin-user-list' %}";
objectDelete($this, name, the_url, redirect_url);
})
.on('click', '#btn-change-admin-user', function () {
if (Object.keys(jumpserver.nodes_selected).length === 0) {
return false;
}
var nodes = [];
$.map(jumpserver.nodes_selected, function(value, index) {
nodes.push(index);
});
replaceNodeAssetsAdminUser(nodes);
})
</script>
{% endblock %}

View File

@@ -0,0 +1,112 @@
{% extends '_base_list.html' %}
{% load i18n static %}
{% block table_search %}
{% endblock %}
{% block help_message %}
<div class="alert alert-info help-message">
管理用户是服务器的root或拥有 NOPASSWD: ALL sudo权限的用户Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。
Windows或其它硬件可以随意设置一个
</div>
{% endblock %}
{% block table_container %}
<div class="uc pull-left m-r-5">
<a href="{% url "assets:admin-user-create" %}" class="btn btn-sm btn-primary"> {% trans "Create admin user" %} </a>
</div>
<table class="table table-striped table-bordered table-hover " id="admin_user_list_table" >
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Username' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'Reachable' %}</th>
<th class="text-center">{% trans 'Unreachable' %}</th>
<th class="text-center">{% trans 'Ratio' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function(){
var options = {
ele: $('#admin_user_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:admin-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 4, createdCell: function (td, cellData) {
var innerHtml = "";
if (cellData !== 0) {
innerHtml = "<span class='text-navy'>" + cellData + "</span>";
} else {
innerHtml = "<span>" + cellData + "</span>";
}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData +'">' + innerHtml + '</span>');
}},
{targets: 5, createdCell: function (td, cellData) {
var innerHtml = "";
if (cellData !== 0) {
innerHtml = "<span class='text-danger'>" + cellData + "</span>";
} else {
innerHtml = "<span>" + cellData + "</span>";
}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}},
{targets: 6, createdCell: function (td, cellData, rowData) {
var val = 0;
var innerHtml = "";
var total = rowData.assets_amount;
var reachable = rowData.reachable_amount;
if (total !== 0) {
val = reachable/total * 100;
}
if (val === 100) {
innerHtml = "<span class='text-navy'>" + val + "% </span>";
} else {
var num = new Number(val);
innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>";
}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}},
{targets: 8, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:admin-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
}}],
ajax_url: '{% url "api-assets:admin-user-list" %}',
columns: [{data: function(){return ""}}, {data: "name" }, {data: "username" }, {data: "assets_amount" },
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" }]
};
jumpserver.initDataTable(options);
})
.on('click', '.btn_admin_user_delete', function () {
var $this = $(this);
var $data_table = $("#admin_user_list_table").DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var the_url = '{% url "api-assets:admin-user-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
objectDelete($this, name, the_url);
setTimeout( function () {
$data_table.ajax.reload();
}, 3000);
});
</script>
{% endblock %}

View File

@@ -0,0 +1,69 @@
{% extends '_base_create_update.html' %}
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% block form %}
<div class="ydxbd" id="formlists" style="display: block;">
<p id="tags_p" class="mgl-5 c02">选择需要修改属性</p>
<div class="tagBtnList">
<a class="label label-primary" id="change_all" value="1">全选</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>
{% endif %}
{% endfor %}
</div>
</div>
<form method="post" class="form-horizontal" id="add_form">
{% csrf_token %}
{% bootstrap_form form layout="horizontal" %}
<div class="form-group abc">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-white" type="reset">{% trans 'Reset' %}</button>
<button class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function () {
$('.select2').select2();
}).on('click', '.field-tag', function() {
changeField(this);
}).on('click', '#change_all', function () {
var tag_fields = $('.field-tag');
var $this = $(this);
var active = '1';
if ($this.attr('value') == '0'){
active = '0';
$this.attr('value', '1').addClass('label-primary')
} else {
active = '1';
$this.attr('value', '0').removeClass('label-primary')
}
$.each(tag_fields, function (k, v) {
changeField(v, active)
})
});
function changeField(obj, active) {
var $this = $(obj);
var field_id = $this.data('id');
if (!active) {
active = $this.attr('value');
}
if (active == '0') {
$this.attr('value', '1').addClass('label-primary');
var form_groups = $('#add_form .form-group:not(.abc)');
form_groups.filter(':has(#' + field_id + ')').show().find('select,input').prop('disabled', false)
} else {
$this.attr('value', '0').removeClass('label-primary');
var form_groups = $('#add_form .form-group:not(.abc)');
form_groups.filter(':has(#' + field_id + ')').hide().find('select,input').prop('disabled', true)
}
}
</script>
{% endblock %}

View File

@@ -0,0 +1,89 @@
{% extends '_base_create_update.html' %}
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% load asset_tags %}
{% load common_tags %}
{% block form %}
<form action="" method="post" class="form-horizontal">
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
{% csrf_token %}
<h3>{% trans 'Basic' %}</h3>
{% bootstrap_field form.hostname layout="horizontal" %}
{% bootstrap_field form.ip layout="horizontal" %}
{% bootstrap_field form.port layout="horizontal" %}
{% bootstrap_field form.platform layout="horizontal" %}
{% bootstrap_field form.public_ip layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Auth' %}</h3>
{% bootstrap_field form.admin_user layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Node' %}</h3>
{% bootstrap_field form.nodes layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Labels' %}</h3>
<div class="form-group {% if form.errors.labels %} has-error {% endif %}">
<label for="{{ form.labels.id_for_label }}" class="col-md-2 control-label">{% trans 'Label' %}</label>
<div class="col-md-9">
<select name="labels" class="select2 labels" data-placeholder="{% trans 'Select labels' %}" style="width: 100%" multiple="" tabindex="4" id="{{ form.labels.id_for_label }}">
{% for name, labels in form.labels.field.queryset|group_labels %}
<optgroup label="{{ name }}">
{% for label in labels %}
{% if label in form.labels.initial %}
<option value="{{ label.id }}" selected>{{ label.value }}</option>
{% else %}
<option value="{{ label.id }}">{{ label.value }}</option>
{% endif %}
{% endfor %}
</optgroup>
{% endfor %}
</select>
{% if form.errors.labels %}
{% for e in form.errors.labels %}
<div class="help-block">{{ e }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="hr-line-dashed"></div>
<h3>{% trans 'Other' %}</h3>
{% bootstrap_field form.comment layout="horizontal" %}
{% bootstrap_field form.is_active layout="horizontal" %}
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<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 %}
{% block custom_foot_js %}
<script>
function format(item) {
var group = item.element.parentElement.label;
return group + ':' + item.text;
}
$(document).ready(function () {
$('.select2').select2({
allowClear: true
});
$(".labels").select2({
allowClear: true,
templateSelection: format
});
})
</script>
{% endblock %}

View File

@@ -0,0 +1,364 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href='{% static "css/plugins/select2/select2.min.css" %}' rel="stylesheet">
<link href='{% static "css/plugins/sweetalert/sweetalert.css" %}' rel="stylesheet">
<script src='{% static "js/plugins/select2/select2.full.min.js" %}'></script>
<script src='{% static "js/plugins/sweetalert/sweetalert.min.js" %}'></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li class="active">
<a href="{% url 'assets:asset-detail' pk=asset.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Asset detail' %} </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>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-danger btn-delete-asset">
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
</a>
</li>
{% endif %}
</ul>
</div>
<div class="tab-content">
<div class="col-sm-7" style="padding-left: 0">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label"><b>{{ 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">
<tbody>
<tr class="no-borders-tr">
<td width="20%">{% trans 'Hostname' %}:</td>
<td><b>{{ asset.hostname }}</b></td>
</tr>
<tr>
<td>{% trans 'IP' %}:</td>
<td><b>{{ asset.ip }}</b></td>
</tr>
<tr>
<td>{% trans 'Public IP' %}:</td>
<td><b>{{ asset.public_ip|default:"" }}</b></td>
</tr>
<tr>
<td>{% trans 'Port' %}:</td>
<td><b>{{ asset.port }}</b></td>
</tr>
<tr>
<td>{% trans 'Admin user' %}:</td>
<td><b>{{ asset.admin_user }}</b></td>
</tr>
<tr>
<td>{% trans 'Vendor' %}:</td>
<td><b>{{ asset.vendor|default:"" }}</b></td>
</tr>
<tr>
<td>{% trans 'Model' %}:</td>
<td><b>{{ asset.model|default:"" }}</b></td>
</tr>
<tr>
<td>{% trans 'CPU' %}:</td>
<td><b>{{ asset.cpu_model|default:"" }} {{ asset.cpu_count|default:"" }}*{{ asset.cpu_cores|default:"" }}</b></td>
</tr>
<tr>
<td>{% trans 'Memory' %}:</td>
<td><b>{{ asset.memory|default:"" }}</b></td>
</tr>
<tr>
<td>{% trans 'Disk' %}:</td>
<td><b>{{ asset.disk_total|default:"" }}</b></td>
</tr>
<tr>
<td>{% trans 'Platform' %}:</td>
<td><b>{{ asset.platform|default:"" }}</b></td>
</tr>
<tr>
<td>{% trans 'OS' %}:</td>
<td><b>{{ asset.os|default:"" }} {{ asset.os_version|default:"" }} {{ asset.os_arch|default:"" }}</b></td>
</tr>
<tr>
<td>{% trans 'Is active' %}:</td>
<td><b>{{ asset.is_active|yesno:"Yes,No" }}</b></td>
</tr>
<tr>
<td>{% trans 'Serial number' %}:</td>
<td><b>{{ asset.sn|default:"" }}</b></td>
</tr>
<tr>
<td>{% trans 'Asset number' %}:</td>
<td><b>{{ asset.number|default:"" }}</b></td>
</tr>
<tr>
<td>{% trans 'Created by' %}:</td>
<td><b>{{ asset.created_by }}</b></td>
</tr>
<tr>
<td>{% trans 'Date joined' %}:</td>
<td><b>{{ asset.date_joined|date:"Y-m-j H:i:s" }}</b></td>
</tr>
<tr>
<td>{% trans 'Comment' %}:</td>
<td><b>{{ asset.comment }}</b></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
{% if user.is_superuser %}
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Quick modify' %}
</div>
<div class="panel-body">
<table class="table">
<tbody>
<tr class="no-borders-tr">
<td width="50%">{% trans 'Active' %}:</td>
<td>
<span class="pull-right">
<div class="switch">
<div class="onoffswitch">
<input type="checkbox" {% if asset.is_active %} checked {% endif %} class="onoffswitch-checkbox" id="is_active">
<label class="onoffswitch-label" for="is_active">
<span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
</label>
</div>
</div>
</span>
</td>
</tr>
{% if asset.is_unixlike %}
<tr>
<td>{% trans 'Refresh hardware' %}:</td>
<td>
<span class="pull-right">
<button type="button" class="btn btn-primary btn-xs" id="btn_refresh_asset" style="width: 54px">{% trans 'Refresh' %}</button>
</span>
</td>
</tr>
<tr>
<td>{% trans 'Test connective' %}:</td>
<td>
<span class="pull-right">
<button type="button" class="btn btn-primary btn-xs" id="btn-test-is-alive" style="width: 54px">{% trans 'Test' %}</button>
</span>
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Nodes' %}
</div>
<div class="panel-body">
<table class="table group_edit" id="add-asset2group">
<tbody>
<form>
<tr>
<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>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td colspan="2" class="no-borders">
<button type="button" class="btn btn-info btn-sm" id="btn-update-nodes">{% trans 'Confirm' %}</button>
</td>
</tr>
</form>
{% for node in asset.nodes.all %}
<tr>
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node.name }}</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>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Labels' %}
</div>
<div class="panel-body">
<ul class="tag-list" style="padding: 0">
{% for label in asset.labels.all %}
<li ><a href=""><i class="fa fa-tag"></i> {{ label.name }}:{{ label.value }}</a></li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
jumpserver.nodes_selected = {};
function updateAssetNodes(nodes) {
var the_url = "{% url 'api-assets:asset-detail' pk=asset.id %}";
var body = {
nodes: Object.assign([], nodes)
};
var success = function(data) {
// remove all the selected groups from select > option and rendered ul element;
$('.select2-selection__rendered').empty();
$('#groups_selected').val('');
$.map(jumpserver.nodes_selected, function(group_name, index) {
$('#opt_' + index).remove();
// change tr html of user groups.
$('#add-asset2group tbody').append(
'<tr>' +
'<td><b class="bdg_node" data-gid="' + index + '">' + group_name + '</b></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn-leave-node" type="button"><i class="fa fa-minus"></i></button></td>' +
'</tr>'
)
});
// clear jumpserver.groups_selected
jumpserver.nodes_selected = {};
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
success: success
});
}
function refreshAssetHardware() {
var the_url = "{% url 'api-assets:asset-refresh' pk=asset.id %}";
var success = function (data) {
location.reload();
};
var error = function (data) {
alert(data)
};
APIUpdateAttr({
url: the_url,
success: success,
error: error,
method: 'GET'
});
}
$(document).ready(function () {
$('.select2.groups').select2().on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.nodes_selected[data.id] = data.text;
}).on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.nodes_selected[data.id]
});
}).on('click', '#is_active', function () {
var the_url = '{% url "api-assets:asset-detail" pk=asset.id %}';
var checked = $(this).prop('checked');
var body = {
'is_active': checked
};
var success = '{% trans "Update successfully!" %}';
var status = $(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").text();
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
success_message: success
});
if (status === "False") {
$(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").html('True');
}else{
$(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").html('False');
}
}).on('click', '#btn-update-nodes', function () {
if (Object.keys(jumpserver.nodes_selected).length === 0) {
return false;
}
var nodes = $('.bdg_node').map(function() {
return $(this).data('gid');
}).get();
$.map(jumpserver.nodes_selected, function(value, index) {
nodes.push(index);
$('#opt_' + index).remove();
});
updateAssetNodes(nodes)
}).on('click', '.btn-leave-node', function() {
var $this = $(this);
var $tr = $this.closest('tr');
var $badge = $tr.find('.bdg_node');
var gid = $badge.data('gid');
var group_name = $badge.html() || $badge.text();
$('#groups_selected').append(
'<option value="' + gid + '" id="opt_' + gid + '">' + group_name + '</option>'
);
$tr.remove();
var groups = $('.bdg_node').map(function () {
return $(this).data('gid');
}).get();
updateAssetNodes(groups)
}).on('click', '.btn-delete-asset', function () {
var $this = $(this);
var name = "{{ asset.hostname }}";
var uid = "{{ asset.id }}";
var the_url = '{% url "api-assets:asset-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
var redirect_url = "{% url 'assets:asset-list' %}";
objectDelete($this, name, the_url, redirect_url);
}).on('click', '#btn_refresh_asset', function () {
alert('关闭alert, 等待完成, 自动刷新页面');
refreshAssetHardware()
}).on('click', '#btn-test-is-alive', function () {
var the_url = "{% url 'api-assets:asset-alive-test' pk=asset.id %}";
var error = function (data) {
alert(data)
};
alert('关闭alert, 等待完成');
APIUpdateAttr({
url: the_url,
error: error,
method: 'GET',
success_message: "{% trans "Reachable" %}"
});
})
</script>
{% endblock %}

View File

@@ -0,0 +1,661 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block help_message %}
<div class="alert alert-info help-message">
左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的,右侧是属于该节点下的资产
</div>
{% endblock %}
{% block custom_head_css_js %}
<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 type="text/css">
div#rMenu {
position:absolute;
visibility:hidden;
text-align: left;
top: 100%;
left: 0;
z-index: 1000;
float: left;
padding: 5px 0;
margin: 2px 0 0;
list-style: none;
background-clip: padding-box;
}
div#rMenu li{
margin: 1px 0;
cursor: pointer;
{#list-style: none outside none;#}
}
.dropdown a:hover {
background-color: #f1f1f1
}
</style>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content">
<div class="row">
<div class="col-lg-3" id="split-left">
<div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0">
<div class="file-manager ">
<div id="assetTree" class="ztree">
</div>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div class="col-lg-9 animated fadeInRight" id="split-right">
<div class="tree-toggle">
<div class="btn btn-sm btn-primary tree-toggle-btn" onclick="toggle()">
<i class="fa fa-angle-left fa-x" id="toggle-icon"></i>
</div>
</div>
<div class="mail-box-header">
<div class="uc pull-left m-r-5"><a class="btn btn-sm btn-primary btn-create-asset"> {% trans "Create asset" %} </a></div>
<div class="html5buttons">
<div class="dt-buttons btn-group">
<a class="btn btn-default btn_import" data-toggle="modal" data-target="#asset_import_modal" tabindex="0">
<span>{% trans "Import" %}</span>
</a>
<a class="btn btn-default btn_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a>
</div>
</div>
<div class="btn-group" style="float: right">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>
<ul class="dropdown-menu labels">
{% for label in labels %}
<li><a style="font-weight: bolder">{{ label.name }}:{{ label.value }}</a></li>
{% endfor %}
</ul>
</div>
<table class="table table-striped table-bordered table-hover " id="asset_list_table" style="width: 100%">
<thead>
<tr>
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
<th class="text-center">{% trans 'Hostname' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'Hardware' %}</th>
<th class="text-center">{% trans 'Active' %}</th>
<th class="text-center">{% trans 'Reachable' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div id="actions" class="hide">
<div class="input-group">
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
<option value="delete">{% trans 'Delete selected' %}</option>
<option value="update">{% trans 'Update selected' %}</option>
<option value="remove">{% trans 'Remove from this node' %}</option>
<option value="deactive">{% trans 'Deactive selected' %}</option>
<option value="active">{% trans 'Active selected' %}</option>
</select>
<div class="input-group-btn pull-left" style="padding-left: 5px;">
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
{% trans 'Submit' %}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="rMenu">
<ul class="dropdown-menu">
<li 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 class="divider"></li>
<li id="m_del" tabindex="-1" onclick="removeTreeNode();"><a>{% trans 'Delete node' %}</a></li>
</ul>
</div>
{% include 'assets/_asset_import_modal.html' %}
{% include 'assets/_asset_list_modal.html' %}
{% endblock %}
{% block custom_foot_js %}
<script>
var zTree, rMenu, asset_table, show = 0;
function initTable() {
var options = {
ele: $('#asset_list_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()
};
asset_table = jumpserver.initServerSideDataTable(options);
return asset_table
}
function addTreeNode() {
hideRMenu();
var parentNode = zTree.getSelectedNodes()[0];
if (!parentNode){
return
}
var url = "{% url 'api-assets:node-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", parentNode.id );
$.post(url, {}, function (data, status){
if (status === "success") {
var newNode = {
name: data["value"],
id: data["id"],
pId: parentNode.id
};
newNode.checked = zTree.getSelectedNodes()[0].checked;
zTree.addNodes(parentNode, 0, newNode);
} else {
alert("{% trans 'Create node failed' %}")
}
});
}
function removeTreeNode() {
hideRMenu();
var current_node = zTree.getSelectedNodes()[0];
if (!current_node){
return
}
if (current_node.children && current_node.children.length > 0) {
alert("{% trans 'Have child node, cancel' %}")
} else {
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.id );
$.ajax({
url: url,
method: "DELETE",
success: function () {
zTree.removeNode(current_node);
}
});
}
}
function editTreeNode() {
hideRMenu();
var current_node = zTree.getSelectedNodes()[0];
if (!current_node){
return
}
if (current_node.value) {
current_node.name = current_node.value;
}
zTree.editName(current_node);
}
function OnRightClick(event, treeId, treeNode) {
if (!treeNode && event.target.tagName.toLowerCase() !== "button" && $(event.target).parents("a").length === 0) {
zTree.cancelSelectedNode();
showRMenu("root", event.clientX, event.clientY);
} else if (treeNode && !treeNode.noR) {
zTree.selectNode(treeNode);
showRMenu("node", event.clientX, event.clientY);
}
}
function showRMenu(type, x, y) {
$("#rMenu ul").show();
{#if (type === "root") {#}
{# return#}
{# } else {#}
{# $("#m_del").show();#}
{# $("#m_check").show();#}
{# $("#m_unCheck").show();#}
{# }#}
x -= 220;
rMenu.css({"top":y+"px", "left":x+"px", "visibility":"visible"});
$("body").bind("mousedown", onBodyMouseDown);
}
function beforeClick(treeId, treeNode, clickFlag) {
return true;
}
function hideRMenu() {
if (rMenu) rMenu.css({"visibility": "hidden"});
$("body").unbind("mousedown", onBodyMouseDown);
}
function onBodyMouseDown(event){
if (!(event.target.id === "rMenu" || $(event.target).parents("#rMenu").length>0)) {
rMenu.css({"visibility" : "hidden"});
}
}
function onRename(event, treeId, treeNode, isCancel){
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", treeNode.id);
var data = {"value": treeNode.name};
if (isCancel){
return
}
APIUpdateAttr({
url: url,
body: JSON.stringify(data),
method: "PATCH"
})
}
function onSelected(event, treeNode) {
var url = asset_table.ajax.url();
url = setUrlParam(url, "node_id", treeNode.id);
setCookie('node_selected', treeNode.id);
asset_table.ajax.url(url);
asset_table.ajax.reload();
}
function selectQueryNode() {
var query_node_id = $.getUrlParam("node");
var cookie_node_id = getCookie('node_selected');
var node;
var node_id;
if (query_node_id !== null) {
node_id = query_node_id
} else if (cookie_node_id !== null) {
node_id = cookie_node_id;
}
node = zTree.getNodesByParam("id", node_id, null);
if (node){
zTree.selectNode(node[0]);
}
}
function beforeDrag() {
return true
}
function beforeDrop(treeId, treeNodes, targetNode, moveType) {
var treeNodesNames = [];
$.each(treeNodes, function (index, value) {
treeNodesNames.push(value.value);
});
var msg = "你想移动节点: `" + treeNodesNames.join(",") + "` 到 `" + targetNode.value + "` 下吗?";
if (confirm(msg)){
return true
} else {
return false
}
}
function onDrag(event, treeId, treeNodes) {
}
function onDrop(event, treeId, treeNodes, targetNode, moveType) {
var treeNodesIds = [];
$.each(treeNodes, function (index, value) {
treeNodesIds.push(value.id);
});
var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.id);
var body = {nodes: treeNodesIds};
APIUpdateAttr({
url: the_url,
method: "PUT",
body: JSON.stringify(body)
})
}
function initTree() {
var setting = {
view: {
dblClickExpand: false,
showLine: true
},
data: {
simpleData: {
enable: true
}
},
edit: {
enable: true,
showRemoveBtn: false,
showRenameBtn: false,
drag: {
isCopy: true,
isMove: true
}
},
callback: {
onRightClick: OnRightClick,
beforeClick: beforeClick,
onRename: onRename,
onSelected: onSelected,
beforeDrag: beforeDrag,
onDrag: onDrag,
beforeDrop: beforeDrop,
onDrop: onDrop
}
};
var zNodes = [];
$.get("{% url 'api-assets:node-list' %}", function(data, status){
$.each(data, function (index, value) {
value["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();
});
}
function toggle() {
if (show === 0) {
$("#split-left").hide(500, function () {
$("#split-right").attr("class", "col-lg-12");
$("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
show = 1;
});
} else {
$("#split-right").attr("class", "col-lg-9");
$("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
$("#split-left").show(500);
show = 0;
}
}
$(document).ready(function(){
initTable();
initTree();
})
.on('click', '.labels li', function () {
var val = $(this).text();
$("#asset_list_table_filter input").val(val);
asset_table.search(val).draw();
})
.on('click', '.btn_export', function () {
var $data_table = $('#asset_list_table').DataTable();
var rows = $data_table.rows('.selected').data();
var assets = [];
$.each(rows, function (index, obj) {
assets.push(obj.id)
});
$.ajax({
url: "{% url "assets:asset-export" %}",
method: 'POST',
data: JSON.stringify({assets_id: assets}),
dataType: "json",
success: function (data, textStatus) {
window.open(data.redirect)
},
error: function () {
toastr.error('Export failed');
}
})
})
.on('click', '#btn_asset_import', function () {
var $form = $('#fm_asset_import');
$form.find('.help-block').remove();
function success (data) {
if (data.valid === false) {
$('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_assets'));
} else {
$('#id_created').html(data.created_info);
$('#id_created_detail').html(data.created.join(', '));
$('#id_updated').html(data.updated_info);
$('#id_updated_detail').html(data.updated.join(', '));
$('#id_failed').html(data.failed_info);
$('#id_failed_detail').html(data.failed.join(', '));
var $data_table = $('#asset_list_table').DataTable();
$data_table.ajax.reload();
}
}
$form.ajaxSubmit({success: success});
})
.on('click', '.btn-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;
}
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);
function success() {
rMenu.css({"visibility" : "hidden"});
}
APIUpdateAttr({
url: the_url,
method: "GET",
success_message: "更新硬件信息任务下发成功",
success: success
});
})
.on('click', '.btn-test-connective', function () {
var url = "{% url 'api-assets:node-test-connective' pk=DEFAULT_PK %}";
var nodes = zTree.getSelectedNodes();
var current_node;
if (nodes && nodes.length ===1 ){
current_node = nodes[0];
} else {
return null;
}
var the_url = url.replace("{{ DEFAULT_PK }}", current_node.id);
function success() {
rMenu.css({"visibility" : "hidden"});
}
APIUpdateAttr({
url: the_url,
method: "GET",
success_message: "测试可连接性任务下发成功",
success: success
});
})
.on('click', '.btn_asset_delete', function () {
var $this = $(this);
var $data_table = $("#asset_list_table").DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var the_url = '{% url "api-assets:asset-detail" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", uid);
objectDelete($this, name, the_url);
setTimeout( function () {
$data_table.ajax.reload();
}, 3000);
})
.on('click', '#btn_bulk_update', function () {
var action = $('#slct_bulk_update').val();
var $data_table = $('#asset_list_table').DataTable();
var id_list = [];
$data_table.rows({selected: true}).every(function(){
id_list.push(this.data().id);
});
if (id_list.length === 0) {
return false;
}
var the_url = "{% url 'api-assets:asset-list' %}";
function doDeactive() {
var data = [];
$.each(id_list, function(index, object_id) {
var obj = {"pk": object_id, "is_active": false};
data.push(obj);
});
function success() {
asset_table.ajax.reload()
}
APIUpdateAttr({
url: the_url,
method: 'PATCH',
body: JSON.stringify(data),
success: success
});
}
function doActive() {
var data = [];
$.each(id_list, function(index, object_id) {
var obj = {"pk": object_id, "is_active": true};
data.push(obj);
});
function success() {
asset_table.ajax.reload()
}
APIUpdateAttr({
url: the_url,
method: 'PATCH',
body: JSON.stringify(data),
success: success
});
}
function doDelete() {
swal({
title: "{% trans 'Are you sure?' %}",
text: "{% trans 'This will delete the selected assets !!!' %}",
type: "warning",
showCancelButton: true,
confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false
}, function() {
var success = function() {
var msg = "{% trans 'Asset Deleted.' %}";
swal("{% trans 'Asset Delete' %}", msg, "success");
$('#asset_list_table').DataTable().ajax.reload();
};
var fail = function() {
var msg = "{% trans 'Asset Deleting failed.' %}";
swal("{% trans 'Asset Delete' %}", msg, "error");
};
var url_delete = the_url + '?id__in=' + JSON.stringify(id_list);
APIUpdateAttr({
url: url_delete,
method: 'DELETE',
success: success,
error: fail
});
$data_table.ajax.reload();
jumpserver.checked = false;
});
}
function doUpdate() {
var id_list_string = id_list.join(',');
var url = "{% url 'assets:asset-bulk-update' %}?assets_id=" + id_list_string;
location.href = url
}
function doRemove() {
var current_node;
var nodes = zTree.getSelectedNodes();
if (nodes && nodes.length === 1) {
current_node = nodes[0]
} else {
return
}
var data = {
'assets': id_list
};
var success = function () {
asset_table.ajax.reload()
};
APIUpdateAttr({
'url': '/api/assets/v1/nodes/' + current_node.id + '/assets/remove/',
'method': 'PUT',
'body': JSON.stringify(data),
'success': success
})
}
switch(action) {
case 'deactive':
doDeactive();
break;
case 'delete':
doDelete();
break;
case 'update':
doUpdate();
break;
case 'active':
doActive();
break;
case 'remove':
doRemove();
break;
default:
break;
}
});
</script>
{% endblock %}

View File

@@ -0,0 +1,93 @@
{% extends '_base_create_update.html' %}
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% load asset_tags %}
{% load common_tags %}
{% block custom_head_css_js_create %}
<link href="{% static "css/plugins/inputTags.css" %}" rel="stylesheet">
<script src="{% static "js/plugins/inputTags.jquery.min.js" %}"></script>
{% endblock %}
{% block form %}
<form action="" method="post" class="form-horizontal">
{% if form.no_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
{% csrf_token %}
<h3>{% trans 'Basic' %}</h3>
{% bootstrap_field form.hostname layout="horizontal" %}
{% bootstrap_field form.ip layout="horizontal" %}
{% bootstrap_field form.port layout="horizontal" %}
{% bootstrap_field form.platform layout="horizontal" %}
{% bootstrap_field form.public_ip layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Auth' %}</h3>
{% bootstrap_field form.admin_user layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Node' %}</h3>
{% bootstrap_field form.nodes layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Labels' %}</h3>
<div class="form-group">
<label for="{{ form.labels.id_for_label }}" class="col-md-2 control-label">{% trans 'Label' %}</label>
<div class="col-md-9">
<select name="labels" class="select2 labels" data-placeholder="Select labels" style="width: 100%" multiple="" tabindex="4" id="{{ form.labels.id_for_label }}">
{% for name, labels in form.labels.field.queryset|group_labels %}
<optgroup label="{{ name }}">
{% for label in labels %}
{% if label in form.labels.initial %}
<option value="{{ label.id }}" selected>{{ label.value }}</option>
{% else %}
<option value="{{ label.id }}">{{ label.value }}</option>
{% endif %}
{% endfor %}
</optgroup>
{% endfor %}
</select>
</div>
</div>
<div class="hr-line-dashed"></div>
<h3>{% trans 'Configuration' %}</h3>
{% bootstrap_field form.number layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Other' %}</h3>
{% bootstrap_field form.comment layout="horizontal" %}
{% bootstrap_field form.is_active layout="horizontal" %}
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-white" type="reset">{% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
{% endblock %}
{% block custom_foot_js %}
<script>
function format(item) {
var group = item.element.parentElement.label;
return group + ':' + item.text;
}
$(document).ready(function () {
$('.select2').select2({
allowClear: true
});
$(".labels").select2({
allowClear: true,
templateSelection: format
});
})
</script>
{% endblock %}

View File

@@ -0,0 +1,15 @@
{% load i18n %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% trans 'Confirm delete' %}</title>
</head>
<body>
<form action="" method="post">
{% csrf_token %}
<p>{% trans 'Are you sure delete' %} <b>{{ object.name }} </b> ?</p>
<input type="submit" value="Confirm" />
</form>
</body>
</html>

View File

@@ -0,0 +1,31 @@
{% extends '_base_create_update.html' %}
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% block form %}
<form id="groupForm" method="post" class="form-horizontal">
{% csrf_token %}
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.value layout="horizontal" %}
{% bootstrap_field form.assets layout="horizontal" %}
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
{% endblock %}
{% block custom_foot_js %}
<script type="text/javascript">
$(document).ready(function () {
$('.select2').select2({
closeOnSelect: false
});
});
</script>
{% endblock %}

View File

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

View File

@@ -0,0 +1,166 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li>
<a href="{% url 'assets:system-user-detail' pk=system_user.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
</li>
<li class="active">
<a href="{% url 'assets:system-user-asset' pk=system_user.id %}" class="text-center">
<i class="fa fa-bar-chart-o"></i> {% trans 'Assets' %}
</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 'Assets of ' %} <b>{{ system_user.name }} </b><span class="badge">{{ paginator.count }}</span></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table table-hover" id="system_user_list">
<thead>
<tr>
<th>{% trans 'Hostname' %}</th>
<th>{% trans 'IP' %}</th>
<th>{% trans 'Port' %}</th>
<th>{% trans 'Reachable' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %}
</div>
<div class="panel-body">
<table class="table">
<tbody>
<tr class="no-borders-tr">
<td width="50%">{% trans 'Push system user now' %}:</td>
<td>
<span style="float: right">
<button type="button" class="btn btn-primary btn-xs btn-push" style="width: 54px">{% trans 'Push' %}</button>
</span>
</td>
</tr>
<tr>
<td width="50%">{% trans 'Test assets connective' %}:</td>
<td>
<span style="float: right">
<button type="button" class="btn btn-primary btn-xs btn-test-connective" style="width: 54px">{% trans 'Test' %}</button>
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
function initAssetsTable() {
var unreachable = {{ system_user.unreachable_assets|safe}};
var options = {
ele: $('#system_user_list'),
buttons: [],
order: [],
columnDefs: [
{targets: 0, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:asset-detail" pk=DEFAULT_PK %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 3, createdCell: function (td, cellData) {
if (unreachable.indexOf(cellData) >= 0) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}}
],
ajax_url: '{% url "api-assets:asset-list" %}?system_user_id={{ system_user.id }}',
columns: [{data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "hostname" }],
op_html: $('#actions').html()
};
jumpserver.initServerSideDataTable(options);
}
$(document).ready(function () {
$('.select2').select2()
.on("select2:select", function (evt) {
var data = evt.params.data;
jumpserver.assets_selected[data.id] = data.text;
jumpserver.asset_groups_selected[data.id] = data.text;
})
.on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.assets_selected[data.id];
delete jumpserver.asset_groups_selected[data.id];
});
initAssetsTable();
})
.on('click', '.btn-push', function () {
var the_url = "{% url 'api-assets:system-user-push' pk=system_user.id %}";
var error = function (data) {
alert(data)
};
APIUpdateAttr({
url: the_url,
error: error,
method: 'GET',
success_message: "{% trans "Task has been send, Go to ops task list seen result" %}"
});
})
.on('click', '.btn-test-connective', function () {
var the_url = "{% url 'api-assets:system-user-connective' pk=system_user.id %}";
var error = function (data) {
alert(data)
};
APIUpdateAttr({
url: the_url,
error: error,
method: 'GET',
success_message: "{% trans "Task has been send, seen left assets status" %}"
});
})
</script>
{% endblock %}

View File

@@ -0,0 +1,7 @@
{% extends 'assets/_system_user.html' %}
{% load i18n %}
{% load static %}
{% block auth %}
{{ block.super }}
{% endblock %}

View File

@@ -0,0 +1,319 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href='{% static "css/plugins/select2/select2.min.css" %}' rel="stylesheet">
<script src='{% static "js/plugins/select2/select2.full.min.js" %}'></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li class="active">
<a href="{% url 'assets:system-user-detail' pk=system_user.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
</li>
{# <li>#}
{# <a href="{% url 'assets:system-user-asset' pk=system_user.id %}" class="text-center">#}
{# <i class="fa fa-bar-chart-o"></i> {% trans 'Attached assets' %}#}
{# </a>#}
{# </li>#}
<li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'assets:system-user-update' pk=system_user.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-danger btn-del">
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-8" style="padding-left: 0;">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label"><b>{{ system_user.name }}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table">
<tbody>
<tr class="no-borders-tr">
<td>{% trans 'Name' %}:</td>
<td><b>{{ system_user.name }}</b></td>
</tr>
<tr>
<td>{% trans 'Username' %}:</td>
<td><b>{{ system_user.username }}</b></td>
</tr>
<tr>
<td>{% trans 'Protocol' %}:</td>
<td><b>{{ system_user.protocol }}</b></td>
</tr>
<tr>
<td>{% trans 'Sudo' %}:</td>
<td><b>{{ system_user.sudo }}</b></td>
</tr>
{% if system_user.shell %}
<tr>
<td>{% trans 'Shell' %}:</td>
<td><b>{{ system_user.shell }}</b></td>
</tr>
{% endif %}
{% if system_user.home %}
<tr>
<td>{% trans 'Home' %}:</td>
<td><b>{{ system_user.home }}</b></td>
</tr>
{% endif %}
{% if system_user.uid %}
<tr>
<td>{% trans 'Uid' %}:</td>
<td><b>{{ system_user.uid }}</b></td>
</tr>
{% endif %}
<tr>
<td>{% trans 'Date created' %}:</td>
<td><b>{{ system_user.date_created }}</b></td>
</tr>
<tr>
<td>{% trans 'Created by' %}:</td>
<td><b>{{ asset_group.created_by }}</b></td>
</tr>
<tr>
<td>{% trans 'Comment' %}:</td>
<td><b>{{ system_user.comment }}</b></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %}
</div>
<div class="panel-body">
<table class="table">
<tbody>
<tr class="no-borders-tr">
<td width="50%">{% trans 'Auto push' %}:</td>
<td>
<span class="pull-right">
<div class="switch">
<div class="onoffswitch">
<input type="checkbox" {% if system_user.auto_push %} checked {% endif %} class="onoffswitch-checkbox" id="btn-auto-push">
<label class="onoffswitch-label" for="btn-auto-push">
<span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
</label>
</div>
</div>
</span>
</td>
</tr>
<tr class="no-borders-tr">
{% if system_user.auto_push %}
<td width="50%">{% trans 'Push system user now' %}:</td>
<td>
<span style="float: right">
<button type="button" class="btn btn-primary btn-xs btn-push" style="width: 54px">{% trans 'Push' %}</button>
</span>
</td>
</tr>
<tr>
{% endif %}
<td width="50%">{% trans 'Test assets connective' %}:</td>
<td>
<span style="float: right">
<button type="button" class="btn btn-primary btn-xs btn-test-connective" style="width: 54px">{% trans 'Test' %}</button>
</span>
</td>
</tr>
{# <tr>#}
{# <td width="50%">{% trans 'Change auth period' %}:</td>#}
{# <td>#}
{# <span style="float: right">#}
{# <button type="button" class="btn btn-primary btn-xs" style="width: 54px">{% trans 'Reset' %}</button>#}
{# </span>#}
{# </td>#}
{# </tr>#}
</tbody>
</table>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Nodes' %}
</div>
<div class="panel-body">
<table class="table node_edit" id="add-asset2group">
<tbody>
<form>
<tr>
<td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Add to node' %}" id="node_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for node in nodes_remain %}
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node.name }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td colspan="2" class="no-borders">
<button type="button" class="btn btn-info btn-sm" id="btn-add-to-node">{% trans 'Confirm' %}</button>
</td>
</tr>
</form>
{% for node in system_user.nodes.all %}
<tr>
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node.name }}</b></td>
<td>
<button class="btn btn-danger pull-right btn-xs btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
function updateSystemUserCluster(nodes) {
var the_url = "{% url 'api-assets:system-user-detail' pk=system_user.id %}";
var body = {
nodes: Object.assign([], nodes)
};
var success = function(data) {
// remove all the selected groups from select > option and rendered ul element;
$('.select2-selection__rendered').empty();
$('#node_selected').val('');
$.map(jumpserver.nodes_selected, function(node_name, index) {
$('#opt_' + index).remove();
// change tr html of user groups.
$('.node_edit tbody').append(
'<tr>' +
'<td><b class="bdg_node" data-gid="' + index + '">' + node_name + '</b></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button></td>' +
'</tr>'
)
});
// clear jumpserver.groups_selected
jumpserver.nodes_selected = {};
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
success: success
});
}
jumpserver.nodes_selected = {};
$(document).ready(function () {
$('.select2').select2()
.on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.nodes_selected[data.id] = data.text;
})
.on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.nodes_selected[data.id];
});
})
.on('click', '#btn-auto-push', function () {
var checked = $(this).prop('checked');
var the_url = "{% url 'api-assets:system-user-detail' pk=system_user.id %}";
var body = {
'auto_push': checked
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body)
});
})
.on('click', '#btn-add-to-node', function() {
if (Object.keys(jumpserver.nodes_selected).length === 0) {
return false;
}
var nodes = $('.bdg_node').map(function() {
return $(this).data('gid');
}).get();
$.map(jumpserver.nodes_selected, function(value, index) {
nodes.push(index);
});
updateSystemUserCluster(nodes);
})
.on('click', '.btn-remove-from-node', function() {
var $this = $(this);
var $tr = $this.closest('tr');
var $badge = $tr.find('.bdg_node');
var gid = $badge.data('gid');
var node_name = $badge.html() || $badge.text();
$('#groups_selected').append(
'<option value="' + gid + '" id="opt_' + gid + '">' + node_name + '</option>'
);
$tr.remove();
var nodes = $('.bdg_node').map(function () {
return $(this).data('gid');
}).get();
updateSystemUserCluster(nodes);
}).on('click', '.btn-del', function () {
var $this = $(this);
var name = "{{ system_user.name}}";
var uid = "{{ system_user.id }}";
var the_url = '{% url "api-assets:system-user-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
var redirect_url = "{% url 'assets:system-user-list' %}";
objectDelete($this, name, the_url, redirect_url);
})
.on('click', '.btn-push', function () {
var the_url = "{% url 'api-assets:system-user-push' pk=system_user.id %}";
var error = function (data) {
alert(data)
};
APIUpdateAttr({
url: the_url,
error: error,
method: 'GET',
success_message: "{% trans "Task has been send, Go to ops task list seen result" %}"
});
})
.on('click', '.btn-test-connective', function () {
var the_url = "{% url 'api-assets:system-user-connective' pk=system_user.id %}";
var error = function (data) {
alert(data)
};
APIUpdateAttr({
url: the_url,
error: error,
method: 'GET',
success_message: "{% trans "Task has been send, seen left assets status" %}"
});
})
</script>
{% endblock %}

View File

@@ -0,0 +1,173 @@
{% extends '_base_list.html' %}
{% load i18n %}
{% block help_message %}
<div class="alert alert-info help-message">
系统用户是 Jumpserver跳转登录资产时使用的用户可以理解为登录资产用户如 web, sa, dba(`ssh web@some-host`), 而不是使用某个用户的用户名跳转登录服务器(`ssh xiaoming@some-host`);
简单来说是 用户使用自己的用户名登录Jumpserver, Jumpserver使用系统用户登录资产。
系统用户创建时,如果选择了自动推送 Jumpserver会使用ansible自动推送系统用户到资产中如果资产(交换机、windows)不支持ansible, 请手动填写账号密码。
目前还不支持Windows的自动推送
</div>
{% endblock %}
{% block table_search %}
{% endblock %}
{% block table_container %}
<div class="uc pull-left m-r-5">
<a href="{% url 'assets:system-user-create' %}" class="btn btn-sm btn-primary "> {% trans "Create system user" %} </a>
</div>
<table class="table table-striped table-bordered table-hover " id="system_user_list_table" >
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Username' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'Reachable' %}</th>
<th class="text-center">{% trans 'Unreachable' %}</th>
<th class="text-center">{% trans 'Ratio' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% endblock %}
{% block custom_foot_js %}
<script>
function initTable() {
var options = {
ele: $('#system_user_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:system-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 4, createdCell: function (td, cellData) {
var innerHtml = "";
if (cellData !== 0) {
innerHtml = "<span class='text-navy'>" + cellData + "</span>";
} else {
innerHtml = "<span>" + cellData + "</span>";
}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData +'">' + innerHtml + '</span>');
}},
{targets: 5, createdCell: function (td, cellData) {
var innerHtml = "";
if (cellData !== 0) {
innerHtml = "<span class='text-danger'>" + cellData + "</span>";
} else {
innerHtml = "<span>" + cellData + "</span>";
}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}},
{targets: 6, createdCell: function (td, cellData, rowData) {
var val = 0;
var innerHtml = "";
var total = rowData.assets_amount;
var reachable = rowData.reachable_amount;
if (total !== 0) {
val = reachable/total * 100;
}
if (val === 100) {
innerHtml = "<span class='text-navy'>" + val + "% </span>";
} else {
var num = new Number(val);
innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>";
}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}},
{targets: 8, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:system-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
}}],
ajax_url: '{% url "api-assets:system-user-list" %}',
columns: [
{data: "id" }, {data: "name" }, {data: "username" }, {data: "assets_amount" },
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" }
],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
}
$(document).ready(function(){
initTable();
})
.on('click', '.btn_admin_user_delete', function () {
var $this = $(this);
var $data_table = $('#cluster_list_table').DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var the_url = '{% url "api-assets:system-user-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
objectDelete($this, name, the_url);
setTimeout( function () {
$data_table.ajax.reload();
}, 3000);
})
.on('click', '#btn_bulk_update', function () {
var action = $('#slct_bulk_update').val();
var $data_table = $('#system_user_list_table').DataTable();
var id_list = [];
var plain_id_list = [];
$data_table.rows({selected: true}).every(function(){
id_list.push({id: this.data().id});
plain_id_list.push(this.data().id);
});
if (id_list === []) {
return false;
}
var the_url = "{% url 'api-assets:system-user-list' %}";
function doDelete() {
swal({
title: "{% trans 'Are you sure?' %}",
text: "{% trans 'This will delete the selected System Users !!!' %}",
type: "warning",
showCancelButton: true,
confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false
}, function() {
var success = function() {
var msg = "{% trans 'System Users Deleted.' %}";
swal("{% trans 'System Users Delete' %}", msg, "success");
$('#system_user_list_table').DataTable().ajax.reload();
};
var fail = function() {
var msg = "{% trans 'System Users Deleting failed.' %}";
swal("{% trans 'System Users Delete' %}", msg, "error");
};
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail});
$data_table.ajax.reload();
jumpserver.checked = false;
});
}
function doUpdate() {
{# TODO: bulk update the System Users #}
}
switch (action) {
case 'delete':
doDelete();
break;
case 'update':
doUpdate();
break;
default:
break;
}
})
</script>
{% endblock %}

View File

@@ -0,0 +1,24 @@
{% extends 'assets/_system_user.html' %}
{% load i18n %}
{% load static %}
{% load bootstrap3 %}
{% block auth %}
<h3>{% trans 'Auth' %}</h3>
{% bootstrap_field form.password layout="horizontal" %}
{% bootstrap_field form.private_key_file layout="horizontal" %}
<div class="form-group">
<label for="{{ form.as_push.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto push' %}</label>
<div class="col-sm-8">
{{ form.auto_push}}
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function () {
$('.select2').select2();
})
</script>
{% endblock %}

View File

@@ -0,0 +1,81 @@
{% extends '_base_list.html' %}
{% load i18n %}
{% load static %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
{% endblock %}
{% block content_left_head %}{% endblock %}
{% block table_search %}
{% endblock %}
{% block table_container %}
<table class="table table-striped table-bordered table-hover " id="asset_list_table" >
<thead>
<tr>
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
<th class="text-center">{% trans 'Hostname' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'Port' %}</th>
<th class="text-center">{% trans 'Hardware' %}</th>
<th class="text-center">{% trans 'Active' %}</th>
<th class="text-center">{% trans 'Connective' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% endblock %}
{% block custom_foot_js %}
<script src="{% static 'js/jquery.form.min.js' %}"></script>
<script type="text/javascript">
function initTable() {
var options = {
ele: $('#asset_list_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: 5, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 6, createdCell: function (td, cellData) {
if (cellData == 'Unknown'){
$(td).html('<i class="fa fa-circle text-warning"></i>')
} else if (!cellData) {
$(td).html('<i class="fa fa-circle text-danger"></i>')
} else {
$(td).html('<i class="fa fa-circle text-navy"></i>')
}
}},
{# {targets: 9, createdCell: function (td, cellData, rowData) {#}
{# var conn_btn = '<a href="{% url "terminal:web-terminal" %}?id={{ DEFAULT_PK }}" class="btn btn-xs btn-info">{% trans "Connect" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);#}
{# $(td).html(conn_btn)#}
{# }}#}
],
ajax_url: '{% url "api-assets:user-asset-list" %}',
columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
{data: "hardware_info"}, {data: "is_active" }, {data: "is_connective"}
],
op_html: $('#actions').html()
};
return jumpserver.initDataTable(options);
}
$(document).ready(function(){
initTable();
});
</script>
{% endblock %}

View File

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

View File

@@ -0,0 +1,12 @@
from collections import defaultdict
from django import template
register = template.Library()
@register.filter
def group_labels(queryset):
grouped = defaultdict(list)
for label in queryset:
grouped[label.name].append(label)
return [(name, labels) for name, labels in grouped.items()]

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,55 @@
# coding:utf-8
from django.conf.urls import url
from .. import api
from rest_framework_bulk.routes import BulkRouter
app_name = 'assets'
router = BulkRouter()
# router.register(r'v1/groups', api.AssetGroupViewSet, 'asset-group')
router.register(r'v1/assets', api.AssetViewSet, 'asset')
# router.register(r'v1/clusters', api.ClusterViewSet, 'cluster')
router.register(r'v1/admin-user', api.AdminUserViewSet, 'admin-user')
router.register(r'v1/system-user', api.SystemUserViewSet, 'system-user')
router.register(r'v1/labels', api.LabelViewSet, 'label')
router.register(r'v1/nodes', api.NodeViewSet, 'node')
urlpatterns = [
url(r'^v1/assets-bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/auth-info/', api.SystemUserAuthInfoApi.as_view(),
name='system-user-auth-info'),
url(r'^v1/assets/(?P<pk>[0-9a-zA-Z\-]{36})/refresh/$',
api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'),
url(r'^v1/assets/(?P<pk>[0-9a-zA-Z\-]{36})/alive/$',
api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'),
url(r'^v1/assets/user-assets/$',
api.UserAssetListView.as_view(), name='user-asset-list'),
# update the asset group, which add or delete the asset to the group
#url(r'^v1/groups/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$',
# api.GroupUpdateAssetsApi.as_view(), name='group-update-assets'),
#url(r'^v1/groups/(?P<pk>[0-9a-zA-Z\-]{36})/assets/add/$',
# api.GroupAddAssetsApi.as_view(), name='group-add-assets'),
# update the Cluster, and add or delete the assets to the Cluster
#url(r'^v1/cluster/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$',
# api.ClusterAddAssetsApi.as_view(), name='cluster-add-assets'),
#url(r'^v1/cluster/(?P<pk>[0-9a-zA-Z\-]{36})/assets/connective/$',
# api.ClusterTestAssetsAliveApi.as_view(), name='cluster-test-connective'),
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/$',
api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$',
api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'),
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/push/$',
api.SystemUserPushApi.as_view(), name='system-user-push'),
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$',
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/$', api.NodeChildrenApi.as_view(), name='node-children'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/add/$', api.NodeAddChildrenApi.as_view(), name='node-add-children'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/add/$', api.NodeAddAssetsApi.as_view(), name='node-add-assets'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/remove/$', api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/refresh-hardware-info/$', api.RefreshNodeHardwareInfoApi.as_view(), name='node-refresh-hardware-info'),
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/test-connective/$', api.TestNodeConnectiveApi.as_view(), name='node-test-connective'),
]
urlpatterns += router.urls

View File

@@ -0,0 +1,43 @@
# coding:utf-8
from django.conf.urls import url
from .. import views
app_name = 'assets'
urlpatterns = [
# Resource asset url
url(r'^$', views.AssetListView.as_view(), name='asset-index'),
url(r'^asset/$', views.AssetListView.as_view(), name='asset-list'),
url(r'^asset/create/$', views.AssetCreateView.as_view(), name='asset-create'),
url(r'^asset/export/$', views.AssetExportView.as_view(), name='asset-export'),
url(r'^asset/import/$', views.BulkImportAssetView.as_view(), name='asset-import'),
url(r'^asset/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.AssetDetailView.as_view(), name='asset-detail'),
url(r'^asset/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.AssetUpdateView.as_view(), name='asset-update'),
url(r'^asset/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.AssetDeleteView.as_view(), name='asset-delete'),
url(r'^asset/update/$', views.AssetBulkUpdateView.as_view(), name='asset-bulk-update'),
# User asset view
url(r'^user-asset/$', views.UserAssetListView.as_view(), name='user-asset-list'),
# Resource admin user url
url(r'^admin-user/$', views.AdminUserListView.as_view(), name='admin-user-list'),
url(r'^admin-user/create/$', views.AdminUserCreateView.as_view(), name='admin-user-create'),
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.AdminUserDetailView.as_view(), name='admin-user-detail'),
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.AdminUserUpdateView.as_view(), name='admin-user-update'),
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.AdminUserDeleteView.as_view(), name='admin-user-delete'),
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$', views.AdminUserAssetsView.as_view(), name='admin-user-assets'),
# Resource system user url
url(r'^system-user/$', views.SystemUserListView.as_view(), name='system-user-list'),
url(r'^system-user/create/$', views.SystemUserCreateView.as_view(), name='system-user-create'),
url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.SystemUserDetailView.as_view(), name='system-user-detail'),
url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.SystemUserUpdateView.as_view(), name='system-user-update'),
url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.SystemUserDeleteView.as_view(), name='system-user-delete'),
url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/asset/$', views.SystemUserAssetView.as_view(), name='system-user-asset'),
url(r'^label/$', views.LabelListView.as_view(), name='label-list'),
url(r'^label/create/$', views.LabelCreateView.as_view(), name='label-create'),
url(r'^label/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.LabelUpdateView.as_view(), name='label-update'),
url(r'^label/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.LabelDeleteView.as_view(), name='label-delete'),
]

48
apps/assets/utils.py Normal file
View File

@@ -0,0 +1,48 @@
# ~*~ coding: utf-8 ~*~
#
from collections import defaultdict
from functools import reduce
import operator
from django.db.models import Q
from common.utils import get_object_or_none
from .models import Asset, SystemUser, Label
def get_assets_by_id_list(id_list):
return Asset.objects.filter(id__in=id_list)
def get_assets_by_hostname_list(hostname_list):
return Asset.objects.filter(hostname__in=hostname_list)
def get_system_user_by_name(name):
system_user = get_object_or_none(SystemUser, name=name)
return system_user
class LabelFilter:
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
query_keys = self.request.query_params.keys()
all_label_keys = Label.objects.values_list('name', flat=True)
valid_keys = set(all_label_keys) & set(query_keys)
labels_query = {}
for key in valid_keys:
labels_query[key] = self.request.query_params.get(key)
conditions = []
for k, v in labels_query.items():
query = {'labels__name': k, 'labels__value': v}
conditions.append(query)
if conditions:
for kwargs in conditions:
queryset = queryset.filter(**kwargs)
return queryset

View File

@@ -0,0 +1,5 @@
# coding:utf-8
from .asset import *
from .system_user import *
from .admin_user import *
from .label import *

View File

@@ -0,0 +1,116 @@
# coding:utf-8
from __future__ import absolute_import, unicode_literals
from django.utils.translation import ugettext as _
from django.conf import settings
from django.urls import reverse_lazy
from django.views.generic import TemplateView, ListView
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic.detail import DetailView, SingleObjectMixin
from common.const import create_success_msg, update_success_msg
from .. import forms
from ..models import AdminUser, Node
from ..hands import AdminUserRequiredMixin
__all__ = [
'AdminUserCreateView', 'AdminUserDetailView',
'AdminUserDeleteView', 'AdminUserListView',
'AdminUserUpdateView', 'AdminUserAssetsView',
]
class AdminUserListView(AdminUserRequiredMixin, TemplateView):
model = AdminUser
template_name = 'assets/admin_user_list.html'
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Admin user list'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class AdminUserCreateView(AdminUserRequiredMixin,
SuccessMessageMixin,
CreateView):
model = AdminUser
form_class = forms.AdminUserForm
template_name = 'assets/admin_user_create_update.html'
success_url = reverse_lazy('assets:admin-user-list')
success_message = create_success_msg
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Create admin user')
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class AdminUserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
model = AdminUser
form_class = forms.AdminUserForm
template_name = 'assets/admin_user_create_update.html'
success_url = reverse_lazy('assets:admin-user-list')
success_message = update_success_msg
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Update admin user'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class AdminUserDetailView(AdminUserRequiredMixin, DetailView):
model = AdminUser
template_name = 'assets/admin_user_detail.html'
context_object_name = 'admin_user'
object = None
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Admin user detail'),
'nodes': Node.objects.all()
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
paginate_by = settings.DISPLAY_PER_PAGE
template_name = 'assets/admin_user_assets.html'
context_object_name = 'admin_user'
object = None
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=AdminUser.objects.all())
return super().get(request, *args, **kwargs)
def get_queryset(self):
self.queryset = self.object.asset_set.all()
return self.queryset
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Admin user detail'),
"total_amount": len(self.queryset),
'unreachable_amount': len([asset for asset in self.queryset if asset.is_connective is False])
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class AdminUserDeleteView(AdminUserRequiredMixin, DeleteView):
model = AdminUser
template_name = 'delete_confirm.html'
success_url = reverse_lazy('assets:admin-user-list')

324
apps/assets/views/asset.py Normal file
View File

@@ -0,0 +1,324 @@
# coding:utf-8
from __future__ import absolute_import, unicode_literals
import csv
import json
import uuid
import codecs
import chardet
from io import StringIO
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.views.generic import TemplateView, ListView, View
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
from django.urls import reverse_lazy
from django.views.generic.detail import DetailView
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from django.core.cache import cache
from django.utils import timezone
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import redirect
from django.contrib.messages.views import SuccessMessageMixin
from common.mixins import JSONResponseMixin
from common.utils import get_object_or_none, get_logger, is_uuid
from common.const import create_success_msg, update_success_msg
from .. import forms
from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser, Label, Node
from ..hands import AdminUserRequiredMixin
__all__ = [
'AssetListView', 'AssetCreateView', 'AssetUpdateView',
'UserAssetListView', 'AssetBulkUpdateView', 'AssetDetailView',
'AssetDeleteView', 'AssetExportView', 'BulkImportAssetView',
]
logger = get_logger(__file__)
class AssetListView(AdminUserRequiredMixin, TemplateView):
template_name = 'assets/asset_list.html'
def get_context_data(self, **kwargs):
Node.root()
context = {
'app': _('Assets'),
'action': _('Asset list'),
'labels': Label.objects.all().order_by('name'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class UserAssetListView(LoginRequiredMixin, TemplateView):
template_name = 'assets/user_asset_list.html'
def get_context_data(self, **kwargs):
context = {
'action': _('My assets'),
'system_users': SystemUser.objects.all(),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
model = Asset
form_class = forms.AssetCreateForm
template_name = 'assets/asset_create.html'
success_url = reverse_lazy('assets:asset-list')
# def form_valid(self, form):
# print("form valid")
# asset = form.save()
# asset.created_by = self.request.user.username or 'Admin'
# asset.date_created = timezone.now()
# asset.save()
# return super().form_valid(form)
def get_form(self, form_class=None):
form = super().get_form(form_class=form_class)
node_id = self.request.GET.get("node_id")
if node_id:
node = get_object_or_none(Node, id=node_id)
else:
node = Node.root()
form["nodes"].initial = node
return form
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Create asset'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
def get_success_message(self, cleaned_data):
return create_success_msg % ({"name": cleaned_data["hostname"]})
# class AssetModalListView(AdminUserRequiredMixin, ListView):
# paginate_by = settings.DISPLAY_PER_PAGE
# model = Asset
# context_object_name = 'asset_modal_list'
# template_name = 'assets/_asset_list_modal.html'
#
# def get_context_data(self, **kwargs):
# assets = Asset.objects.all()
# assets_id = self.request.GET.get('assets_id', '')
# assets_id_list = [i for i in assets_id.split(',') if i.isdigit()]
# context = {
# 'all_assets': assets_id_list,
# 'assets': assets
# }
# kwargs.update(context)
# return super().get_context_data(**kwargs)
class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
model = Asset
form_class = forms.AssetBulkUpdateForm
template_name = 'assets/asset_bulk_update.html'
success_url = reverse_lazy('assets:asset-list')
id_list = None
form = None
def get(self, request, *args, **kwargs):
assets_id = self.request.GET.get('assets_id', '')
self.id_list = [i for i in assets_id.split(',')]
if kwargs.get('form'):
self.form = kwargs['form']
elif assets_id:
self.form = self.form_class(
initial={'assets': self.id_list}
)
else:
self.form = self.form_class()
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
form.save()
return redirect(self.success_url)
else:
return self.get(request, form=form, *args, **kwargs)
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Bulk update asset'),
'form': self.form,
'assets_selected': self.id_list,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class AssetUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
model = Asset
form_class = forms.AssetUpdateForm
template_name = 'assets/asset_update.html'
success_url = reverse_lazy('assets:asset-list')
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Update asset'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
def get_success_message(self, cleaned_data):
return update_success_msg % ({"name": cleaned_data["hostname"]})
class AssetDeleteView(AdminUserRequiredMixin, DeleteView):
model = Asset
template_name = 'delete_confirm.html'
success_url = reverse_lazy('assets:asset-list')
class AssetDetailView(DetailView):
model = Asset
context_object_name = 'asset'
template_name = 'assets/asset_detail.html'
def get_context_data(self, **kwargs):
nodes_remain = Node.objects.exclude(assets=self.object)
context = {
'app': _('Assets'),
'action': _('Asset detail'),
'nodes_remain': nodes_remain,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
@method_decorator(csrf_exempt, name='dispatch')
class AssetExportView(View):
def get(self, request):
spm = request.GET.get('spm', '')
assets_id_default = [Asset.objects.first().id] if Asset.objects.first() else []
assets_id = cache.get(spm, assets_id_default)
fields = [
field for field in Asset._meta.fields
if field.name not in [
'date_created'
]
]
filename = 'assets-{}.csv'.format(
timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S')
)
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="%s"' % filename
response.write(codecs.BOM_UTF8)
assets = Asset.objects.filter(id__in=assets_id)
writer = csv.writer(response, dialect='excel', quoting=csv.QUOTE_MINIMAL)
header = [field.verbose_name for field in fields]
writer.writerow(header)
for asset in assets:
data = [getattr(asset, field.name) for field in fields]
writer.writerow(data)
return response
def post(self, request, *args, **kwargs):
try:
assets_id = json.loads(request.body).get('assets_id', [])
except ValueError:
return HttpResponse('Json object not valid', status=400)
spm = uuid.uuid4().hex
cache.set(spm, assets_id, 300)
url = reverse_lazy('assets:asset-export') + '?spm=%s' % spm
return JsonResponse({'redirect': url})
class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
form_class = forms.FileForm
def form_valid(self, form):
f = form.cleaned_data['file']
det_result = chardet.detect(f.read())
f.seek(0) # reset file seek index
file_data = f.read().decode(det_result['encoding']).strip(codecs.BOM_UTF8.decode())
csv_file = StringIO(file_data)
reader = csv.reader(csv_file)
csv_data = [row for row in reader]
fields = [
field for field in Asset._meta.fields
if field.name not in [
'date_created'
]
]
header_ = csv_data[0]
mapping_reverse = {field.verbose_name: field.name for field in fields}
attr = [mapping_reverse.get(n, None) for n in header_]
if None in attr:
data = {'valid': False,
'msg': 'Must be same format as '
'template or export file'}
return self.render_json_response(data)
created, updated, failed = [], [], []
assets = []
for row in csv_data[1:]:
if set(row) == {''}:
continue
asset_dict = dict(zip(attr, row))
id_ = asset_dict.pop('id', 0)
for k, v in asset_dict.items():
if k == 'is_active':
v = True if v in ['TRUE', 1, 'true'] else False
elif k == 'admin_user':
v = get_object_or_none(AdminUser, name=v)
elif k in ['port', 'cpu_count', 'cpu_cores']:
try:
v = int(v)
except ValueError:
v = 0
else:
continue
asset_dict[k] = v
asset = get_object_or_none(Asset, id=id_) if is_uuid(id_) else None
if not asset:
try:
if len(Asset.objects.filter(hostname=asset_dict.get('hostname'))):
raise Exception(_('already exists'))
asset = Asset.objects.create(**asset_dict)
created.append(asset_dict['hostname'])
assets.append(asset)
except Exception as e:
failed.append('%s: %s' % (asset_dict['hostname'], str(e)))
else:
for k, v in asset_dict.items():
if v:
setattr(asset, k, v)
try:
asset.save()
updated.append(asset_dict['hostname'])
except Exception as e:
failed.append('%s: %s' % (asset_dict['hostname'], str(e)))
data = {
'created': created,
'created_info': 'Created {}'.format(len(created)),
'updated': updated,
'updated_info': 'Updated {}'.format(len(updated)),
'failed': failed,
'failed_info': 'Failed {}'.format(len(failed)),
'valid': True,
'msg': 'Created: {}. Updated: {}, Error: {}'.format(
len(created), len(updated), len(failed))
}
return self.render_json_response(data)

View File

@@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
#
from django.views.generic import TemplateView, CreateView, \
UpdateView, DeleteView, DetailView
from django.utils.translation import ugettext_lazy as _
from django.urls import reverse_lazy
from common.mixins import AdminUserRequiredMixin
from common.const import create_success_msg, update_success_msg
from ..models import Label
from ..forms import LabelForm
__all__ = (
"LabelListView", "LabelCreateView", "LabelUpdateView",
"LabelDetailView", "LabelDeleteView",
)
class LabelListView(AdminUserRequiredMixin, TemplateView):
template_name = 'assets/label_list.html'
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Label list'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class LabelCreateView(AdminUserRequiredMixin, CreateView):
model = Label
template_name = 'assets/label_create_update.html'
form_class = LabelForm
success_url = reverse_lazy('assets:label-list')
success_message = create_success_msg
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Create label'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class LabelUpdateView(AdminUserRequiredMixin, UpdateView):
model = Label
template_name = 'assets/label_create_update.html'
form_class = LabelForm
success_url = reverse_lazy('assets:label-list')
success_message = update_success_msg
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Update label'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class LabelDetailView(AdminUserRequiredMixin, DetailView):
pass
class LabelDeleteView(AdminUserRequiredMixin, DeleteView):
model = Label
template_name = 'delete_confirm.html'
success_url = reverse_lazy('assets:label-list')

View File

@@ -0,0 +1,99 @@
# ~*~ coding: utf-8 ~*~
from django.utils.translation import ugettext as _
from django.views.generic import TemplateView
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from django.urls import reverse_lazy
from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic.detail import DetailView
from common.const import create_success_msg, update_success_msg
from ..forms import SystemUserForm
from ..models import SystemUser, Node
from ..hands import AdminUserRequiredMixin
__all__ = [
'SystemUserCreateView', 'SystemUserUpdateView',
'SystemUserDetailView', 'SystemUserDeleteView',
'SystemUserAssetView', 'SystemUserListView',
]
class SystemUserListView(AdminUserRequiredMixin, TemplateView):
template_name = 'assets/system_user_list.html'
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('System user list'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
model = SystemUser
form_class = SystemUserForm
template_name = 'assets/system_user_create.html'
success_url = reverse_lazy('assets:system-user-list')
success_message = create_success_msg
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Create system user'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class SystemUserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
model = SystemUser
form_class = SystemUserForm
template_name = 'assets/system_user_update.html'
success_url = reverse_lazy('assets:system-user-list')
success_message = update_success_msg
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Update system user')
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class SystemUserDetailView(AdminUserRequiredMixin, DetailView):
template_name = 'assets/system_user_detail.html'
context_object_name = 'system_user'
model = SystemUser
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('System user detail'),
'nodes_remain': Node.objects.exclude(systemuser=self.object)
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class SystemUserDeleteView(AdminUserRequiredMixin, DeleteView):
model = SystemUser
template_name = 'delete_confirm.html'
success_url = reverse_lazy('assets:system-user-list')
class SystemUserAssetView(AdminUserRequiredMixin, DetailView):
model = SystemUser
template_name = 'assets/system_user_asset.html'
context_object_name = 'system_user'
def get_context_data(self, **kwargs):
context = {
'app': _('assets'),
'action': _('System user asset'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)

76
apps/common/README.md Normal file
View File

@@ -0,0 +1,76 @@
# Common app
Common app provide common view, function or others.
Common app shouldn't rely on other apps, because It may lead to cycle
import.
If your want to implement some function or class, you should think
whether other app use or not. If yes, You should make in common.
If the ability more relate to your app tightness, It's mean your app
provide this ability, not common, You should write it on your app utils.
## Celery usage
Jumpserver use celery to run task async. Using redis as the broker, so
you should run a redis instance
#### Run redis
$ yum -y install redis
or
$ docker run -name jumpserver-redis -d -p 6379:6379 redis redis-server
#### Write tasks in app_name/tasks.py
ops/tasks.py
```
from __future__ import absolute_import
import time
from celery import shared_task
from common import celery_app
@shared_task
def longtime_add(x, y):
print 'long time task begins'
# sleep 5 seconds
time.sleep(5)
print 'long time task finished'
return x + y
@celery_app.task(name='hello-world')
def hello():
print 'hello world!'
```
#### Run celery in development
```
$ cd apps
$ celery -A common worker -l info
```
#### Test using task
```
$ ./manage.py shell
>>> from ops.tasks import longtime_add
>>> res = longtime_add.delay(1, 2)
>>> res.get()
```

5
apps/common/__init__.py Normal file
View File

@@ -0,0 +1,5 @@
from __future__ import absolute_import
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app

107
apps/common/api.py Normal file
View File

@@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
#
import json
from rest_framework.views import APIView
from rest_framework.views import Response
from ldap3 import Server, Connection
from django.core.mail import get_connection, send_mail
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from .permissions import IsSuperUser, IsAppUser
from .serializers import MailTestSerializer, LDAPTestSerializer
class MailTestingAPI(APIView):
permission_classes = (IsSuperUser,)
serializer_class = MailTestSerializer
success_message = _("Test mail sent to {}, please check")
def post(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
email_host_user = serializer.validated_data["EMAIL_HOST_USER"]
kwargs = {
"host": serializer.validated_data["EMAIL_HOST"],
"port": serializer.validated_data["EMAIL_PORT"],
"username": serializer.validated_data["EMAIL_HOST_USER"],
"password": serializer.validated_data["EMAIL_HOST_PASSWORD"],
"use_ssl": serializer.validated_data["EMAIL_USE_SSL"],
"use_tls": serializer.validated_data["EMAIL_USE_TLS"]
}
connection = get_connection(timeout=5, **kwargs)
try:
connection.open()
except Exception as e:
return Response({"error": str(e)}, status=401)
try:
send_mail("Test", "Test smtp setting", email_host_user,
[email_host_user], connection=connection)
except Exception as e:
return Response({"error": str(e)}, status=401)
return Response({"msg": self.success_message.format(email_host_user)})
else:
return Response({"error": str(serializer.errors)}, status=401)
class LDAPTestingAPI(APIView):
permission_classes = (IsSuperUser,)
serializer_class = LDAPTestSerializer
success_message = _("Test ldap success")
def post(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
host = serializer.validated_data["AUTH_LDAP_SERVER_URI"]
bind_dn = serializer.validated_data["AUTH_LDAP_BIND_DN"]
password = serializer.validated_data["AUTH_LDAP_BIND_PASSWORD"]
use_ssl = serializer.validated_data.get("AUTH_LDAP_START_TLS", False)
search_ou = serializer.validated_data["AUTH_LDAP_SEARCH_OU"]
search_filter = serializer.validated_data["AUTH_LDAP_SEARCH_FILTER"]
attr_map = serializer.validated_data["AUTH_LDAP_USER_ATTR_MAP"]
try:
attr_map = json.loads(attr_map)
except json.JSONDecodeError:
return Response({"error": "AUTH_LDAP_USER_ATTR_MAP not valid"}, status=401)
server = Server(host, use_ssl=use_ssl)
conn = Connection(server, bind_dn, password)
try:
conn.bind()
except Exception as e:
return Response({"error": str(e)}, status=401)
ok = conn.search(search_ou, search_filter % ({"user": "*"}),
attributes=list(attr_map.values()))
if not ok:
return Response({"error": "Search no entry matched"}, status=401)
users = []
for entry in conn.entries:
user = {}
for attr, mapping in attr_map.items():
if hasattr(entry, mapping):
user[attr] = getattr(entry, mapping)
users.append(user)
if len(users) > 0:
return Response({"msg": _("Match {} s users").format(len(users))})
else:
return Response({"error": "Have user but attr mapping error"}, status=401)
else:
return Response({"error": str(serializer.errors)}, status=401)
class DjangoSettingsAPI(APIView):
def get(self, request):
if not settings.DEBUG:
return Response('Only debug mode support')
configs = {}
for i in dir(settings):
if i.isupper():
configs[i] = str(getattr(settings, i))
return Response(configs)

13
apps/common/apps.py Normal file
View File

@@ -0,0 +1,13 @@
from __future__ import unicode_literals
from django.apps import AppConfig
class CommonConfig(AppConfig):
name = 'common'
def ready(self):
from . import signals_handler
from .signals import django_ready
django_ready.send(self.__class__)
return super().ready()

201
apps/common/celery.py Normal file
View File

@@ -0,0 +1,201 @@
# ~*~ coding: utf-8 ~*~
import os
import json
from functools import wraps
from celery import Celery, subtask
from celery.signals import worker_ready, worker_shutdown
from django.db.utils import ProgrammingError, OperationalError
from .utils import get_logger
logger = get_logger(__file__)
# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'jumpserver.settings')
from django.conf import settings
from django.core.cache import cache
app = Celery('jumpserver')
# Using a string here means the worker will not have to
# pickle the object when using Windows.
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks(lambda: [app_config.split('.')[0] for app_config in settings.INSTALLED_APPS])
def create_or_update_celery_periodic_tasks(tasks):
from django_celery_beat.models import PeriodicTask, IntervalSchedule, CrontabSchedule
"""
:param tasks: {
'add-every-monday-morning': {
'task': 'tasks.add' # A registered celery task,
'interval': 30,
'crontab': "30 7 * * *",
'args': (16, 16),
'kwargs': {},
'enabled': False,
},
}
:return:
"""
# Todo: check task valid, task and callback must be a celery task
for name, detail in tasks.items():
interval = None
crontab = None
try:
IntervalSchedule.objects.all().count()
except (ProgrammingError, OperationalError):
return None
if isinstance(detail.get("interval"), int):
intervals = IntervalSchedule.objects.filter(
every=detail["interval"], period=IntervalSchedule.SECONDS
)
if intervals:
interval = intervals[0]
else:
interval = IntervalSchedule.objects.create(
every=detail['interval'],
period=IntervalSchedule.SECONDS,
)
elif isinstance(detail.get("crontab"), str):
try:
minute, hour, day, month, week = detail["crontab"].split()
except ValueError:
raise SyntaxError("crontab is not valid")
kwargs = dict(
minute=minute, hour=hour, day_of_week=week,
day_of_month=day, month_of_year=month,
)
contabs = CrontabSchedule.objects.filter(
**kwargs
)
if contabs:
crontab = contabs[0]
else:
crontab = CrontabSchedule.objects.create(**kwargs)
else:
raise SyntaxError("Schedule is not valid")
defaults = dict(
interval=interval,
crontab=crontab,
name=name,
task=detail['task'],
args=json.dumps(detail.get('args', [])),
kwargs=json.dumps(detail.get('kwargs', {})),
enabled=detail.get('enabled', True),
)
task = PeriodicTask.objects.update_or_create(
defaults=defaults, name=name,
)
return task
def disable_celery_periodic_task(task_name):
from django_celery_beat.models import PeriodicTask
PeriodicTask.objects.filter(name=task_name).update(enabled=False)
def delete_celery_periodic_task(task_name):
from django_celery_beat.models import PeriodicTask
PeriodicTask.objects.filter(name=task_name).delete()
__REGISTER_PERIODIC_TASKS = []
__AFTER_APP_SHUTDOWN_CLEAN_TASKS = []
__AFTER_APP_READY_RUN_TASKS = []
def register_as_period_task(crontab=None, interval=None):
"""
Warning: Task must be have not any args and kwargs
:param crontab: "* * * * *"
:param interval: 60*60*60
:return:
"""
if crontab is None and interval is None:
raise SyntaxError("Must set crontab or interval one")
def decorate(func):
if crontab is None and interval is None:
raise SyntaxError("Interval and crontab must set one")
# Because when this decorator run, the task was not created,
# So we can't use func.name
name = '{func.__module__}.{func.__name__}'.format(func=func)
if name not in __REGISTER_PERIODIC_TASKS:
create_or_update_celery_periodic_tasks({
name: {
'task': name,
'interval': interval,
'crontab': crontab,
'args': (),
'enabled': True,
}
})
__REGISTER_PERIODIC_TASKS.append(name)
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
return decorate
def after_app_ready_start(func):
# Because when this decorator run, the task was not created,
# So we can't use func.name
name = '{func.__module__}.{func.__name__}'.format(func=func)
if name not in __AFTER_APP_READY_RUN_TASKS:
__AFTER_APP_READY_RUN_TASKS.append(name)
@wraps(func)
def decorate(*args, **kwargs):
return func(*args, **kwargs)
return decorate
def after_app_shutdown_clean(func):
# Because when this decorator run, the task was not created,
# So we can't use func.name
name = '{func.__module__}.{func.__name__}'.format(func=func)
if name not in __AFTER_APP_READY_RUN_TASKS:
__AFTER_APP_SHUTDOWN_CLEAN_TASKS.append(name)
@wraps(func)
def decorate(*args, **kwargs):
return func(*args, **kwargs)
return decorate
@worker_ready.connect
def on_app_ready(sender=None, headers=None, body=None, **kwargs):
if cache.get("CELERY_APP_READY", 0) == 1:
return
cache.set("CELERY_APP_READY", 1, 10)
logger.debug("App ready signal recv")
logger.debug("Start need start task: [{}]".format(
", ".join(__AFTER_APP_READY_RUN_TASKS))
)
for task in __AFTER_APP_READY_RUN_TASKS:
subtask(task).delay()
@worker_shutdown.connect
def after_app_shutdown(sender=None, headers=None, body=None, **kwargs):
if cache.get("CELERY_APP_SHUTDOWN", 0) == 1:
return
cache.set("CELERY_APP_SHUTDOWN", 1, 10)
from django_celery_beat.models import PeriodicTask
logger.debug("App shutdown signal recv")
logger.debug("Clean need cleaned period tasks: [{}]".format(
', '.join(__AFTER_APP_SHUTDOWN_CLEAN_TASKS))
)
PeriodicTask.objects.filter(name__in=__AFTER_APP_SHUTDOWN_CLEAN_TASKS).delete()

82
apps/common/compat.py Normal file
View File

@@ -0,0 +1,82 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
"""
兼容Python版本
"""
import sys
is_py2 = (sys.version_info[0] == 2)
is_py3 = (sys.version_info[0] == 3)
try:
import simplejson as json
except (ImportError, SyntaxError):
import json
if is_py2:
def to_bytes(data):
"""若输入为unicode 则转为utf-8编码的bytes其他则原样返回。"""
if isinstance(data, unicode):
return data.encode('utf-8')
else:
return data
def to_string(data):
"""把输入转换为str对象"""
return to_bytes(data)
def to_unicode(data):
"""把输入转换为unicode要求输入是unicode或者utf-8编码的bytes。"""
if isinstance(data, bytes):
return data.decode('utf-8')
else:
return data
def stringify(input):
if isinstance(input, dict):
return dict([(stringify(key), stringify(value)) for key,value in input.iteritems()])
elif isinstance(input, list):
return [stringify(element) for element in input]
elif isinstance(input, unicode):
return input.encode('utf-8')
else:
return input
builtin_str = str
bytes = str
str = unicode
elif is_py3:
def to_bytes(data):
"""若输入为str即unicode则转为utf-8编码的bytes其他则原样返回"""
if isinstance(data, str):
return data.encode(encoding='utf-8')
else:
return data
def to_string(data):
"""若输入为bytes则认为是utf-8编码并返回str"""
if isinstance(data, bytes):
return data.decode('utf-8')
else:
return data
def to_unicode(data):
"""把输入转换为unicode要求输入是unicode或者utf-8编码的bytes。"""
return to_string(data)
def stringify(input):
return input
builtin_str = str
bytes = bytes
str = str

7
apps/common/const.py Normal file
View File

@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext as _
create_success_msg = _("<b>%(name)s</b> was created successfully")
update_success_msg = _("<b>%(name)s</b> was updated successfully")

View File

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

45
apps/common/fields.py Normal file
View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
#
import json
from django import forms
from django.utils import six
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _
from rest_framework import serializers
class DictField(forms.Field):
widget = forms.Textarea
def to_python(self, value):
"""Returns a Python boolean object."""
# Explicitly check for the string 'False', which is what a hidden field
# will submit for False. Also check for '0', since this is what
# RadioSelect will provide. Because bool("True") == bool('1') == True,
# we don't need to handle that explicitly.
if isinstance(value, six.string_types):
try:
value = json.loads(value)
return value
except json.JSONDecodeError:
return ValidationError(_("Not a valid json"))
else:
return ValidationError(_("Not a string type"))
def validate(self, value):
if isinstance(value, ValidationError):
raise value
if not value and self.required:
raise ValidationError(self.error_messages['required'], code='required')
def has_changed(self, initial, data):
# Sometimes data or initial may be a string equivalent of a boolean
# so we should run it through to_python first to get a boolean value
return self.to_python(initial) != self.to_python(data)
class StringIDField(serializers.Field):
def to_representation(self, value):
return {"pk": value.pk, "name": value.__str__()}

170
apps/common/forms.py Normal file
View File

@@ -0,0 +1,170 @@
# -*- coding: utf-8 -*-
#
import json
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.utils.html import escape
from django.db import transaction
from django.conf import settings
from .models import Setting
from .fields import DictField
def to_model_value(value):
try:
return json.dumps(value)
except json.JSONDecodeError:
return None
def to_form_value(value):
try:
data = json.loads(value)
if isinstance(data, dict):
data = value
return data
except json.JSONDecodeError:
return ""
class BaseForm(forms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
db_settings = Setting.objects.all()
for name, field in self.fields.items():
db_value = getattr(db_settings, name).value
django_value = getattr(settings, name) if hasattr(settings, name) else None
if db_value is False or db_value:
field.initial = to_form_value(db_value)
elif django_value is False or django_value:
field.initial = to_form_value(to_model_value(django_value))
def save(self, category="default"):
if not self.is_bound:
raise ValueError("Form is not bound")
db_settings = Setting.objects.all()
if self.is_valid():
with transaction.atomic():
for name, value in self.cleaned_data.items():
field = self.fields[name]
if isinstance(field.widget, forms.PasswordInput) and not value:
continue
if value == to_form_value(getattr(db_settings, name).value):
continue
defaults = {
'name': name,
'category': category,
'value': to_model_value(value)
}
Setting.objects.update_or_create(defaults=defaults, name=name)
else:
raise ValueError(self.errors)
class BasicSettingForm(BaseForm):
SITE_URL = forms.URLField(
label=_("Current SITE URL"),
help_text="eg: http://jumpserver.abc.com:8080"
)
USER_GUIDE_URL = forms.URLField(
label=_("User Guide URL"), required=False,
help_text=_("User first login update profile done redirect to it")
)
EMAIL_SUBJECT_PREFIX = forms.CharField(
max_length=1024, label=_("Email Subject Prefix"),
initial="[Jumpserver] "
)
class EmailSettingForm(BaseForm):
EMAIL_HOST = forms.CharField(
max_length=1024, label=_("SMTP host"), initial='smtp.jumpserver.org'
)
EMAIL_PORT = forms.CharField(max_length=5, label=_("SMTP port"), initial=25)
EMAIL_HOST_USER = forms.CharField(
max_length=128, label=_("SMTP user"), initial='noreply@jumpserver.org'
)
EMAIL_HOST_PASSWORD = forms.CharField(
max_length=1024, label=_("SMTP password"), widget=forms.PasswordInput,
required=False, help_text=_("Some provider use token except password")
)
EMAIL_USE_SSL = forms.BooleanField(
label=_("Use SSL"), initial=False, required=False,
help_text=_("If SMTP port is 465, may be select")
)
EMAIL_USE_TLS = forms.BooleanField(
label=_("Use TLS"), initial=False, required=False,
help_text=_("If SMTP port is 587, may be select")
)
class LDAPSettingForm(BaseForm):
AUTH_LDAP_SERVER_URI = forms.CharField(
label=_("LDAP server"), initial='ldap://localhost:389'
)
AUTH_LDAP_BIND_DN = forms.CharField(
label=_("Bind DN"), initial='cn=admin,dc=jumpserver,dc=org'
)
AUTH_LDAP_BIND_PASSWORD = forms.CharField(
label=_("Password"), initial='',
widget=forms.PasswordInput, required=False
)
AUTH_LDAP_SEARCH_OU = forms.CharField(
label=_("User OU"), initial='ou=tech,dc=jumpserver,dc=org'
)
AUTH_LDAP_SEARCH_FILTER = forms.CharField(
label=_("User search filter"), initial='(cn=%(user)s)',
help_text=_("Choice may be (cn|uid|sAMAccountName)=%(user)s)")
)
AUTH_LDAP_USER_ATTR_MAP = DictField(
label=_("User attr map"),
initial=json.dumps({
"username": "cn",
"name": "sn",
"email": "mail"
}),
help_text=_(
"User attr map present how to map LDAP user attr to jumpserver, username,name,email is jumpserver attr")
)
# AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU
# AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER
AUTH_LDAP_START_TLS = forms.BooleanField(
label=_("Use SSL"), initial=False, required=False
)
AUTH_LDAP = forms.BooleanField(label=_("Enable LDAP auth"), initial=False, required=False)
class TerminalSettingForm(BaseForm):
SORT_BY_CHOICES = (
('hostname', _('Hostname')),
('ip', _('IP')),
)
TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField(
choices=SORT_BY_CHOICES, initial='hostname', label=_("List sort by")
)
TERMINAL_HEARTBEAT_INTERVAL = forms.IntegerField(
initial=5, label=_("Heartbeat interval"), help_text=_("Units: seconds")
)
TERMINAL_PASSWORD_AUTH = forms.BooleanField(
initial=True, required=False, label=_("Password auth")
)
TERMINAL_PUBLIC_KEY_AUTH = forms.BooleanField(
initial=True, required=False, label=_("Public key auth")
)
TERMINAL_COMMAND_STORAGE = DictField(
label=_("Command storage"), help_text=_(
"Set terminal storage setting, `default` is the using as default,"
"You can set other storage and some terminal using"
)
)
TERMINAL_REPLAY_STORAGE = DictField(
label=_("Replay storage"), help_text=_(
"Set replay storage setting, `default` is the using as default,"
"You can set other storage and some terminal using"
)
)

126
apps/common/mixins.py Normal file
View File

@@ -0,0 +1,126 @@
# coding: utf-8
from django.db import models
from django.http import JsonResponse
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.mixins import UserPassesTestMixin
class NoDeleteQuerySet(models.query.QuerySet):
def delete(self):
return self.update(is_discard=True, discard_time=timezone.now())
class NoDeleteManager(models.Manager):
def get_all(self):
return NoDeleteQuerySet(self.model, using=self._db)
def get_queryset(self):
return NoDeleteQuerySet(self.model, using=self._db).filter(is_discard=False)
def get_deleted(self):
return NoDeleteQuerySet(self.model, using=self._db).filter(is_discard=True)
class NoDeleteModelMixin(models.Model):
is_discard = models.BooleanField(verbose_name=_("is discard"), default=False)
discard_time = models.DateTimeField(verbose_name=_("discard time"), null=True, blank=True)
objects = NoDeleteManager()
class Meta:
abstract = True
def delete(self):
self.is_discard = True
self.discard_time = timezone.now()
return self.save()
class JSONResponseMixin(object):
"""JSON mixin"""
@staticmethod
def render_json_response(context):
return JsonResponse(context)
class IDInFilterMixin(object):
def filter_queryset(self, queryset):
queryset = super(IDInFilterMixin, self).filter_queryset(queryset)
id_list = self.request.query_params.get('id__in')
if id_list:
import json
try:
ids = json.loads(id_list)
except Exception as e:
return queryset
if isinstance(ids, list):
queryset = queryset.filter(id__in=ids)
return queryset
class BulkSerializerMixin(object):
"""
Become rest_framework_bulk not support uuid as a primary key
so rewrite it. https://github.com/miki725/django-rest-framework-bulk/issues/66
"""
def to_internal_value(self, data):
from rest_framework_bulk import BulkListSerializer
ret = super(BulkSerializerMixin, self).to_internal_value(data)
id_attr = getattr(self.Meta, 'update_lookup_field', 'id')
request_method = getattr(getattr(self.context.get('view'), 'request'), 'method', '')
# add update_lookup_field field back to validated data
# since super by default strips out read-only fields
# hence id will no longer be present in validated_data
if all((isinstance(self.root, BulkListSerializer),
id_attr,
request_method in ('PUT', 'PATCH'))):
id_field = self.fields[id_attr]
if data.get("id"):
id_value = id_field.to_internal_value(data.get("id"))
else:
id_value = id_field.to_internal_value(data.get("pk"))
ret[id_attr] = id_value
return ret
class DatetimeSearchMixin:
date_format = '%Y-%m-%d'
date_from = date_to = None
def get(self, request, *args, **kwargs):
date_from_s = self.request.GET.get('date_from')
date_to_s = self.request.GET.get('date_to')
if date_from_s:
date_from = timezone.datetime.strptime(date_from_s, self.date_format)
tz = timezone.get_current_timezone()
self.date_from = tz.localize(date_from)
else:
self.date_from = timezone.now() - timezone.timedelta(7)
if date_to_s:
date_to = timezone.datetime.strptime(
date_to_s + ' 23:59:59', self.date_format + ' %H:%M:%S'
)
self.date_to = date_to.replace(
tzinfo=timezone.get_current_timezone()
)
else:
self.date_to = timezone.now()
return super().get(request, *args, **kwargs)
class AdminUserRequiredMixin(UserPassesTestMixin):
def test_func(self):
if not self.request.user.is_authenticated:
return False
elif not self.request.user.is_superuser:
self.raise_exception = True
return False
return True

81
apps/common/models.py Normal file
View File

@@ -0,0 +1,81 @@
import json
import ldap
from django.db import models
from django.db.utils import ProgrammingError, OperationalError
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django_auth_ldap.config import LDAPSearch
class SettingQuerySet(models.QuerySet):
def __getattr__(self, item):
instances = self.filter(name=item)
if len(instances) == 1:
return instances[0]
else:
return Setting()
class SettingManager(models.Manager):
def get_queryset(self):
return SettingQuerySet(self.model, using=self._db)
class Setting(models.Model):
name = models.CharField(max_length=128, unique=True, verbose_name=_("Name"))
value = models.TextField(verbose_name=_("Value"))
category = models.CharField(max_length=128, default="default")
enabled = models.BooleanField(verbose_name=_("Enabled"), default=True)
comment = models.TextField(verbose_name=_("Comment"))
objects = SettingManager()
def __str__(self):
return self.name
@property
def cleaned_value(self):
try:
return json.loads(self.value)
except json.JSONDecodeError:
return None
@cleaned_value.setter
def cleaned_value(self, item):
try:
v = json.dumps(item)
self.value = v
except json.JSONDecodeError as e:
raise ValueError("Json dump error: {}".format(str(e)))
@classmethod
def refresh_all_settings(cls):
try:
settings_list = cls.objects.all()
for setting in settings_list:
setting.refresh_setting()
except (ProgrammingError, OperationalError):
pass
def refresh_setting(self):
try:
value = json.loads(self.value)
except json.JSONDecodeError:
return
setattr(settings, self.name, value)
if self.name == "AUTH_LDAP":
if self.cleaned_value and settings.AUTH_LDAP_BACKEND not in settings.AUTHENTICATION_BACKENDS:
settings.AUTHENTICATION_BACKENDS.insert(0, settings.AUTH_LDAP_BACKEND)
elif not self.cleaned_value and settings.AUTH_LDAP_BACKEND in settings.AUTHENTICATION_BACKENDS:
settings.AUTHENTICATION_BACKENDS.remove(settings.AUTH_LDAP_BACKEND)
if self.name == "AUTH_LDAP_SEARCH_FILTER":
settings.AUTH_LDAP_USER_SEARCH = LDAPSearch(
settings.AUTH_LDAP_SEARCH_OU, ldap.SCOPE_SUBTREE,
settings.AUTH_LDAP_SEARCH_FILTER,
)
class Meta:
db_table = "settings"

View File

@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
#
from rest_framework import permissions
class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission):
"""Allows access to valid user, is active and not expired"""
def has_permission(self, request, view):
return super(IsValidUser, self).has_permission(request, view) \
and request.user.is_valid
class IsAppUser(IsValidUser):
"""Allows access only to app user """
def has_permission(self, request, view):
return super(IsAppUser, self).has_permission(request, view) \
and request.user.is_app
class IsSuperUser(IsValidUser):
"""Allows access only to superuser"""
def has_permission(self, request, view):
return super(IsSuperUser, self).has_permission(request, view) \
and request.user.is_superuser
class IsSuperUserOrAppUser(IsValidUser):
"""Allows access between superuser and app user"""
def has_permission(self, request, view):
return super(IsSuperUserOrAppUser, self).has_permission(request, view) \
and (request.user.is_superuser or request.user.is_app)
class IsSuperUserOrAppUserOrUserReadonly(IsSuperUserOrAppUser):
def has_permission(self, request, view):
if IsValidUser.has_permission(self, request, view) \
and request.method in permissions.SAFE_METHODS:
return True
else:
return IsSuperUserOrAppUser.has_permission(self, request, view)
class IsCurrentUserOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj == request.user

View File

@@ -0,0 +1,21 @@
from rest_framework import serializers
class MailTestSerializer(serializers.Serializer):
EMAIL_HOST = serializers.CharField(max_length=1024, required=True)
EMAIL_PORT = serializers.IntegerField(default=25)
EMAIL_HOST_USER = serializers.CharField(max_length=1024)
EMAIL_HOST_PASSWORD = serializers.CharField()
EMAIL_USE_SSL = serializers.BooleanField(default=False)
EMAIL_USE_TLS = serializers.BooleanField(default=False)
class LDAPTestSerializer(serializers.Serializer):
AUTH_LDAP_SERVER_URI = serializers.CharField(max_length=1024)
AUTH_LDAP_BIND_DN = serializers.CharField(max_length=1024)
AUTH_LDAP_BIND_PASSWORD = serializers.CharField()
AUTH_LDAP_SEARCH_OU = serializers.CharField()
AUTH_LDAP_SEARCH_FILTER = serializers.CharField()
AUTH_LDAP_USER_ATTR_MAP = serializers.CharField()
AUTH_LDAP_START_TLS = serializers.BooleanField(required=False)

7
apps/common/signals.py Normal file
View File

@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
#
from django.dispatch import Signal
django_ready = Signal()
ldap_auth_enable = Signal(providing_args=["enabled"])

View File

@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
#
from django.dispatch import receiver
from django.db.models.signals import post_save
from django.conf import settings
from django.db.utils import ProgrammingError, OperationalError
from .models import Setting
from .utils import get_logger
from .signals import django_ready, ldap_auth_enable
logger = get_logger(__file__)
@receiver(post_save, sender=Setting, dispatch_uid="my_unique_identifier")
def refresh_settings_on_changed(sender, instance=None, **kwargs):
logger.debug("Receive setting item change")
logger.debug(" - refresh setting: {}".format(instance.name))
if instance:
instance.refresh_setting()
@receiver(django_ready, dispatch_uid="my_unique_identifier")
def refresh_all_settings_on_django_ready(sender, **kwargs):
logger.debug("Receive django ready signal")
logger.debug(" - fresh all settings")
try:
Setting.refresh_all_settings()
except (ProgrammingError, OperationalError):
pass
@receiver(ldap_auth_enable, dispatch_uid="my_unique_identifier")
def ldap_auth_on_changed(sender, enabled=True, **kwargs):
if enabled:
logger.debug("Enable LDAP auth")
if settings.AUTH_LDAP_BACKEND not in settings.AUTH_LDAP_BACKEND:
settings.AUTHENTICATION_BACKENDS.insert(0, settings.AUTH_LDAP_BACKEND)
else:
logger.debug("Disable LDAP auth")
if settings.AUTH_LDAP_BACKEND in settings.AUTHENTICATION_BACKENDS:
settings.AUTHENTICATION_BACKENDS.remove(settings.AUTH_LDAP_BACKEND)

33
apps/common/tasks.py Normal file
View File

@@ -0,0 +1,33 @@
from django.core.mail import send_mail
from django.conf import settings
from .celery import app
from .utils import get_logger
logger = get_logger(__file__)
@app.task
def send_mail_async(*args, **kwargs):
""" Using celery to send email async
You can use it as django send_mail function
Example:
send_mail_sync.delay(subject, message, from_mail, recipient_list, fail_silently=False, html_message=None)
Also you can ignore the from_mail, unlike django send_mail, from_email is not a require args:
Example:
send_mail_sync.delay(subject, message, recipient_list, fail_silently=False, html_message=None)
"""
if len(args) == 3:
args = list(args)
args[0] = settings.EMAIL_SUBJECT_PREFIX + args[0]
args.insert(2, settings.EMAIL_HOST_USER)
args = tuple(args)
try:
send_mail(*args, **kwargs)
except Exception as e:
logger.error("Sending mail error: {}".format(e))

View File

@@ -0,0 +1,103 @@
{% extends 'base.html' %}
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% load common_tags %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li class="active">
<a href="" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
</li>
<li>
<a href="{% url 'settings:email-setting' %}" class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
</li>
<li>
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li>
<li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-12" style="padding-left:0">
<div class="ibox-content" style="border-width: 0;padding-top: 40px;">
<form action="" method="post" class="form-horizontal">
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
{% csrf_token %}
{% for field in form %}
{% if not field.field|is_bool_field %}
{% bootstrap_field field layout="horizontal" %}
{% else %}
<div class="form-group">
<label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label>
<div class="col-sm-8">
<div class="col-sm-1">
{{ field }}
</div>
<div class="col-sm-9">
<span class="help-block" >{{ field.help_text }}</span>
</div>
</div>
</div>
{% endif %}
{% endfor %}
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function () {
})
.on("click", ".btn-test", function () {
var data = {};
var form = $("form").serializeArray();
$.each(form, function (i, field) {
data[field.name] = field.value;
});
var the_url = "{% url 'api-common:mail-testing' %}";
function error(message) {
toastr.error(message)
}
function success(message) {
toastr.success(message.msg)
}
APIUpdateAttr({
url: the_url,
body: JSON.stringify(data),
method: "POST",
flash_message: false,
success: success,
error: error
});
})
</script>
{% endblock %}

View File

@@ -0,0 +1,104 @@
{% extends 'base.html' %}
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% load common_tags %}
{% 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 'settings:basic-setting' %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
</li>
<li class="active">
<a href="{% url 'settings:email-setting' %}" class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
</li>
<li>
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li>
<li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-12" style="padding-left:0">
<div class="ibox-content" style="border-width: 0;padding-top: 40px;">
<form action="" method="post" class="form-horizontal">
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
{% csrf_token %}
{% for field in form %}
{% if not field.field|is_bool_field %}
{% bootstrap_field field layout="horizontal" %}
{% else %}
<div class="form-group">
<label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label>
<div class="col-sm-8">
<div class="col-sm-1">
{{ field }}
</div>
<div class="col-sm-9">
<span class="help-block" >{{ field.help_text }}</span>
</div>
</div>
</div>
{% endif %}
{% endfor %}
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-default btn-test" type="button"> {% trans 'Test connection' %}</button>
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function () {
})
.on("click", ".btn-test", function () {
var data = {};
var form = $("form").serializeArray();
$.each(form, function (i, field) {
data[field.name] = field.value;
});
var the_url = "{% url 'api-common:mail-testing' %}";
function error(message) {
toastr.error(message)
}
function success(message) {
toastr.success(message.msg)
}
APIUpdateAttr({
url: the_url,
body: JSON.stringify(data),
method: "POST",
flash_message: false,
success: success,
error: error
});
})
</script>
{% endblock %}

View File

@@ -0,0 +1,103 @@
{% extends 'base.html' %}
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% load common_tags %}
{% 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 'settings:basic-setting' %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
</li>
<li>
<a href="{% url 'settings:email-setting' %}" class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
</li>
<li class="active">
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li>
<li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-12" style="padding-left:0">
<div class="ibox-content" style="border-width: 0;padding-top: 40px;">
<form action="" method="post" class="form-horizontal">
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
{% csrf_token %}
{% for field in form %}
{% if not field.field|is_bool_field %}
{% bootstrap_field field layout="horizontal" %}
{% else %}
<div class="form-group">
<label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label>
<div class="col-sm-8">
<div class="col-sm-1">
{{ field }}
</div>
<div class="col-sm-9">
<span class="help-block" >{{ field.help_text }}</span>
</div>
</div>
</div>
{% endif %}
{% endfor %}
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-default btn-test" type="button"> {% trans 'Test connection' %}</button>
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function () {
})
.on("click", ".btn-test", function () {
var data = {};
var form = $("form").serializeArray();
$.each(form, function (i, field) {
data[field.name] = field.value;
});
var the_url = "{% url 'api-common:ldap-testing' %}";
function error(message) {
toastr.error(message)
}
function success(message) {
toastr.success(message.msg)
}
APIUpdateAttr({
url: the_url,
body: JSON.stringify(data),
method: "POST",
flash_message: false,
success: success,
error: error
});
})
</script>
{% endblock %}

View File

@@ -0,0 +1,150 @@
{% extends 'base.html' %}
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% load common_tags %}
{% 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 'settings:basic-setting' %}" class="text-center"><i
class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
</li>
<li>
<a href="{% url 'settings:email-setting' %}" class="text-center"><i
class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
</li>
<li>
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i
class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li>
<li class="active">
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i
class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-12" style="padding-left:0">
<div class="ibox-content" style="border-width: 0;padding-top: 40px;">
<form action="" method="post" class="form-horizontal">
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
{% csrf_token %}
<h3>{% trans "Basic setting" %}</h3>
{% for field in form %}
{% if not field.field|is_bool_field %}
{% bootstrap_field field layout="horizontal" %}
{% else %}
<div class="form-group">
<label for="{{ field.id_for_label }}"
class="col-sm-2 control-label">{{ field.label }}</label>
<div class="col-sm-8">
<div class="col-sm-1">
{{ field }}
</div>
<div class="col-sm-9">
<span class="help-block">{{ field.help_text }}</span>
</div>
</div>
</div>
{% endif %}
{% endfor %}
<div class="hr-line-dashed"></div>
<h3>{% trans "Command storage" %}</h3>
<table class="table table-hover " id="task-history-list-table">
<thead>
<tr>
<th>{% trans 'Name' %}</th>
<th>{% trans 'Type' %}</th>
</tr>
</thead>
<tbody>
{% for name, setting in command_storage.items %}
<tr>
<td>{{ name }}</td>
<td>{{ setting.TYPE }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="hr-line-dashed"></div>
<h3>{% trans "Replay storage" %}</h3>
<table class="table table-hover " id="task-history-list-table">
<thead>
<tr>
<th>{% trans 'Name' %}</th>
<th>{% trans 'Type' %}</th>
</tr>
</thead>
<tbody>
{% for name, setting in replay_storage.items %}
<tr>
<td>{{ name }}</td>
<td>{{ setting.TYPE }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary"
type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function () {
})
.on("click", ".btn-test", function () {
var data = {};
var form = $("form").serializeArray();
$.each(form, function (i, field) {
data[field.name] = field.value;
});
var the_url = "{% url 'api-common:ldap-testing' %}";
function error(message) {
toastr.error(message)
}
function success(message) {
toastr.success(message.msg)
}
APIUpdateAttr({
url: the_url,
body: JSON.stringify(data),
method: "POST",
flash_message: false,
success: success,
error: error
});
})
.on('click', '', function () {
})
</script>
{% endblock %}

View File

@@ -0,0 +1,102 @@
# ~*~ coding: utf-8 ~*~
from django import template
from django.utils import timezone
from django.utils.translation import gettext as _
from django.utils.html import escape
from django import forms
register = template.Library()
@register.filter
def join_queryset_attr(queryset, attr, delimiter=', '):
return delimiter.join([getattr(obj, attr, '') for obj in queryset])
@register.filter
def pagination_range(total_page, current_num=1, display=5):
"""Return Page range
:param total_page: Total numbers of paginator
:param current_num: current display page num
:param display: Display as many as [:display:] page
In order to display many page num on web like:
< 1 2 3 4 5 >
"""
try:
current_num = int(current_num)
except ValueError:
current_num = 1
half_display = int(display/2)
start = current_num - half_display if current_num > half_display else 1
if start + display <= total_page:
end = start + display
else:
end = total_page + 1
start = end - display if end > display else 1
return range(start, end)
@register.filter
def join_attr(seq, attr=None, sep=None):
if sep is None:
sep = ', '
if attr is not None:
seq = [getattr(obj, attr) for obj in seq]
return sep.join(seq)
@register.filter
def int_to_str(value):
return str(value)
@register.filter
def ts_to_date(ts):
try:
ts = float(ts)
except (TypeError, ValueError):
ts = 0
dt = timezone.datetime.fromtimestamp(ts).\
replace(tzinfo=timezone.get_current_timezone())
return dt.strftime('%Y-%m-%d %H:%M:%S')
@register.filter
def to_html(s):
return escape(s).replace('\n', '<br />')
@register.filter
def time_util_with_seconds(date_from, date_to):
if not date_from:
return ''
if not date_to:
return ''
date_to = timezone.now()
delta = date_to - date_from
seconds = delta.seconds
if seconds < 60:
return '{} s'.format(seconds)
elif seconds < 60*60:
return '{} m'.format(seconds//60)
else:
return '{} h'.format(seconds//3600)
@register.filter
def is_bool_field(field):
if isinstance(field, forms.BooleanField):
return True
else:
return False
@register.filter
def to_dict(data):
return dict(data)

View File

@@ -0,0 +1,13 @@
from __future__ import absolute_import
from django.conf.urls import url
from .. import api
app_name = 'common'
urlpatterns = [
url(r'^v1/mail/testing/$', api.MailTestingAPI.as_view(), name='mail-testing'),
url(r'^v1/ldap/testing/$', api.LDAPTestingAPI.as_view(), name='ldap-testing'),
url(r'^v1/django-settings/$', api.DjangoSettingsAPI.as_view(), name='django-settings'),
]

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