Compare commits

...

818 Commits
0.3.2 ... v0.4

Author SHA1 Message Date
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
ibuler
1959c685b9 new feature (connect) Ignore case in searching
finshed
2016-04-01 17:52:47 +08:00
673 changed files with 35476 additions and 27793 deletions

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

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

60
.gitignore vendored
View File

@@ -1,46 +1,26 @@
*.py[cod]
.idea
test.py
.DS_Store
db.sqlite3
# C extensions
*.so
# Packages
*.egg
*.egg-info
*.pyc
*.pyo
*.swp
.env
env
env*
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/*

1
.python-version Normal file
View File

@@ -0,0 +1 @@
system

22
Dockerfile Normal file
View File

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

16
Dockerfile-py3 Normal file
View File

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

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.

175
README.md
View File

@@ -1,80 +1,113 @@
## 写在前面
- 版本号变更 2.0 -> 0.2版本 3.0 -> 0.3版本
## Jumpserver
#欢迎使用Jumpserver
**Jumpserver** 是一款由python编写开源的跳板机(堡垒机)系统实现了跳板机应有的功能。基于ssh协议来管理客户端无需安装agent。
支持常见系统:
1. CentOS, RedHat, Fedora, Amazon Linux
2. Debian
3. SUSE, Ubuntu
4. FreeBSD
5. 其他ssh协议硬件设备
[![Python3](https://img.shields.io/badge/python-3.6-green.svg?style=plastic)](https://www.python.org/)
[![Django](https://img.shields.io/badge/django-1.11-brightgreen.svg?style=plastic)](https://www.djangoproject.com/)
[![Ansible](https://img.shields.io/badge/ansible-2.2.2.0-blue.svg?style=plastic)](https://www.ansible.com/)
[![Paramiko](https://img.shields.io/badge/paramiko-2.1.2-green.svg?style=plastic)](http://www.paramiko.org/)
###截图:
Jumpserver is a open source proxy server, developed by `Python` and `Django`, aim to help
companies to efficiently user, assets, authority and audit management
首页
Jumpserver是一款使用Python, Django开发的开源跳板机系统, 助力互联网企业高效 用户、资产、权限、审计 管理
### Feature 功能
- Auth 统一认证
- CMDB 资产管理
- Perm 统一授权
- Audit 审计
- LDAP AUTH 支持LDAP认证
- Web terminal
- SSH Server
### Environment 环境
* Python 3.6
* Django 1.11
### Install 安装
Using docker compose to setup it
使用docker compose 安装一键完成docker compose 安装见 docker官方
  $ docker-compose up
### Usage 使用
  1. Visit http://$HOST:8080 (访问 http://你的主机IP:8080 来访问 Jumpserver)
![webterminal](https://github.com/ibuler/static/raw/master/jumpserver3/index.jpg)
  2. Click left navigation visit Applications-Terminal and accept coco and luna register
(点击左侧 应用程序接受 Coco和Luna的注册)
  3. Click Assets-Admin user, Create admin user
(添加 管理用户)
4. Click Assets-System user, Create system user
(添加 系统用户)
  5. Click Assets-Asset, Add a asset
(添加 资产)
  6. Click Perms-Asset permission, Add a perm rule
(添加授权规则授权给admin)
  7. Connect ssh server coco (连接 ssh server coco)
ssh -p2222 $USER@$Host
  8. Visit web terminal server Luna, click server test connection
(访问 访问Luna点击左侧服务器连接测试)
http://$HOST:5000
### Snapshot 截图
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)
https://github.com/jumpserver/jumpserver/issues/438
### Demo
demo使用了开发者模式并发只能为1
- Jumpserver: [访问](http://demo.jumpserver.org:8080) 账号: admin 密码: admin
- Luna: [访问](http://demo.jumpserver.org:5000) 同Jumpserver认证
- Coco: ssh -p 2222 admin@demo.jumpserver.org 密码: admin
### ROADMAP
参见 https://github.com/jumpserver/jumpserver/milestone/2
### Docs 开发者文档
* [Project structure 项目结构描述](https://github.com/jumpserver/jumpserver/blob/dev/docs/project_structure.md)
* [Code style Python代码规范](https://github.com/jumpserver/jumpserver/blob/dev/docs/python_style_guide.md)
* [Api style API设计规范](https://github.com/jumpserver/jumpserver/blob/dev/docs/api_style_guide.md)
### Contributor 贡献者
#### 0.4.0
- ibuler <广宏伟>
- 小彧 <李磊> Django资深开发者为users模块贡献了很多代码
- sofia <周小侠> 资深前端工程师, luna前端代码贡献者和现在维护者
- liuz <刘正> 全栈工程师, 编写了luna大部分代码
- jiaxiangkong <陈尚委> Jumpserver测试运营
#### 0.3.2
- halcyon <王墉> DevOps 资深开发者, 0.3.2 核心开发者之一
- yumaojun03 <喻茂峻> DevOps 资深开发者jperm开发者擅长Python, Go以及PAAS平台开发
- kelianchun <柯连春> DevOps 资产开发者fix了很多connect.py bug
### 开发者群
如果你为Jumpserver贡献过代码请加一下群 需要验证一下你的github id
群号: 489385245
### License & Copyright
Copyright (c) 2014-2017 Beijing Duizhan Tech, Inc., All rights reserved.
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.

7
apps/__init__.py Normal file
View File

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

103
apps/applications/api.py Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,77 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li class="active">
<a href="" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Terminal detail' %} </a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'applications:terminal-update' pk=terminal.id %}"><i class="fa fa-edit"></i>Update</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-7" style="padding-left: 0">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label"><b>{{ terminal.name }}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table">
<tbody>
<tr class="no-borders-tr">
<td width="20%">{% trans 'Name' %}:</td>
<td><b>{{ terminal.name }}</b></td>
</tr>
<tr>
<td>{% trans 'Remote address' %}:</td>
<td><b>{{ terminal.remote_addr }}</b></td>
</tr>
<tr>
<td>{% trans 'URL to login' %}:</td>
<td><b>{{ terminal.url }}</b></td>
</tr>
<tr>
<td>{% trans 'Terminal type' %}:</td>
<td><b>{{ terminal.get_type_display }}</b></td>
</tr>
<tr>
<td>{% trans 'Date created' %}:</td>
<td><b>{{ terminal.date_created }}</b></td>
</tr>
<tr>
<td>{% trans 'Comment' %}:</td>
<td><b>{{ asset.comment }}</b></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,149 @@
{% extends '_base_list.html' %}
{% load i18n static %}
{% block custom_head_css_js %}
{{ block.super }}
<style>
div.dataTables_wrapper div.dataTables_filter,
.dataTables_length {
float: right !important;
}
div.dataTables_wrapper div.dataTables_filter {
margin-left: 15px;
}
#modal .modal-body { max-height: 200px; }
</style>
{% endblock %}
{% block table_search %}{% endblock %}
{% block table_container %}
{#<div class="uc pull-left m-l-5 m-r-5"><a href="{% url "users:user-create" %}" class="btn btn-sm btn-primary"> {% trans "Create user" %} </a></div>#}
<table class="table table-striped table-bordered table-hover " id="terminal_list_table" >
<thead>
<tr>
<th class="text-center">
<div class="checkbox checkbox-default">
<input type="checkbox" class="ipt_check_all">
</div>
</th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'Type' %}</th>
<th class="text-center">{% trans 'Session online' %}</th>
<th class="text-center">{% trans 'Active' %}</th>
<th class="text-center">{% trans 'Alive' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% include 'applications/terminal_modal_accept.html' %}
{% endblock %}
{% block custom_foot_js %}
<script src="{% static 'js/jquery.form.min.js' %}"></script>
<script>
$(document).ready(function(){
var options = {
ele: $('#terminal_list_table'),
buttons: [],
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "applications:terminal-detail" pk=99991937 %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('99991937', rowData.id));
}},
{targets: 5, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 6, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-circle text-danger"></i>')
} else {
$(td).html('<i class="fa fa-circle text-navy"></i>')
}
}},
{targets: 7, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "applications:terminal-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'
.replace('99991937', cellData);
var delete_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-del" data-id="99991937" data-name="99991938">{% trans "Delete" %}</a>'
.replace('99991937', cellData)
.replace('99991938', rowData.name);
var accept_btn = '<a class="btn btn-xs btn-primary btn-accept" data-id="99991937">{% trans "Accept" %}</a> '
.replace('99991937', cellData);
var reject_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-del" data-id="99991937" data-name="99991938">{% trans "Reject" %}</a>'
.replace('99991937', cellData)
.replace('99991938', rowData.name);
var connect_btn = '<a href="{% url "applications:terminal-connect" pk=99991937 %}"" class="btn btn-xs btn-warning btn-connect" >{% trans "Connect" %}</a> '
.replace('99991937', cellData);
if (rowData.is_accepted) {
{% if user.is_superuser %}
$(td).html(connect_btn + update_btn + delete_btn);
{% else %}
$(td).html(connect_btn);
{% endif %}
} else {
{% if user.is_superuser %}
$(td).html(accept_btn + reject_btn);
{% endif %}
}
}}
],
ajax_url: '{% url "api-applications:terminal-list" %}',
columns: [{data: function(){return ""}}, {data: "name" }, {data: "remote_addr" }, {data: "get_type_display" },
{data: "proxy_online"}, {data: "is_active" }, {data: 'is_active'}, {data: "id"}],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
$('#btn_terminal_accept').click(function () {
var $form = $('#form_terminal_accept');
function success(data, textStatus, jqXHR) {
if (data.success === true) {
window.location.reload()
} else {
$('#modal-error').html(data.msg).css('display', 'block');
}
}
$form.ajaxSubmit({success: success});
})
}).on('click', '.btn-del', function(){
var $this = $(this);
var id = $this.data('id');
var name = $(this).data('name');
var the_url = '{% url "api-applications:terminal-detail" pk=99991937 %}'.replace('99991937', id);
objectDelete($this, name, the_url)
}).on('click', '.btn-accept', function () {
var $this = $(this);
var terminal_id = $this.data('id');
var the_url = "{% url 'api-applications:terminal-detail' pk=99991937 %}".replace('99991937', terminal_id);
var post_url = $('#form_terminal_accept').attr('action').replace('99991937', terminal_id);
console.log(post_url);
$.ajax({
url: the_url,
method: 'GET',
success: function (data) {
$('#id_name').val(data.name);
$('#id_remote_addr').val(data.remote_addr);
$('#id_type').val(data.type);
$('#id_url').val(data.url);
$('#id_comment').val(data.comment);
$('#form_terminal_accept').attr('action', post_url)
}
});
$('#modal_terminal_accept').modal({
show: true
});
}).on('click', '.btn-connect', function () {
var $this = $(this);
var id = $this.data('id');
console.log(id)
})
</script>
{% endblock %}

View File

@@ -0,0 +1,19 @@
{% extends '_modal.html' %}
{% load i18n %}
{% block modal_id %}modal_terminal_accept{% endblock %}
{% block modal_class %}modal-lg{% endblock %}
{% block modal_title%}{% trans "Accept terminal registration" %}{% endblock %}
{% block modal_body %}
{% load bootstrap3 %}
<form action="{% url 'applications:terminal-modal-accept' pk="99991937" %}" method="post" class="form-horizontal" id="form_terminal_accept" enctype="multipart/form-data">
{% csrf_token %}
<p class="alert alert-danger" id="modal-error" style="display: none"></p>
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.remote_addr layout="horizontal" %}
{% bootstrap_field form.type layout="horizontal" %}
{% bootstrap_field form.url layout="horizontal" %}
{% bootstrap_field form.comment layout="horizontal" %}
</form>
{% endblock %}
{% block modal_confirm_id %}btn_terminal_accept{% endblock %}

View File

@@ -0,0 +1,5 @@
<form action="" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit">
</form>

View File

@@ -0,0 +1,72 @@
{% 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>
<link href="{% static "css/plugins/datepicker/datepicker3.css" %}" rel="stylesheet">
{% 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 method="post" class="form-horizontal" action="" enctype="multipart/form-data">
{% csrf_token %}
<h3>{% trans 'Info' %}</h3>
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.remote_addr layout="horizontal" %}
{% bootstrap_field form.type layout="horizontal" %}
{% bootstrap_field form.url layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Other' %}</h3>
{% bootstrap_field form.comment layout="horizontal" %}
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-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 src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
<script>
$(document).ready(function () {
$('.select2').select2();
$('.input-group.date').datepicker({
format: "yyyy-mm-dd",
todayBtn: "linked",
keyboardNavigation: false,
forceParse: false,
calendarWeeks: true,
autoclose: true
});
})
</script>
{% endblock %}

View File

@@ -0,0 +1 @@

View File

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

View File

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

115
apps/applications/views.py Normal file
View File

@@ -0,0 +1,115 @@
# ~*~ coding: utf-8 ~*~
#
from django.views.generic import ListView, UpdateView, DeleteView, \
DetailView, TemplateView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils.translation import ugettext as _
from django.urls import reverse_lazy, reverse
from common.mixins import JSONResponseMixin
from .models import Terminal
from .forms import TerminalForm
from .hands import AdminUserRequiredMixin
class TerminalListView(LoginRequiredMixin, ListView):
model = Terminal
template_name = 'applications/terminal_list.html'
form_class = TerminalForm
def get_context_data(self, **kwargs):
context = super(TerminalListView, self).get_context_data(**kwargs)
context.update({
'app': _('Terminal'),
'action': _('Terminal list'),
'form': self.form_class()
})
return context
class TerminalUpdateView(AdminUserRequiredMixin, UpdateView):
model = Terminal
form_class = TerminalForm
template_name = 'applications/terminal_update.html'
success_url = reverse_lazy('applications:terminal-list')
def get_context_data(self, **kwargs):
context = super(TerminalUpdateView, self).get_context_data(**kwargs)
context.update({'app': _('Applications'), 'action': _('Update terminal')})
return context
class TerminalDetailView(LoginRequiredMixin, DetailView):
model = Terminal
template_name = 'applications/terminal_detail.html'
context_object_name = 'terminal'
def get_context_data(self, **kwargs):
context = super(TerminalDetailView, self).get_context_data(**kwargs)
context.update({
'app': _('Applications'),
'action': _('Terminal detail')
})
return context
class TerminalDeleteView(AdminUserRequiredMixin, DeleteView):
model = Terminal
template_name = 'assets/delete_confirm.html'
success_url = reverse_lazy('applications:applications-list')
class TerminalModelAccept(AdminUserRequiredMixin, JSONResponseMixin, UpdateView):
model = Terminal
form_class = TerminalForm
template_name = 'applications/terminal_modal_test.html'
def post(self, request, *args, **kwargs):
print(request.POST)
return super(TerminalModelAccept, self).post(request, *args, **kwargs)
def form_valid(self, form):
terminal = form.save()
terminal.is_accepted = True
terminal.is_active = True
terminal.save()
data = {
'success': True,
'msg': 'success'
}
return self.render_json_response(data)
def form_invalid(self, form):
print('form.data')
data = {
'success': False,
'msg': str(form.errors),
}
return self.render_json_response(data)
class TerminalConnectView(LoginRequiredMixin, DetailView):
template_name = 'flash_message_standalone.html'
model = Terminal
def get_context_data(self, **kwargs):
if self.object.type == 'Web':
context = {
'title': _('Redirect to web terminal'),
'messages': _('Redirect to web terminal') + self.object.url,
'auto_redirect': True,
'interval': 3,
'redirect_url': self.object.url
}
else:
context = {
'title': _('Connect ssh terminal'),
'messages': _('You should use your ssh client tools '
'connect terminal: {} <br /> <br />'
'{}'.format(self.object.name, self.object.url)),
'redirect_url': reverse('applications:terminal-list')
}
kwargs.update(context)
return super(TerminalConnectView, self).get_context_data(**kwargs)

220
apps/assets/api.py Normal file
View File

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

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

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

361
apps/assets/forms.py Normal file
View File

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

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

@@ -0,0 +1,18 @@
"""
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-2017 by Jumpserver Team.
:license: GPL v2, see LICENSE for more details.
"""
from users.utils import AdminUserRequiredMixin
from users.permissions import IsAppUser, IsSuperUser, IsValidUser
from users.models import User, UserGroup
from perms.utils import get_user_granted_assets
from perms.tasks import push_users

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from .idc import *
from .user import *
from .group import *
from .asset import *
from .utils import *

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

@@ -0,0 +1,156 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from __future__ import unicode_literals
from django.db import models
import logging
from django.utils.translation import ugettext_lazy as _
from . import IDC, AssetGroup, AdminUser, SystemUser
__all__ = ['Asset']
logger = logging.getLogger(__name__)
def get_default_idc():
return IDC.initial()
class Asset(models.Model):
STATUS_CHOICES = (
('In use', _('In use')),
('Out of use', _('Out of use')),
)
TYPE_CHOICES = (
('Server', _('Server')),
('VM', _('VM')),
('Switch', _('Switch')),
('Router', _('Router')),
('Firewall', _('Firewall')),
('Storage', _("Storage")),
)
ENV_CHOICES = (
('Prod', 'Production'),
('Dev', 'Development'),
('Test', 'Testing'),
)
# Important
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'))
groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets',
verbose_name=_('Asset groups'))
admin_user = models.ForeignKey(AdminUser, null=True, blank=True, related_name='assets',
on_delete=models.SET_NULL, verbose_name=_("Admin user"))
system_users = models.ManyToManyField(SystemUser, blank=True,
related_name='assets',
verbose_name=_("System User"))
idc = models.ForeignKey(IDC, blank=True, null=True, related_name='assets',
on_delete=models.SET_NULL, verbose_name=_('IDC'),)
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True,
default='Server', verbose_name=_('Asset type'),)
env = models.CharField(choices=ENV_CHOICES, max_length=8, blank=True, null=True,
default='Prod', verbose_name=_('Asset environment'),)
status = models.CharField(choices=STATUS_CHOICES, max_length=12, null=True, blank=True,
default='In use', verbose_name=_('Asset status'))
# Some information
public_ip = models.GenericIPAddressField(max_length=32, blank=True,
null=True, verbose_name=_('Public IP'))
remote_card_ip = models.CharField(max_length=16, null=True, blank=True,
verbose_name=_('Remote control card IP'))
cabinet_no = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Cabinet number'))
cabinet_pos = models.IntegerField(null=True, blank=True, verbose_name=_('Cabinet position'))
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, null=True, blank=True, verbose_name=_('Platform'))
os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS'))
os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version'))
os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch'))
hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw'))
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
def __unicode__(self):
return '%s <%s: %s>' % (self.hostname, self.ip, self.port)
__str__ = __unicode__
@property
def is_valid(self):
warning = ''
if not self.is_active:
warning += ' inactive'
else:
return True, ''
return False, warning
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"""
return {
'id': self.id,
'hostname': self.hostname,
'ip': self.ip,
'port': self.port,
'groups': [group.name for group in self.groups.all()],
'username': self.admin_user.username if self.admin_user else '',
'password': self.admin_user.password if self.admin_user else '',
'private_key': self.admin_user.private_key_file if self.admin_user else None,
'become': {
'method': self.admin_user.become_method,
'user': self.admin_user.become_user,
'pass': self.admin_user.become_pass,
} if self.admin_user and self.admin_user.become else {},
}
class Meta:
unique_together = ('ip', 'port')
@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()),
idc=choice(IDC.objects.all()),
port=22,
created_by='Fake')
try:
asset.save()
asset.system_users = [choice(SystemUser.objects.all()) for i in range(3)]
asset.groups = [choice(AssetGroup.objects.all()) for i in range(3)]
logger.debug('Generate fake asset : %s' % asset.ip)
except IntegrityError:
print('Error continue')
continue

View File

@@ -0,0 +1,52 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from __future__ import unicode_literals
from django.db import models
import logging
from django.utils.translation import ugettext_lazy as _
from . import SystemUser
__all__ = ['AssetGroup']
logger = logging.getLogger(__name__)
class AssetGroup(models.Model):
name = models.CharField(max_length=64, unique=True, verbose_name=_('Name'))
system_users = models.ManyToManyField(SystemUser, related_name='asset_groups', blank=True)
created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
def __unicode__(self):
return self.name
__str__ = __unicode__
class Meta:
ordering = ['name']
@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

69
apps/assets/models/idc.py Normal file
View File

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

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

@@ -0,0 +1,259 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from __future__ import unicode_literals
import os
import logging
from hashlib import md5
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from common.utils import signer, validate_ssh_private_key, ssh_key_string_to_obj
__all__ = ['AdminUser', 'SystemUser', 'private_key_validator']
logger = logging.getLogger(__name__)
def private_key_validator(value):
if not validate_ssh_private_key(value):
raise ValidationError(
_('%(value)s is not an even number'),
params={'value': value},
)
class AdminUser(models.Model):
BECOME_METHOD_CHOICES = (
('sudo', 'sudo'),
('su', 'su'),
)
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
username = models.CharField(max_length=16, verbose_name=_('Username'))
_password = models.CharField(
max_length=256, blank=True, null=True, verbose_name=_('Password'))
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'),
validators=[private_key_validator,])
become = models.BooleanField(default=True)
become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4)
become_user = models.CharField(default='root', max_length=64)
become_pass = models.CharField(default='', max_length=128)
_public_key = models.TextField(
max_length=4096, blank=True, verbose_name=_('SSH public key'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now_add=True, null=True)
created_by = models.CharField(
max_length=32, null=True, verbose_name=_('Created by'))
def __unicode__(self):
return self.name
__str__ = __unicode__
@property
def password(self):
if self._password:
return signer.unsign(self._password)
else:
return ''
@password.setter
def password(self, password_raw):
self._password = signer.sign(password_raw)
@property
def private_key(self):
if self._private_key:
key_str = signer.unsign(self._private_key)
return ssh_key_string_to_obj(key_str)
else:
return None
@private_key.setter
def private_key(self, private_key_raw):
self._private_key = signer.sign(private_key_raw)
@property
def private_key_file(self):
if not self.private_key:
return None
project_dir = settings.PROJECT_DIR
tmp_dir = os.path.join(project_dir, 'tmp')
key_name = md5(self._private_key.encode()).hexdigest()
key_path = os.path.join(tmp_dir, key_name)
if not os.path.exists(key_path):
self.private_key.write_private_key_file(key_path)
return key_path
@property
def public_key(self):
return signer.unsign(self._public_key)
@public_key.setter
def public_key(self, public_key_raw):
self._public_key = signer.sign(public_key_raw)
@property
def assets_amount(self):
return self.assets.count()
class Meta:
ordering = ['name']
@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(models.Model):
PROTOCOL_CHOICES = (
('ssh', 'ssh'),
)
AUTH_METHOD_CHOICES = (
('P', 'Password'),
('K', 'Public key'),
)
name = models.CharField(max_length=128, unique=True,
verbose_name=_('Name'))
username = models.CharField(max_length=16, verbose_name=_('Username'))
_password = models.CharField(
max_length=256, blank=True, verbose_name=_('Password'))
protocol = models.CharField(
max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
_private_key = models.TextField(
max_length=8192, blank=True, verbose_name=_('SSH private key'))
_public_key = models.TextField(
max_length=8192, blank=True, verbose_name=_('SSH public key'))
auth_method = models.CharField(choices=AUTH_METHOD_CHOICES, default='K',
max_length=1, verbose_name=_('Auth method'))
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
sudo = models.TextField(
max_length=4096, default='/sbin/ifconfig', verbose_name=_('Sudo'))
shell = models.CharField(
max_length=64, default='/bin/bash', verbose_name=_('Shell'))
date_created = models.DateTimeField(auto_now_add=True)
created_by = models.CharField(
max_length=32, blank=True, verbose_name=_('Created by'))
comment = models.TextField(
max_length=128, blank=True, verbose_name=_('Comment'))
def __unicode__(self):
return self.name
__str__ = __unicode__
@property
def password(self):
if self._password:
return signer.unsign(self._password)
return None
@password.setter
def password(self, password_raw):
self._password = signer.sign(password_raw)
@property
def private_key(self):
if self._private_key:
return signer.unsign(self._private_key)
return None
@private_key.setter
def private_key(self, private_key_raw):
self._private_key = signer.sign(private_key_raw)
@property
def public_key(self):
return signer.unsign(self._public_key)
@public_key.setter
def public_key(self, public_key_raw):
self._public_key = signer.sign(public_key_raw)
def get_assets_inherit_from_asset_groups(self):
assets = set()
asset_groups = self.asset_groups.all()
for asset_group in asset_groups:
for asset in asset_group.assets.all():
setattr(asset, 'is_inherit_from_asset_groups', True)
setattr(asset, 'inherit_from_asset_groups',
getattr(asset, 'inherit_from_asset_groups', set()).add(asset_group))
assets.add(asset)
return assets
def get_assets(self):
assets = set(self.assets.all()
) | self.get_assets_inherit_from_asset_groups()
return list(assets)
def _to_secret_json(self):
"""Push system user use it"""
return {
'name': self.name,
'username': self.username,
'shell': self.shell,
'sudo': self.sudo,
'password': self.password,
'public_key': self.public_key
}
@property
def assets_amount(self):
return self.assets.count()
@property
def asset_group_amount(self):
return self.asset_groups.count()
def to_json(self):
return {
'id': self.id,
'name': self.name,
'username': self.username,
'protocol': self.protocol,
'auth_method': self.auth_method,
'auto_push': self.auto_push,
}
class Meta:
ordering = ['name']
@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,23 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from . import IDC, SystemUser, AdminUser, AssetGroup, Asset
__all__ = ['init_model', 'generate_fake']
def init_model():
for cls in [IDC, SystemUser, AdminUser, AssetGroup, Asset]:
if hasattr(cls, 'initial'):
cls.initial()
def generate_fake():
for cls in [IDC, SystemUser, AdminUser, AssetGroup, Asset]:
if hasattr(cls, 'generate_fake'):
cls.generate_fake()
if __name__ == '__main__':
pass

189
apps/assets/serializers.py Normal file
View File

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

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

@@ -0,0 +1,80 @@
# ~*~ coding: utf-8 ~*~
from celery import shared_task
import json
from django.core.cache import cache
from ops.tasks import run_AdHoc
from common.utils import get_object_or_none, capacity_convert, sum_capacity
from .models import Asset
@shared_task
def update_assets_hardware_info(assets):
task_tuple = (
('setup', ''),
)
summary, result = run_AdHoc(task_tuple, assets, record=False)
for hostname, info in result['contacted'].items():
if info:
info = info[0]['ansible_facts']
else:
continue
asset = get_object_or_none(Asset, hostname=hostname)
if not asset:
continue
___vendor = info['ansible_system_vendor']
___model = info['ansible_product_version']
___sn = info['ansible_product_serial']
for ___cpu_model in info['ansible_processor']:
if ___cpu_model.endswith('GHz'):
break
else:
___cpu_model = 'Unknown'
___cpu_count = info['ansible_processor_count']
___cpu_cores = info['ansible_processor_cores']
___memory = '%s %s' % capacity_convert('{} MB'.format(info['ansible_memtotal_mb']))
disk_info = {}
for dev, dev_info in info['ansible_devices'].items():
if dev_info['removable'] == '0':
disk_info[dev] = dev_info['size']
___disk_total = '%s %s' % sum_capacity(disk_info.values())
___disk_info = json.dumps(disk_info)
___platform = info['ansible_system']
___os = info['ansible_distribution']
___os_version = info['ansible_distribution_version']
___os_arch = info['ansible_architecture']
___hostname_raw = info['ansible_hostname']
for k, v in locals().items():
if k.startswith('___'):
setattr(asset, k.strip('_'), v)
asset.save()
return summary
@shared_task
def update_assets_hardware_period():
assets = Asset.objects.filter(type__in=['Server', 'VM'])
update_assets_hardware_info(assets)
@shared_task
def test_admin_user_connective_period():
assets = Asset.objects.filter(type__in=['Server', 'VM'])
task_tuple = (
('ping', ''),
)
summary, _ = run_AdHoc(task_tuple, assets, record=False)
for i in summary['success']:
cache.set(i, '1', 2*60*60*60)
for i, msg in summary['failed']:
cache.set(i, '0', 60*60*60)
return summary

View File

@@ -0,0 +1,42 @@
{% 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,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>{% trans 'Create system user' %}</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.protocol layout="horizontal" %}
<h3>{% trans 'Auth' %}</h3>
{% bootstrap_field form.auth_method layout="horizontal" %}
{% block auth %}
<div class="password-auth hidden">
{% bootstrap_field form.password layout="horizontal" %}
</div>
<div class="public-key-auth">
<div class="form-group">
<label for="{{ form.auto_generate_key.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto generate key' %}</label>
<div class="col-sm-8">
{{ form.auto_generate_key}}
</div>
</div>
<div>
{% bootstrap_field form.private_key_file layout="horizontal" %}
</div>
</div>
{% endblock %}
<div class="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>
<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 auth_method = '#'+'{{ form.auth_method.id_for_label }}';
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
function authMethodDisplay() {
if ($(auth_method).val() == 'P') {
$('.password-auth').removeClass('hidden');
$('.public-key-auth').addClass('hidden');
$('#'+'{{ form.password.id_for_label }}').attr('required', 'required');
$('#'+'{{ form.password.id_for_label }}').removeAttr('disabled');
$('#'+'{{ form.private_key_file.id_for_label }}').removeAttr('required');
} else if ($(auth_method).val() == 'K') {
$('.password-auth').addClass('hidden');
$('.public-key-auth').removeClass('hidden');
$('#'+'{{ form.password.id_for_label }}').removeAttr('required');
$('#'+'{{ form.password.id_for_label }}').attr('disabled', 'disabled');
if ($(auto_generate_key).prop('checked')){
$('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group').addClass('hidden');
} else {
$('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group').removeClass('hidden');
$('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group input').attr('required', 'required');
}
}
}
$(document).ready(function () {
$('.select2').select2();
authMethodDisplay();
$(auth_method).change(function () {
authMethodDisplay();
});
$(auto_generate_key).change(function () {
authMethodDisplay();
});
})
</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>{% trans 'Create admin user' %}</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,441 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li class="active">
<a href="" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'assets:admin-user-update' pk=admin_user.id %}"><i class="fa fa-edit"></i>Update</a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-danger btn-delete-admin-user">
<i class="fa fa-edit"></i>Delete
</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-7" style="padding-left: 0;">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label"><b>{{ 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 class="ibox float-e-margins">
<div class="ibox-title">
<span style="float: left">{% trans 'Asset list of ' %} <b>{{ admin_user.name }}</b> <span class="badge"> {{ paginator.count }}</span></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table table-hover" id="system_user_assets_table">
<thead>
<tr>
<th>{% trans 'Hostname' %}</th>
<th>{% trans 'IP' %}</th>
<th>{% trans 'Port' %}</th>
<th>{% trans 'Alive' %}</th>
<th>{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
{# {% for asset in page_obj %}#}
{# <tr>#}
{# <td>{{ asset.hostname }}</td>#}
{# <td>{{ asset.ip }}</td>#}
{# <td>{{ asset.port }}</td>#}
{# <td>Alive</td>#}
{# </tr>#}
{# {% endfor %}#}
</tbody>
</table>
{# <div class="row">#}
{# {% include '_pagination.html' %}#}
{# </div>#}
</div>
</div>
</div>
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %}
</div>
<div class="panel-body">
<table class="table">
<tbody>
<tr class="no-borders-tr">
<td width="50%">{% trans 'Get install script' %}:</td>
<td>
<span style="float: right">
<button type="button" class="btn btn-primary btn-xs" style="width: 54px">{% trans 'Get' %}</button>
</span>
</td>
</tr>
<tr>
<td width="50%">{% trans 'Retest asset connectivity' %}:</td>
<td>
<span style="float: right">
<button type="button" class="btn btn-primary btn-xs" style="width: 54px">{% trans 'Start' %}</button>
</span>
</td>
</tr>
<tr>
<td width="50%">{% trans 'Reset private key' %}:</td>
<td>
<span style="float: right">
<button type="button" class="btn btn-primary btn-xs" style="width: 54px">{% trans 'Reset' %}</button>
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Replace asset admin user with this' %}
</div>
<div class="panel-body">
<table class="table">
<tbody>
<form>
<tr class="no-borders-tr">
<td colspan="2">
<select data-placeholder="{% trans 'Select asset' %}" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for asset in assets_remain %}
<option value="{{ asset.id }}">{{ asset.ip }}:{{ asset.port }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr class="no-borders-tr">
<td colspan="2">
<button type="button" class="btn btn-info btn-sm btn-replace-asset-admin_user">{% trans 'Replace' %}</button>
</td>
</tr>
</form>
</tbody>
</table>
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Replace asset admin user with this admin user' %}
</div>
<div class="panel-body">
<table class="table">
<tbody>
<form>
<tr class="no-borders-tr">
<td colspan="2">
<select data-placeholder="{% trans 'Select asset groups' %}" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for asset_group in asset_groups %}
<option value="{{ asset_group.id }}">{{ asset_group.name }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr class="no-borders-tr">
<td colspan="2">
<button type="button" class="btn btn-warning btn-sm btn-replace-asset_groups-admin_user">{% trans 'Replace' %}</button>
</td>
</tr>
</form>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
Array.prototype.remove = function(val) {
var index = this.indexOf(val);
if (index > -1) {
this.splice(index, 1);
}
};
Array.prototype.unique = function(){
var res = [];
var json = {};
for(var i = 0; i < this.length; i++){
if(!json[this[i]]){
res.push(this[i]);
json[this[i]] = 1;
}
}
return res;
};
function objectRemove(obj, name, url, data) {
function doRemove() {
var body = data;
var success = function() {
swal('Remove!', "[ "+name+"]"+" has been deleted ", "success");
$(obj).parent().parent().remove();
};
var fail = function() {
swal("Failed", "Remove"+"[ "+name+" ]"+"failed", "error");
};
APIUpdateAttr({
url: url,
body: JSON.stringify(body),
method: 'PATCH',
success: success,
error: fail
});
}
swal({
title: 'Are you sure remove ?',
text: " [" + name + "] ",
type: "warning",
showCancelButton: true,
cancelButtonText: 'Cancel',
confirmButtonColor: "#DD6B55",
confirmButtonText: 'Confirm',
closeOnConfirm: false
}, function () {
doRemove()
});
}
jumpserver.assets_selected = {};
jumpserver.asset_groups_selected = {};
$(document).ready(function () {
$('.select2').select2()
.on("select2:select", function (evt) {
var data = evt.params.data;
jumpserver.assets_selected[data.id] = data.text;
jumpserver.asset_groups_selected[data.id] = data.text;
})
.on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.assets_selected[data.id];
delete jumpserver.asset_groups_selected[data.id]
});
var options = {
ele: $('#system_user_assets_table'),
buttons: [],
order: [],
columnDefs: [
{targets: 0, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:asset-detail" pk=99991937 %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
$(td).html(detail_btn.replace('99991937', rowData.id));
}},
{targets: 3, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 4, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:asset-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', rowData.id);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_remove" data-aid="99991937">{% trans "Remove" %}</a>'.replace('99991937', rowData.id);
$(td).html(update_btn + del_btn)
}}
],
ajax_url: '{% url "api-assets:asset-list" %}?admin_user_id={{ admin_user.id }}',
columns: [{data: "hostname" }, {data: "ip" }, {data: "port" },
{data: "is_active" }, {data: "id"}],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
function adminUserDelete(name, url) {
function doDelete() {
var body = {};
var success = function() {
swal('Deleted!', "[ "+name+"]"+" has been deleted ", "success");
window.location.href="{% url 'assets:idc-list' %}";
};
var fail = function() {
swal("Failed", "Delete"+"[ "+name+" ]"+"failed", "error");
};
APIUpdateAttr({
url: url,
body: JSON.stringify(body),
method: 'DELETE',
success: success,
error: fail
});
}
swal({
title: 'Are you sure delete ?',
text: " [" + name + "] ",
type: "warning",
showCancelButton: true,
cancelButtonText: 'Cancel',
confirmButtonColor: "#DD6B55",
confirmButtonText: 'Confirm',
closeOnConfirm: false
}, function () {
doDelete()
});
}
})
.on('click', '.btn-replace-asset-admin_user', function () {
if (Object.keys(jumpserver.assets_selected).length === 0) {
return false;
}
jumpserver.asset_groups_selected = {};
var $data_table = $("#system_user_assets_table").DataTable();
var assets = [];
$.map(jumpserver.assets_selected, function(value, index) {
assets.push(parseInt(index));
});
assets.unique();
var data = [];
var admin_user_id = {{ admin_user.id }};
var the_url = '{% url "api-assets:asset-list" %}';
for (var i=0; i<assets.length; i++) {
data.push({"id": assets[i], "admin_user": admin_user_id});
}
APIUpdateAttr({
url: the_url,
body: JSON.stringify(data),
method: 'PATCH'
});
$data_table.ajax.reload();
})
.on('click', '.btn-replace-asset_groups-admin_user', function () {
if (Object.keys(jumpserver.asset_groups_selected).length === 0) {
return false;
}
jumpserver.assets_selected = {};
var $data_table = $("#system_user_assets_table").DataTable();
var asset_groups = [];
var assets = [];
var data = [];
var the_url = '{% url "api-assets:asset-list" %}';
$.map(jumpserver.asset_groups_selected, function(value, index) {
asset_groups.push(parseInt(index));
});
$.ajax({
url: '{% url "api-assets:asset-group-list" %}?id__in=['+asset_groups.join(',')+']',
method: 'GET',
dataType: 'json',
success: function (result) {
for (var i=0; i<result.length; i++) {
for (var j=0; j<result[i]['assets'].length; j++) {
assets.push(result[i]['assets'][j])
}
}
for (var z=0; z<assets.length; z++) {
data.push({"id":assets[z], "admin_user":{{admin_user.id}} });
}
APIUpdateAttr({
url: the_url,
body: JSON.stringify(data),
method: 'PATCH'
});
$data_table.ajax.reload();
}
});
})
.on('click', '.btn_asset_remove', function () {
var $this = $(this);
var the_url = "{% url 'api-assets:admin-user-detail' pk=admin_user.id %}";
var name = $(this).closest("tr").find(":nth-child(1) > a").html();
var assets = [];
var delete_asset_id = $(this).data('aid');
$.ajax({
url: the_url,
method: 'GET',
dataType: 'json',
success: function (result) {
for (var i=0; i<result['assets'].length; i++) {
assets.push(result['assets'][i])
}
assets.remove(delete_asset_id);
var data = {"assets": assets};
objectRemove($this, name, the_url, data);
}
})
}).on('click', '.btn-delete-admin-user', function () {
var $this = $(this);
var name = "{{ admin_user.name }}";
var uid = "{{ admin_user.id }}";
var the_url = '{% url "api-assets:admin-user-detail" pk=99991937 %}'.replace('99991937', uid);
var redirect_url = "{% url 'assets:admin-user-list' %}";
objectDelete($this, name, the_url, redirect_url);
})
</script>
{% endblock %}

View File

@@ -0,0 +1,71 @@
{% extends '_base_list.html' %}
{% load i18n static %}
{% block table_search %}
{% endblock %}
{% block table_container %}
<div class="uc pull-left m-l-5 m-r-5">
<a href="{% url "assets: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 num' %}</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=99991937 %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('99991937', rowData.id));
}},
{targets: 4, createdCell: function (td, cellData) {
var innerHtml = cellData.length > 8 ? cellData.substring(0, 24) + '...': cellData;
$(td).html('<a href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</a>');
}},
{targets: 5, createdCell: function (td, cellData, rowData) {
{# var script_btn = '<a href="{% url "assets:admin-user-update" pk=99991937 %}" class="btn btn-xs btn-primary">{% trans "Script" %}</a>'.replace('99991937', cellData);#}
var update_btn = '<a href="{% url "assets:admin-user-update" pk=99991937 %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData);
{# $(td).html(script_btn + update_btn + del_btn)#}
$(td).html(update_btn + del_btn)
}}],
ajax_url: '{% url "api-assets:admin-user-list" %}',
columns: [{data: function(){return ""}}, {data: "name" }, {data: "username" }, {data: "assets_amount" },
{data: "comment" }, {data: "id" }],
};
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=99991937 %}'.replace('99991937', 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,58 @@
{% extends '_base_create_update.html' %}
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% 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.public_ip layout="horizontal" %}
{% bootstrap_field form.port layout="horizontal" %}
{% bootstrap_field form.type layout="horizontal" %}
{% bootstrap_field form.env layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Group' %}</h3>
{% bootstrap_field form.idc layout="horizontal" %}
{% bootstrap_field form.groups layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Asset user' %}</h3>
{% bootstrap_field form.admin_user 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-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>
$(document).ready(function () {
$('.select2').select2();
$("#id_tags").select2({
tags: true,
maximumSelectionLength: 8 //最多能够选择的个数
//closeOnSelect: false
});
})
</script>
{% endblock %}

View File

@@ -0,0 +1,471 @@
{% 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>Update</a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-danger btn-delete-asset">
<i class="fa fa-edit"></i>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 }}</b></td>
</tr>
<tr>
<td>{% trans 'Port' %}:</td>
<td><b>{{ asset.port }}</b></td>
</tr>
<tr>
<td>{% trans 'Admin user' %}:</td>
{% if asset.admin_user %}
<td><b>{{ asset.admin_user.name }}</b></td>
{% else %}
<td><b>None</b></td>
{% endif %}
</tr>
<tr>
<td>{% trans 'Remote card IP' %}:</td>
<td><b>{{ asset.remote_card_ip }}</b></td>
</tr>
<tr>
<td>{% trans 'IDC' %}:</td>
<td><b>{{ asset.idc.name }}</b></td>
</tr>
<tr>
<td>{% trans 'Cabinet number' %}:</td>
<td><b>{{ asset.cabinet_no }}</b></td>
</tr>
<tr>
<td>{% trans 'Cabinet position' %}:</td>
<td><b>{{ asset.cabinet_pos }}</b></td>
</tr>
<tr>
<td>{% trans 'Vendor' %}:</td>
<td><b>{{ asset.vendor }}</b></td>
</tr>
<tr>
<td>{% trans 'Model' %}:</td>
<td><b>{{ asset.model }}</b></td>
</tr>
<tr>
<td>{% trans 'CPU' %}:</td>
<td><b>{{ asset.cpu_model }} {{ asset.cpu_count }}*{{ asset.cpu_cores }}</b></td>
</tr>
<tr>
<td>{% trans 'Memory' %}:</td>
<td><b>{{ asset.memory }}</b></td>
</tr>
<tr>
<td>{% trans 'Disk' %}:</td>
<td><b>{{ asset.disk_total }}</b></td>
</tr>
<tr>
<td>{% trans 'Platform' %}:</td>
<td><b>{{ asset.platform }}</b></td>
</tr>
<tr>
<td>{% trans 'OS' %}:</td>
<td><b>{{ asset.os }} {{ asset.os_version }} {{ asset.os_arch }}</b></td>
</tr>
<tr>
<td>{% trans 'Asset status' %}:</td>
<td><b>{{ asset.status }}</b></td>
</tr>
<tr>
<td>{% trans 'Is active' %}:</td>
<td><b>{{ asset.is_active }}</b></td>
</tr>
<tr>
<td>{% trans 'Asset type' %}:</td>
<td><b>{{ asset.type }}</b></td>
</tr>
<tr>
<td>{% trans 'Asset environment' %}:</td>
<td><b>{{ asset.env }}</b></td>
</tr>
<tr>
<td>{% trans 'Serial number' %}:</td>
<td><b>{{ asset.sn }}</b></td>
</tr>
<tr>
<td>{% trans 'Asset number' %}:</td>
<td><b>{{ asset.number }}</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>
<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 admin user' %}:</td>
<td>
<span class="pull-right">
<button type="button" class="btn btn-primary btn-xs" id="btn_test_admin_user" style="width: 54px;">{% trans 'Test' %}</button>
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Asset groups' %}
</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 'Join asset groups' %}" id="groups_selected" class="select2 groups" style="width: 100%" multiple="" tabindex="4">
{% for asset_group in asset_groups_remain %}
<option value="{{ asset_group.id }}" id="opt_{{ asset_group.id }}" >{{ asset_group.name }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td colspan="2" class="no-borders">
<button type="button" class="btn btn-info btn-sm" id="btn_add_user_group">{% trans 'Confirm' %}</button>
</td>
</tr>
</form>
{% for asset_group in asset_groups %}
<tr>
<td ><b class="bdg_group" data-gid={{ asset_group.id }}>{{ asset_group.name }}</b></td>
<td>
<button class="btn btn-danger pull-right btn-xs btn_leave_group" 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 'Push system users' %}
</div>
<div class="panel-body">
<table class="table group_edit" id="add-asset2systemuser">
<tbody>
<form>
<tr class="no-borders-tr">
<td colspan="2">
<select data-placeholder="{% trans 'Select system users' %}" class="select2 system-user" style="width: 100%" multiple="" tabindex="4">
{% for system_user in system_users_all %}
<option value="{{ system_user.id }}" id="opt_{{ system_user.id }}">{{ system_user.name }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr class="no-borders-tr">
<td colspan="2">
<button type="button" class="btn btn-warning btn-sm btn-system-user">{% trans 'Confirm' %}</button>
</td>
</tr>
</form>
{% for system_user in system_users %}
<tr>
<td ><b class="bdg_group" data-sid={{ system_user.id }}>{{ system_user.name }}</b></td>
<td>
<button class="btn btn-danger btn-xs pull-right btn_leave_system" type="button" style="float: right;"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
jumpserver.groups_selected = {};
jumpserver.system_user_selected = {};
function updateAssetGroups(groups) {
var the_url = "{% url 'api-assets:asset-update-group' pk=asset.id %}";
var body = {
groups: Object.assign([], groups)
};
var success = function(data) {
// remove all the selected groups from select > option and rendered ul element;
$('.select2-selection__rendered').empty();
$('#groups_selected').val('');
$.map(jumpserver.groups_selected, function(group_name, index) {
$('#opt_' + index).remove();
// change tr html of user groups.
$('#add-asset2group tbody').append(
'<tr>' +
'<td><b class="bdg_group" data-gid="' + index + '">' + group_name + '</b></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn_leave_group" type="button"><i class="fa fa-minus"></i></button></td>' +
'</tr>'
)
});
// clear jumpserver.groups_selected
jumpserver.groups_selected = {};
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
success: success
});
}
function updateAssetSystemUser(system_users) {
var the_url = "{% url 'api-assets:asset-update-system-users' pk=asset.id %}";
var body = {
system_users: Object.assign([], system_users)
};
var success = function(data) {
$('.select2-selection__rendered').empty();
$('#groups_selected').val('');
$.map(jumpserver.system_user_selected, function(name, index) {
$('#opt_' + index).remove();
$('#add-asset2systemuser tbody').append(
'<tr>' +
'<td><b class="bdg_group" data-sid="' + index + '">' + name + '</b></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn_leave_system" type="button"><i class="fa fa-minus"></i></button></td>' +
'</tr>'
)
});
// clear jumpserver.groups_selected
jumpserver.system_user_selected = {};
};
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();
};
APIUpdateAttr({
url: the_url,
success: success,
method: 'GET'
})
}
$(document).ready(function () {
$('.select2.groups').select2().on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.groups_selected[data.id] = data.text;
}).on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.groups_selected[data.id]
});
$('.select2.system-user').select2().on('select2:select', function(evt) {
var data = evt.params.data;
jumpserver.system_user_selected[data.id] = data.text;
}).on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.system_user_selected[data.id]
})
}).on('click', '#is_active', function () {
var the_url = '{% url "api-assets:asset-detail" pk=asset.id %}';
var checked = $(this).prop('checked');
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_add_user_group', function () {
if (Object.keys(jumpserver.groups_selected).length === 0) {
return false;
}
var groups = $('.bdg_group').map(function() {
return $(this).data('gid');
}).get();
$.map(jumpserver.groups_selected, function(value, index) {
groups.push(parseInt(index));
$('#opt_' + index).remove();
});
updateAssetGroups(groups)
}).on('click', '.btn_leave_group', function() {
var $this = $(this);
var $tr = $this.closest('tr');
var $badge = $tr.find('.bdg_group');
var gid = $badge.data('gid');
var group_name = $badge.html() || $badge.text();
$('#groups_selected').append(
'<option value="' + gid + '" id="opt_' + gid + '">' + group_name + '</option>'
);
$tr.remove();
var groups = $('.bdg_group').map(function () {
return $(this).data('gid');
}).get();
updateAssetGroups(groups)
}).on('click', '.btn-system-user', function () {
if (Object.keys(jumpserver.system_user_selected).length === 0) {
return false;
}
var system_users = $('.bdg_group').map(function() {
return $(this).data('sid');
}).get();
$.map(jumpserver.system_user_selected, function(value, index) {
system_users.push(parseInt(index));
$('#opt_' + index).remove();
});
updateAssetSystemUser(system_users)
}).on('click', '.btn_leave_system', function () {
var $this = $(this);
var $tr = $this.closest('tr');
var $badge = $tr.find('.bdg_group');
var sid = $badge.data('sid');
var name = $badge.html() || $badge.text();
$('#groups_selected').append(
'<option value="' + sid + '" id="opt_' + sid + '">' + name + '</option>'
);
$tr.remove();
var system_users = $('.bdg_group').map(function () {
return $(this).data('sid');
}).get();
updateAssetSystemUser(system_users)
}).on('click', '.btn-delete-asset', function () {
var $this = $(this);
var name = "{{ asset.hostname }}";
var uid = "{{ asset.id }}";
var the_url = '{% url "api-assets:asset-detail" pk=99991937 %}'.replace('99991937', uid);
var redirect_url = "{% url 'assets:asset-list' %}";
objectDelete($this, name, the_url, redirect_url);
}).on('click', '#btn_refresh_asset', function () {
alert('请等待几秒, 等待完成');
refreshAssetHardware()
}).on('click', '#btn_test_admin_user', function () {
$.ajax({
url: '{% url "api-assets:asset-admin-user-test" pk=asset.id %}'
}).done(function (data, textStatue, jqXHR) {
toastr.success('Success')
}).fail(function (data, textStaue, errorThrown) {
toastr.error('Error')
})
})
</script>
{% 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-10">
<div class="ibox float-e-margins">
<div id="ibox-content" class="ibox-title">
<h5> {{ action }}</h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<div class="panel blank-panel">
<div class="panel-body">
<div class="tab-content">
<div id="tab-1" class="ibox float-e-margins tab-pane active"></div>
<form id="groupForm" method="post" class="form-horizontal">
{% csrf_token %}
<h3 class="widget-head-color-box">资产组信息</h3>
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.comment layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3 class="widget-head-color-box">用户选择的资产</h3>
<div class="form-group">
<label class="col-sm-2 control-label" id="asset_on_count">已选({{ assets_count }}</label>
<div class="col-sm-9" id="asset_sed">
<div class="form-asset-on" id="add_asset">
<p id="asset_on_p">
{% for asset in assets_on_list %}
<button name='asset_hostname' title='{{ asset.ip }}' type='button' class='btn btn-default btn-xs'>{{ asset.hostname }}</button>
{% endfor %}
</p>
</div>
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-5">
<button class="btn btn-white" type="reset"> 重置 </button>
<button class="btn btn-primary" type="submit"> 提交 </button>
<div id='box2'> </div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 模态框Modal -->
<div class="modal fade" id="modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content" id="box">
<!--此部分为主体内容,将远程加载进来-->
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script type="text/javascript">
$(document).ready(function () {
$('.select2').select2();
$('.select2-system-user').select2();
});
$('#add_asset').on('click',function(){
$('#modal').modal('show');
});
$('#modal').modal({
show: false,
backdrop: 'static',
keyboard: 'false',
remote:"{% url 'assets:asset-modal-list' %}?group_id={{ group_id }}"
});
$('#modal').on('show.bs.modal',function(){
//alert('当调用show方法时立即触发')
});
$('#modal').on('shown.bs.modal',function(){
//alert('当弹窗完全加载完后,再触发;')
});
$('#modal').on('hide.bs.modal',function(){
//alert('当关闭时,立即触发;')
});
$('#modal').on('hidden.bs.modal',function(){
//alert('当关完全关闭后,再触发;')
});
$('#modal').on('loaded.bs.modal',function(){
//alert('当远程数据加载完毕后,再触发;')
});
</script>
{% endblock %}

View File

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

View File

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

View File

@@ -0,0 +1,319 @@
{% 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>
{#<style>#}
{# .custom{#}
{# margin-right:5px;#}
{# }#}
{# #modal .modal-body { max-height: 200px; }#}
{#</style>#}
{% endblock %}
{% block content_left_head %}{% endblock %}
{% block table_search %}
<div class="html5buttons">
<div class="dt-buttons btn-group">
<a class="btn btn-default btn_import" data-toggle="modal" data-target="#asset_import_modal" tabindex="0">
<span>{% trans "Import" %}</span>
</a>
<a class="btn btn-default btn_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a>
</div>
</div>
{% endblock %}
{% block table_container %}
<div class="uc pull-left m-l-5 m-r-5"><a href="{% url "assets:asset-create" %}" class="btn btn-sm btn-primary"> {% trans "Create asset" %} </a></div>
<table class="table table-striped table-bordered table-hover " id="asset_list_table" >
<thead>
<tr>
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
<th class="text-center">{% trans 'Hostname' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'Port' %}</th>
<th class="text-center">{% trans 'Type' %}</th>
<th class="text-center">{% trans 'Env' %}</th>
<th class="text-center">{% trans 'Hardware' %}</th>
<th class="text-center">{% trans 'Valid' %}</th>
<th class="text-center">{% trans 'Alive' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div id="actions" class="hide">
<div class="input-group">
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
<option value="delete">{% trans 'Delete selected' %}</option>
<option value="update">{% trans 'Update selected' %}</option>
<option value="deactive">{% trans 'Deactive selected' %}</option>
<option value="active">{% trans 'Active' %}</option>
</select>
<div class="input-group-btn pull-left" style="padding-left: 5px;">
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
{% trans 'Submit' %}
</button>
</div>
</div>
</div>
{% include 'assets/_asset_import_modal.html' %}
{#{% include 'assets/_asset_bulk_update_modal.html' %}#}
{% endblock %}
{% block custom_foot_js %}
<script src="{% static 'js/jquery.form.min.js' %}"></script>
<script type="text/javascript">
window.onload = function (){
var tag_on = document.getElementsByName("tag_on");
var oDiv = document.getElementById("ydxbd");
if(tag_on.length > 0){
oDiv.style.display = "block";
}
};
function tagShow() {
var oDiv = document.getElementById("ydxbd");
if (oDiv.style.display == 'none'){
oDiv.style.display = "block";
}else{
oDiv.style.display = "none";
}
} //onload;
$(document).ready(function(){
var options = {
ele: $('#asset_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:asset-detail" pk=99991937 %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('99991937', rowData.id));
}},
{targets: 7, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 8, createdCell: function (td, cellData) {
if (cellData == 'Unknown'){
$(td).html('<i class="fa fa-circle text-warning"></i>')
} else if (!cellData) {
$(td).html('<i class="fa fa-circle text-danger"></i>')
} else {
$(td).html('<i class="fa fa-circle text-navy"></i>')
}
}},
{targets: 9, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:asset-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData);
$(td).html(update_btn + del_btn)
}}
],
ajax_url: '{% url "api-assets:asset-list" %}',
columns: [{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
{data: "get_type_display" }, {data: "get_env_display"}, {data: "hardware"},
{data: "is_active" }, {data: "is_online"}, {data: "id" }],
op_html: $('#actions').html()
};
var table = jumpserver.initDataTable(options);
$('.btn_export').click(function () {
var assets = [];
var rows = table.rows('.selected').data();
$.each(rows, function (index, obj) {
assets.push(obj.id)
});
console.log(assets);
$.ajax({
url: "{% url "assets:asset-export" %}",
method: 'POST',
data: JSON.stringify({assets_id: assets}),
dataType: "json",
success: function (data, textStatus) {
window.open(data.redirect)
},
error: function () {
toastr.error('Export failed');
}
})
});
$('#btn_asset_import').click(function() {
var $form = $('#fm_asset_import');
$form.find('.help-block').remove();
function success (data) {
if (data.valid === false) {
$('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_assets'));
} else {
$('#id_created').html(data.created_info);
$('#id_created_detail').html(data.created.join(', '));
$('#id_updated').html(data.updated_info);
$('#id_updated_detail').html(data.updated.join(', '));
$('#id_failed').html(data.failed_info);
$('#id_failed_detail').html(data.failed.join(', '));
var $data_table = $('#asset_list_table').DataTable();
$data_table.ajax.reload();
}
}
$form.ajaxSubmit({success: success});
})
})
.on('click', '.btn_asset_delete', function () {
var $this = $(this);
var $data_table = $("#asset_list_table").DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var the_url = '{% url "api-assets:asset-detail" pk=99991937 %}'.replace('99991937', uid);
console.log(the_url);
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 body = $.each(id_list, function(index, asset_object) {
asset_object['is_active'] = false;
});
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
$data_table.ajax.reload();
jumpserver.checked = false;
}
function doActive() {
var body = $.each(id_list, function(index, asset_object) {
asset_object['is_active'] = true;
});
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
$data_table.ajax.reload();
jumpserver.checked = false;
}
function doDelete() {
swal({
title: "{% trans 'Are you sure?' %}",
text: "{% trans 'This will delete the selected assets !!!' %}",
type: "warning",
showCancelButton: true,
confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false
}, function() {
var success = function() {
var msg = "{% trans 'Asset Deleted.' %}";
swal("{% trans 'Asset Delete' %}", msg, "success");
$('#asset_list_table').DataTable().ajax.reload();
};
var fail = function() {
var msg = "{% trans 'Asset Deleting failed.' %}";
swal("{% trans 'Asset Delete' %}", msg, "error");
};
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail});
$data_table.ajax.reload();
jumpserver.checked = false;
});
}
function doUpdate() {
var id_list_string = id_list.join(',');
var url = "{% url 'assets:asset-bulk-update' %}?assets_id=" + id_list_string;
location.href = url
}
switch(action) {
case 'deactive':
doDeactive();
break;
case 'delete':
doDelete();
break;
case 'update':
doUpdate();
break;
case 'active':
doActive();
break;
default:
break;
}
});
{##}
{#.on('click', '#btn_asset_bulk_update', function () {#}
{# var json_data = $("#fm_asset_bulk_update").serializeObject();#}
{# var body = {};#}
{# body.enable_otp = (json_data.enable_otp === 'on')? true: false;#}
{# if (json_data.type != '') {#}
{# body.type = json_data.type;#}
{# }#}
{# if (json_data.groups != undefined) {#}
{# body.groups = json_data.groups;#}
{# }#}
{# if (typeof body.groups === 'string') {#}
{# body.groups = [parseInt(body.groups)]#}
{# } else if(typeof body.groups === 'array') {#}
{# var new_groups = body.groups.map(Number);#}
{# body.groups = new_groups;#}
{# }#}
{##}
{# if (json_data.system_users != undefined) {#}
{# body.system_users = json_data.system_users;#}
{# }#}
{# if (typeof body.system_users === 'string') {#}
{# body.system_users = [parseInt(body.system_users)]#}
{# } else if(typeof body.system_users === 'array') {#}
{# var new_users = body.system_users.map(Number);#}
{# body.system_users = new_users;#}
{# }#}
{##}
{# if (json_data.tags != undefined) {#}
{# body.tags = json_data.tags;#}
{# }#}
{# if (typeof body.tags == 'string') {#}
{# body.tags = [parseInt(body.tags)];#}
{# } else if (typeof body.tags === 'array') {#}
{# var new_tags = body.tags.map(Number);#}
{# body.tags = new_tags;#}
{# }#}
{##}
{# var $data_table = $('#asset_list_table').DataTable();#}
{# var post_list = [];#}
{# $data_table.rows({selected: true}).every(function(){#}
{# var content = Object.assign({id: this.data().id}, body);#}
{# post_list.push(content);#}
{# });#}
{# if (post_list === []) {#}
{# return false#}
{# }#}
{# var the_url = "{% url 'api-assets:asset-list' %}";#}
{# var success = function() {#}
{# var msg = "{% trans 'The selected assets has been updated successfully.' %}";#}
{# swal("{% trans 'Asset Updated' %}", msg, "success");#}
{# $('#asset_list_table').DataTable().ajax.reload();#}
{# jumpserver.checked = false;#}
{# };#}
{# console.log(JSON.stringify(post_list));#}
{# console.log(the_url);#}
{# APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success});#}
{# $('#asset_bulk_update_modal').modal('hide');#}
{#})#}
</script>
{% endblock %}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,371 @@
{% 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 'Attached assets' %}</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-7" style="padding-left: 0;">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span style="float: left">{% trans '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>
<th>{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
{% for asset in page_obj %}
<tr>
<td>{{ asset.hostname }}</td>
<td>{{ asset.ip }}</td>
<td>{{ asset.port }}</td>
<td>
<i class="fa fa-check text-navy"></i>
</td>
<td>
<button class="btn btn-danger pull-right btn-xs {% if asset.is_inherit_from_asset_groups %} disabled {% endif %}" type="button"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{# <div class="row">#}
{# {% include '_pagination.html' %}#}
{# </div>#}
</div>
</div>
</div>
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Attach to assets ' %}
</div>
<div class="panel-body">
<table class="table">
<tbody>
<form>
<tr class="no-borders-tr">
<td colspan="2">
<select data-placeholder="{% trans 'Select asset' %}" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for asset in assets_remain %}
<option value="{{ asset.id }}">{{ asset.ip}}:{{ asset.port }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr class="no-borders-tr">
<td colspan="2">
<button type="button" class="btn btn-primary btn-sm btn-add-asset2system-user">{% trans 'Confirm' %}</button>
</td>
</tr>
</form>
</tbody>
</table>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Attach to asset groups' %}
</div>
<div class="panel-body">
<table class="table group_edit">
<tbody>
<form>
<tr>
<td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Add asset group' %}" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for asset_group in asset_groups_remain %}
<option value="{{ asset_group.id }}">{{ asset_group.name }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td colspan="2" class="no-borders">
<button type="button" class="btn btn-info btn-sm" id="btn_add_user_group">{% trans 'Attach AssetGroup' %}</button>
</td>
</tr>
</form>
{% for asset_group in asset_groups %}
<tr>
<td ><b class="bdg_asset_groups" data-gid={{ asset_group.id }}>{{ asset_group.name }}</b></td>
<td>
<button class="btn btn-danger pull-right btn-xs btn-leave-system_user" type="button"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
jumpserver.assets_selected = {};
jumpserver.asset_groups_selected = {};
Array.prototype.remove = function(val) {
var index = this.indexOf(val);
if (index > -1) {
this.splice(index, 1);
}
};
Array.prototype.unique = function(){
var res = [];
var json = {};
for(var i = 0; i < this.length; i++){
if(!json[this[i]]){
res.push(this[i]);
json[this[i]] = 1;
}
}
return res;
};
function objectDelete(obj, name, url, data) {
function doDelete() {
var body = data;
var success = function() {
swal('Deleted!', "[ "+name+"]"+" has been deleted ", "success");
$(obj).parent().parent().remove();
};
var fail = function() {
swal("Failed", "Delete"+"[ "+name+" ]"+"failed", "error");
};
APIUpdateAttr({
url: url,
body: JSON.stringify(body),
method: 'PATCH',
success: success,
error: fail
});
}
swal({
title: 'Are you sure delete ?',
text: " [" + name + "] ",
type: "warning",
showCancelButton: true,
cancelButtonText: 'Cancel',
confirmButtonColor: "#DD6B55",
confirmButtonText: 'Confirm',
closeOnConfirm: false
}, function () {
doDelete()
});
}
function updateSystemUserAssetGroup(asset_groups) {
var the_url = "{% url 'api-assets:systemuser-update-assetgroups' pk=system_user.id %}";
var body = {
asset_groups: Object.assign([], asset_groups)
};
var success = function(data) {
$('.select2-selection__rendered').empty();
$('#groups_selected').val('');
$.map(jumpserver.asset_groups_selected, function(asset_groups, index) {
$('#opt_' + index).remove();
$('.system-user-table tbody').append(
'<tr>' +
'<td><b class="bdg_asset_groups" data-sid="' + index + '">' + asset_groups + '</b></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn-leave-system_user" type="button"><i class="fa fa-minus"></i></button></td>' +
'</tr>'
)
});
jumpserver.assets_selected = {};
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
success: success
});
}
$(document).ready(function () {
$('.select2').select2()
.on("select2:select", function (evt) {
var data = evt.params.data;
jumpserver.assets_selected[data.id] = data.text;
jumpserver.asset_groups_selected[data.id] = data.text;
})
.on('select2:unselect', function(evt) {
var data = evt.params.data;
delete jumpserver.assets_selected[data.id];
delete jumpserver.asset_groups_selected[data.id];
});
var options = {
ele: $('#system_user_list'),
buttons: [],
order: [],
columnDefs: [
{targets: 0, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:asset-detail" pk=99991937 %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
$(td).html(detail_btn.replace('99991937', rowData.id));
}},
{targets: 3, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 4, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:asset-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', rowData.id);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-aid="99991937">{% trans "Delete" %}</a>'.replace('99991937', rowData.id);
$(td).html(update_btn + del_btn)
}}
],
ajax_url: '{% url "api-assets:asset-list" %}?system_user_id={{ system_user.id }}',
columns: [{data: "hostname" }, {data: "ip" }, {data: "port" }, {data: function () { return ""; } }, {data: "id"}],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
})
.on('click', '.btn-add-asset2system-user', function () {
if (Object.keys(jumpserver.assets_selected).length === 0) {
return false;
}
var $data_table = $("#system_user_list").DataTable();
var assets = [];
$.ajax({
url: '{% url "api-assets:asset-list" %}?system_user_id={{ system_user.id }}',
method: 'GET',
dataType: 'json',
success: function (result) {
for(var i in result){
if (!isNaN(parseInt(result[i]['id']))) {
assets.push(parseInt(result[i]['id']))
}
}
$.map(jumpserver.assets_selected, function(value, index) {
assets.push(parseInt(index));
});
assets.unique();
var the_url = "{% url 'api-assets:systemuser-update-assets' pk=system_user.id %}";
var body = {"assets": assets};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
method: 'PATCH'
});
$data_table.ajax.reload();
}
});
})
.on('click', '.btn_asset_delete', function () {
var $this = $(this);
var the_url = "{% url 'api-assets:systemuser-update-assets' pk=system_user.id %}";
var name = $(this).closest("tr").find(":nth-child(1) > a").html();
var $data_table = $("#system_user_list").DataTable();
var assets = [];
$('#system_user_list > tbody > tr').map(function () {
assets.push(parseInt($(this).closest("tr").find(":nth-child(1) > a").attr("data-aid")))
});
var delete_asset_id = $(this).data('aid');
assets.remove(delete_asset_id);
assets.unique();
var data = {"assets": assets};
objectDelete($this, name, the_url, data);
$data_table.ajax.reload();
})
.on('click', '#btn_add_user_group', function () {
jumpserver.assets_selected = {};
if (Object.keys(jumpserver.asset_groups_selected).length === 0) {
return false;
}
asset_groups = [];
$.ajax({
url: '{% url "api-assets:systemuser-update-assetgroups" pk=system_user.id %}',
method: 'GET',
dataType: 'json',
success: function (result) {
for (var i in result['asset_groups']) {
if (!isNaN(result['asset_groups'][i])) {
asset_groups.push(parseInt(result['asset_groups'][i]));
}
}
$.map(jumpserver.asset_groups_selected, function(value, index) {
asset_groups.push(parseInt(index));
});
asset_groups.unique();
console.log(asset_groups);
var the_url = '{% url "api-assets:systemuser-update-assetgroups" pk=system_user.id %}';
var body = {"asset_groups": asset_groups};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
method: 'PATCH'
});
{# TODO: reload the table #}
{# window.location.href="{% url 'assets:system-user-asset' pk=system_user.id %}"#}
}
});
})
.on('click', '.btn-leave-system_user', function () {
var $this = $(this);
var $tr = $this.closest('tr');
var $badge = $tr.find('.bdg_asset_groups');
var sid = $badge.data('gid');
var name = $badge.html() || $badge.text();
$('system-user-table').append(
'<option value="' + sid + '" id="opt_' + sid + '">' + name + '</option>'
);
$tr.remove();
var asset_groups = $('.bdg_asset_groups').map(function () {
return $(this).data('gid');
}).get();
updateSystemUserAssetGroup(asset_groups);
});
</script>
{% endblock %}

View File

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

View File

@@ -0,0 +1,178 @@
{% 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>Update</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-7" style="padding-left: 0;">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span 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 'Auto push' %}:</td>
<td><b>{{ system_user.auto_push|yesno:"Yes,No,Unknown" }}</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-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 update' %}
</div>
<div class="panel-body">
<table class="table">
<tbody>
<tr class="no-borders-tr">
<td width="50%">{% trans 'Get manual install script' %}:</td>
<td>
<span style="float: right">
<button type="button" class="btn btn-primary btn-xs" style="width: 54px">{% trans 'Get' %}</button>
</span>
</td>
</tr>
<tr>
<td width="50%">{% trans 'Retest asset connectivity' %}:</td>
<td>
<span style="float: right">
<button type="button" class="btn btn-primary btn-xs" style="width: 54px">{% trans 'Start' %}</button>
</span>
</td>
</tr>
<tr>
<td width="50%">{% trans 'Reset private key' %}:</td>
<td>
<span style="float: right">
<button type="button" class="btn btn-primary btn-xs" style="width: 54px">{% trans 'Reset' %}</button>
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
{# function switch_user_status(obj) {#}
{# var status = $(obj).prop('checked');#}
{##}
{# $.ajax({#}
{# url: "{% url 'users:user-active-api' pk=user.id %}",#}
{# type: "PUT",#}
{# data: {#}
{# 'is_active': status#}
{# },#}
{# success: function (data, status) {#}
{# console.log(data)#}
{# },#}
{# error: function () {#}
{# console.log('error')#}
{# }#}
{# })#}
{# }#}
$(document).ready(function () {
$('.select2').select2();
});
</script>
{% endblock %}

View File

@@ -0,0 +1,138 @@
{% extends '_base_list.html' %}
{% load i18n %}
{% block table_search %}
{% endblock %}
{% block table_container %}
<div class="uc pull-left m-l-5 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 'Unreachable' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div id="actions" class="hide">
<div class="input-group">
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
<option value="delete">{% trans 'Delete selected' %}</option>
<option value="update">{% trans 'Update selected' %}</option>
</select>
<div class="input-group-btn pull-left" style="padding-left: 5px;">
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
{% trans 'Submit' %}
</button>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function(){
var options = {
ele: $('#system_user_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:system-user-detail" pk=99991937 %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('99991937', rowData.id));
}},
{targets: 5, createdCell: function (td, cellData) {
var innerHtml = cellData.length > 30 ? cellData.substring(0, 30) + '...': cellData;
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}},
{targets: 6, createdCell: function (td, cellData, rowData) {
{# var script_btn = '<a href="{% url "assets:system-user-update" pk=99991937 %}" class="btn btn-xs btn-primary">{% trans "Script" %}</a>'.replace('99991937', cellData);#}
var update_btn = '<a href="{% url "assets:system-user-update" pk=99991937 %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData);
$(td).html(update_btn + del_btn)
}}],
ajax_url: '{% url "api-assets:system-user-list" %}',
columns: [{data: "id" }, {data: "name" }, {data: "username" }, {data: "assets_amount" }, {data: function () { return "3"}},
{data: "comment" }, {data: "id" }],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
})
.on('click', '.btn_admin_user_delete', function () {
var $this = $(this);
var $data_table = $('#idc_list_table').DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var the_url = '{% url "api-assets:system-user-detail" pk=99991937 %}'.replace('99991937', uid);
objectDelete($this, name, the_url);
setTimeout( function () {
$data_table.ajax.reload();
}, 3000);
})
.on('click', '#btn_bulk_update', function () {
var action = $('#slct_bulk_update').val();
var $data_table = $('#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,53 @@
{% extends 'assets/_system_user.html' %}
{% load i18n %}
{% load static %}
{% load bootstrap3 %}
{% block auth %}
<div class="password-auth hidden">
{% bootstrap_field form.password layout="horizontal" %}
</div>
<div class="public-key-auth">
<div>
{% bootstrap_field form.private_key_file layout="horizontal" %}
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
var auth_method = '#'+'{{ form.auth_method.id_for_label }}';
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
function authMethodDisplay() {
if ($(auth_method).val() == 'P') {
$('.password-auth').removeClass('hidden');
$('.public-key-auth').addClass('hidden');
$('#'+'{{ form.password.id_for_label }}').removeAttr('disabled');
} else if ($(auth_method).val() == 'K') {
$('.password-auth').addClass('hidden');
$('.public-key-auth').removeClass('hidden');
$('#'+'{{ form.password.id_for_label }}').removeAttr('required');
$('#'+'{{ form.password.id_for_label }}').attr('disabled', 'disabled');
if ($(auto_generate_key).prop('checked')){
$('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group').addClass('hidden');
} else {
$('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group').removeClass('hidden');
}
}
}
$(document).ready(function () {
$('.select2').select2();
authMethodDisplay();
$(auth_method).change(function () {
authMethodDisplay();
});
$(auto_generate_key).change(function () {
authMethodDisplay();
});
})
</script>
{% endblock %}

View File

@@ -0,0 +1,264 @@
{% 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>
<style>
.custom{
margin-right:5px;
}
#modal .modal-body { max-height: 200px; }
</style>
{% endblock %}
{% block content_left_head %}{% endblock %}
{% block table_search %}
{# <div class="html5buttons">#}
{# <div class="dt-buttons btn-group">#}
{# <a class="btn btn-default btn_export" tabindex="0">#}
{# <span>{% trans "Export" %}</span>#}
{# </a>#}
{# </div>#}
{# </div>#}
{% 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 'Type' %}</th>
<th class="text-center">{% trans 'Env' %}</th>
<th class="text-center">{% trans 'Hardware' %}</th>
<th class="text-center">{% trans 'Valid' %}</th>
<th class="text-center">{% trans 'Alive' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% endblock %}
{% block custom_foot_js %}
<script src="{% static 'js/jquery.form.min.js' %}"></script>
<script type="text/javascript">
$(document).ready(function(){
var options = {
ele: $('#asset_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:asset-detail" pk=99991937 %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('99991937', rowData.id));
}},
{targets: 7, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 8, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-circle text-danger"></i>')
} else {
$(td).html('<i class="fa fa-circle text-navy"></i>')
}
}}
],
ajax_url: '{% url "api-assets:asset-list" %}',
columns: [{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
{data: "get_type_display" }, {data: "get_env_display"}, {data: "hardware"},
{data: "is_active" }, {data: "is_active"}],
};
var table = jumpserver.initDataTable(options);
$('.btn_export').click(function () {
var assets = [];
var rows = table.rows('.selected').data();
$.each(rows, function (index, obj) {
assets.push(obj.id)
});
console.log(assets);
$.ajax({
url: "{% url "assets:asset-export" %}",
method: 'POST',
data: JSON.stringify({assets_id: assets}),
dataType: "json",
success: function (data, textStatus) {
window.open(data.redirect)
},
error: function () {
toastr.error('Export failed');
}
})
});
$('#btn_asset_import').click(function() {
var $form = $('#fm_asset_import');
$form.find('.help-block').remove();
function success (data) {
if (data.valid === false) {
$('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_assets'));
} else {
$('#id_created').html(data.created_info);
$('#id_created_detail').html(data.created.join(', '));
$('#id_updated').html(data.updated_info);
$('#id_updated_detail').html(data.updated.join(', '));
$('#id_failed').html(data.failed_info);
$('#id_failed_detail').html(data.failed.join(', '));
var $data_table = $('#asset_list_table').DataTable();
$data_table.ajax.reload();
}
}
$form.ajaxSubmit({success: success});
})
})
.on('click', '.btn_asset_delete', function () {
var $this = $(this);
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var uid = $this.data('uid');
var the_url = '{% url "api-assets:asset-detail" pk=99991937 %}'.replace('99991937', uid);
objectDelete($this, name, the_url);
})
.on('click', '#btn_bulk_update', function () {
var action = $('#slct_bulk_update').val();
var $data_table = $('#asset_list_table').DataTable();
var id_list = [];
var plain_id_list = [];
$data_table.rows({selected: true}).every(function(){
id_list.push({id: this.data().id});
plain_id_list.push(this.data().id);
});
if (plain_id_list.length == 0) {
return false;
}
var the_url = "{% url 'api-assets:asset-list' %}";
function doDeactive() {
var body = $.each(id_list, function(index, asset_object) {
asset_object['is_active'] = false;
});
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
$data_table.ajax.reload();
jumpserver.checked = false;
}
function doActive() {
var body = $.each(id_list, function(index, asset_object) {
asset_object['is_active'] = true;
});
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
$data_table.ajax.reload();
jumpserver.checked = false;
}
function doDelete() {
swal({
title: "{% trans 'Are you sure?' %}",
text: "{% trans 'This will delete the selected assets !!!' %}",
type: "warning",
showCancelButton: true,
confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false
}, function() {
var success = function() {
var msg = "{% trans 'Asset Deleted.' %}";
swal("{% trans 'Asset Delete' %}", msg, "success");
$('#asset_list_table').DataTable().ajax.reload();
};
var fail = function() {
var msg = "{% trans 'Asset Deleting failed.' %}";
swal("{% trans 'Asset Delete' %}", msg, "error");
};
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail});
$data_table.ajax.reload();
jumpserver.checked = false;
});
}
function doUpdate() {
$('#asset_bulk_update_modal').modal('show');
}
switch(action) {
case 'deactive':
doDeactive();
break;
case 'delete':
doDelete();
break;
case 'update':
doUpdate();
break;
case 'active':
doActive();
break;
default:
break;
}
})
.on('click', '#btn_asset_bulk_update', function () {
var json_data = $("#fm_asset_bulk_update").serializeObject();
var body = {};
body.enable_otp = (json_data.enable_otp === 'on')? true: false;
if (json_data.type != '') {
body.type = json_data.type;
}
if (json_data.groups != undefined) {
body.groups = json_data.groups;
}
if (typeof body.groups === 'string') {
body.groups = [parseInt(body.groups)]
} else if(typeof body.groups === 'array') {
var new_groups = body.groups.map(Number);
body.groups = new_groups;
}
if (json_data.system_users != undefined) {
body.system_users = json_data.system_users;
}
if (typeof body.system_users === 'string') {
body.system_users = [parseInt(body.system_users)]
} else if(typeof body.system_users === 'array') {
var new_users = body.system_users.map(Number);
body.system_users = new_users;
}
if (json_data.tags != undefined) {
body.tags = json_data.tags;
}
if (typeof body.tags == 'string') {
body.tags = [parseInt(body.tags)];
} else if (typeof body.tags === 'array') {
var new_tags = body.tags.map(Number);
body.tags = new_tags;
}
var $data_table = $('#asset_list_table').DataTable();
var post_list = [];
$data_table.rows({selected: true}).every(function(){
var content = Object.assign({id: this.data().id}, body);
post_list.push(content);
});
if (post_list === []) {
return false
}
var the_url = "{% url 'api-assets:asset-list' %}";
var success = function() {
var msg = "{% trans 'The selected assets has been updated successfully.' %}";
swal("{% trans 'Asset Updated' %}", msg, "success");
$('#asset_list_table').DataTable().ajax.reload();
jumpserver.checked = false;
};
console.log(JSON.stringify(post_list));
console.log(the_url);
{# APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success});#}
$('#asset_bulk_update_modal').modal('hide');
});
</script>
{% endblock %}

View File

@@ -0,0 +1,6 @@
from django import template
from django.utils import timezone
from django.conf import settings
register = template.Library()

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,57 @@
# coding:utf-8
from django.conf.urls import url
from .. import api
from rest_framework import routers
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/idc', api.IDCViewSet, 'idc')
router.register(r'v1/admin-user', api.AdminUserViewSet, 'admin-user')
router.register(r'v1/system-user', api.SystemUserViewSet, 'system-user')
urlpatterns = [
url(r'^v1/assets-bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
url(r'^v1/system-user/(?P<pk>[0-9]+)/auth-info/', api.SystemUserAuthInfoApi.as_view(),
name='system-user-auth-info'),
url(r'^v1/assets/(?P<pk>\d+)/groups/$',
api.AssetUpdateGroupApi.as_view(), name='asset-update-group'),
url(r'^v1/assets/(?P<pk>\d+)/refresh/$',
api.AssetRefreshHardwareView.as_view(), name='asset-refresh'),
url(r'^v1/assets/(?P<pk>\d+)/admin-user-test/$',
api.AssetAdminUserTestView.as_view(), name='asset-admin-user-test'),
url(r'^v1/assets/(?P<pk>\d+)/system-users/$',
api.SystemUserUpdateApi.as_view(), name='asset-update-system-users'),
url(r'^v1/groups/(?P<pk>\d+)/push-system-user/$',
api.AssetGroupPushSystemUserView.as_view(), name='asset-group-push-system-user'),
# update the system users, which add and delete the asset to the system user
url(r'^v1/system-user/(?P<pk>\d+)/assets/$',
api.SystemUserUpdateAssetsApi.as_view(), name='systemuser-update-assets'),
url(r'^v1/system-user/(?P<pk>\d+)/groups/$',
api.SystemUserUpdateAssetGroupApi.as_view(), name='systemuser-update-assetgroups'),
# update the asset group, which add or delete the asset to the group
url(r'^v1/groups/(?P<pk>\d+)/assets/$',
api.AssetGroupUpdateApi.as_view(), name='asset-groups-update'),
# update the asset group, and add or delete the system_user to the group
url(r'^v1/groups/(?P<pk>\d+)/system-users/$',
api.AssetGroupUpdateSystemUserApi.as_view(), name='asset-groups-update-systemusers'),
# update the IDC, and add or delete the assets to the IDC
url(r'^v1/idc/(?P<pk>\d+)/assets/$',
api.IDCUpdateAssetsApi.as_view(), name='idc-update-assets'),
]
urlpatterns += router.urls

View File

@@ -0,0 +1,56 @@
# 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-9]+)/$', views.AssetDetailView.as_view(), name='asset-detail'),
url(r'^asset/(?P<pk>[0-9]+)/update/$', views.AssetUpdateView.as_view(), name='asset-update'),
url(r'^asset/(?P<pk>[0-9]+)/delete/$', views.AssetDeleteView.as_view(), name='asset-delete'),
url(r'^asset-modal$', views.AssetModalListView.as_view(), name='asset-modal-list'),
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 asset group url
url(r'^asset-group/$', views.AssetGroupListView.as_view(), name='asset-group-list'),
url(r'^asset-group/create/$', views.AssetGroupCreateView.as_view(), name='asset-group-create'),
url(r'^asset-group/(?P<pk>[0-9]+)/$', views.AssetGroupDetailView.as_view(), name='asset-group-detail'),
url(r'^asset-group/(?P<pk>[0-9]+)/update/$', views.AssetGroupUpdateView.as_view(), name='asset-group-update'),
url(r'^asset-group/(?P<pk>[0-9]+)/delete/$', views.AssetGroupDeleteView.as_view(), name='asset-group-delete'),
# Resource idc url
url(r'^idc/$', views.IDCListView.as_view(), name='idc-list'),
url(r'^idc/create/$', views.IDCCreateView.as_view(), name='idc-create'),
url(r'^idc/(?P<pk>[0-9]+)/$', views.IDCDetailView.as_view(), name='idc-detail'),
url(r'^idc/(?P<pk>[0-9]+)/update/', views.IDCUpdateView.as_view(), name='idc-update'),
url(r'^idc/(?P<pk>[0-9]+)/delete/$', views.IDCDeleteView.as_view(), name='idc-delete'),
url(r'^idc/(?P<pk>[0-9]+)/assets/$', views.IDCAssetsView.as_view(), name='idc-assets'),
# 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-9]+)/$', views.AdminUserDetailView.as_view(), name='admin-user-detail'),
url(r'^admin-user/(?P<pk>[0-9]+)/update/$', views.AdminUserUpdateView.as_view(), name='admin-user-update'),
url(r'^admin-user/(?P<pk>[0-9]+)/delete/$', views.AdminUserDeleteView.as_view(), name='admin-user-delete'),
# 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-9]+)/$', views.SystemUserDetailView.as_view(), name='system-user-detail'),
url(r'^system-user/(?P<pk>[0-9]+)/update/$', views.SystemUserUpdateView.as_view(), name='system-user-update'),
url(r'^system-user/(?P<pk>[0-9]+)/delete/$', views.SystemUserDeleteView.as_view(), name='system-user-delete'),
url(r'^system-user/(?P<pk>[0-9]+)/asset/$', views.SystemUserAssetView.as_view(), name='system-user-asset'),
# url(r'^system-user/(?P<pk>[0-9]+)/asset-group$', views.SystemUserAssetGroupView.as_view(),
# name='system-user-asset-group'),
]

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

@@ -0,0 +1,17 @@
# ~*~ coding: utf-8 ~*~
#
from ops.utils import run_AdHoc
def test_admin_user_connective_manual(asset):
if not isinstance(asset, list):
asset = [asset]
task_tuple = (
('ping', ''),
)
summary, _ = run_AdHoc(task_tuple, asset, record=False)
if len(summary['failed']) != 0:
return False
else:
return True

View File

@@ -0,0 +1,7 @@
# coding:utf-8
from .asset import *
from .group import *
from .idc import *
from .system_user import *
from .admin_user import *

View File

@@ -0,0 +1,112 @@
# coding:utf-8
from __future__ import absolute_import, unicode_literals
from django.utils.translation import ugettext as _
from django.conf import settings
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.contrib.messages.views import SuccessMessageMixin
from django.views.generic.detail import DetailView, SingleObjectMixin
from .. import forms
from ..models import Asset, AssetGroup, AdminUser, IDC, SystemUser
from ..hands import AdminUserRequiredMixin
__all__ = ['AdminUserCreateView', 'AdminUserDetailView',
'AdminUserDeleteView', 'AdminUserListView',
'AdminUserUpdateView',
]
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(AdminUserListView, self).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')
def get_context_data(self, **kwargs):
context = {
'app': 'assets',
'action': 'Create admin user'
}
kwargs.update(context)
return super(AdminUserCreateView, self).get_context_data(**kwargs)
def get_success_message(self, cleaned_data):
success_message = _(
'Create admin user <a href="{url}">{name}</a> successfully.'.format(
url=reverse_lazy('assets:admin-user-detail',
kwargs={'pk': self.object.pk}),
name=self.object.name,
))
return success_message
def form_invalid(self, form):
return super(AdminUserCreateView, self).form_invalid(form)
class AdminUserUpdateView(AdminUserRequiredMixin, UpdateView):
model = AdminUser
form_class = forms.AdminUserForm
template_name = 'assets/admin_user_create_update.html'
def get_context_data(self, **kwargs):
context = {
'app': 'assets',
'action': 'Update admin user'
}
kwargs.update(context)
return super(AdminUserUpdateView, self).get_context_data(**kwargs)
def get_success_url(self):
success_url = reverse_lazy('assets:admin-user-detail',
kwargs={'pk': self.object.pk})
return success_url
class AdminUserDetailView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
template_name = 'assets/admin_user_detail.html'
context_object_name = 'admin_user'
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=AdminUser.objects.all())
return super(AdminUserDetailView, self).get(request, *args, **kwargs)
def get_queryset(self):
return self.object.assets.all()
def get_context_data(self, **kwargs):
asset_groups = AssetGroup.objects.all()
assets = self.get_queryset()
context = {
'app': 'assets',
'action': 'Admin user detail',
'assets_remain': [asset for asset in Asset.objects.all() if asset not in assets],
'asset_groups': asset_groups,
}
kwargs.update(context)
return super(AdminUserDetailView, self).get_context_data(**kwargs)
class AdminUserDeleteView(AdminUserRequiredMixin, DeleteView):
model = AdminUser
template_name = 'assets/delete_confirm.html'
success_url = reverse_lazy('assets:admin-user-list')

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

@@ -0,0 +1,345 @@
# 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 collections import defaultdict
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, SingleObjectMixin
from django.http import HttpResponse, JsonResponse, HttpResponseRedirect
from django.views.decorators.csrf import csrf_protect, 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 get_object_or_404, redirect, reverse
from common.mixins import JSONResponseMixin
from common.utils import get_object_or_none
from .. import forms
from ..models import Asset, AssetGroup, AdminUser, IDC, SystemUser
from ..hands import AdminUserRequiredMixin
from ..tasks import update_assets_hardware_info
__all__ = ['AssetListView', 'AssetCreateView', 'AssetUpdateView',
'UserAssetListView', 'AssetBulkUpdateView', 'AssetDetailView',
'AssetModalListView', 'AssetDeleteView', 'AssetExportView',
'BulkImportAssetView',
]
class AssetListView(AdminUserRequiredMixin, TemplateView):
template_name = 'assets/asset_list.html'
def get_context_data(self, **kwargs):
context = {
'app': 'Assets',
'action': 'Asset list',
'groups': AssetGroup.objects.all(),
'system_users': SystemUser.objects.all(),
# 'form': forms.AssetBulkUpdateForm(),
}
kwargs.update(context)
return super(AssetListView, self).get_context_data(**kwargs)
class UserAssetListView(LoginRequiredMixin, TemplateView):
template_name = 'assets/user_asset_list.html'
def get_context_data(self, **kwargs):
context = {
'app': 'Assets',
'action': 'Asset list',
'system_users': SystemUser.objects.all(),
}
kwargs.update(context)
return super(UserAssetListView, self).get_context_data(**kwargs)
class AssetCreateView(AdminUserRequiredMixin, 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):
self.asset = asset = form.save()
asset.created_by = self.request.user.username or 'Admin'
asset.date_created = timezone.now()
asset.save()
return super(AssetCreateView, self).form_valid(form)
def get_context_data(self, **kwargs):
context = {
'app': 'Assets',
'action': 'Create asset',
}
kwargs.update(context)
return super(AssetCreateView, self).get_context_data(**kwargs)
def get_success_url(self):
update_assets_hardware_info.delay([self.asset._to_secret_json()])
return super(AssetCreateView, self).get_success_url()
class AssetModalListView(AdminUserRequiredMixin, ListView):
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
model = Asset
context_object_name = 'asset_modal_list'
template_name = 'assets/asset_modal_list.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(AssetModalListView, self).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')
def get(self, request, *args, **kwargs):
assets_id = self.request.GET.get('assets_id', '')
self.assets_id_list = [int(i) for i in assets_id.split(',') if i.isdigit()]
if kwargs.get('form'):
self.form = kwargs['form']
elif assets_id:
self.form = self.form_class(
initial={'assets': self.assets_id_list}
)
else:
self.form = self.form_class()
return super(AssetBulkUpdateView, self).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):
# assets_list = Asset.objects.filter(id__in=self.assets_id_list)
context = {
'app': 'Assets',
'action': 'Bulk update asset',
'form': self.form,
'assets_selected': self.assets_id_list,
'assets': Asset.objects.all(),
}
kwargs.update(context)
return super(AssetBulkUpdateView, self).get_context_data(**kwargs)
class AssetUpdateView(AdminUserRequiredMixin, 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(AssetUpdateView, self).get_context_data(**kwargs)
def form_invalid(self, form):
print(form.errors)
return super(AssetUpdateView, self).form_invalid(form)
class AssetDeleteView(AdminUserRequiredMixin, DeleteView):
model = Asset
template_name = 'assets/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):
asset_groups = self.object.groups.all()
system_users = self.object.system_users.all()
context = {
'app': 'Assets',
'action': 'Asset detail',
'asset_groups_remain': [asset_group for asset_group in AssetGroup.objects.all()
if asset_group not in asset_groups],
'asset_groups': asset_groups,
'system_users_all': SystemUser.objects.all(),
'system_users': system_users,
}
kwargs.update(context)
return super(AssetDetailView, self).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 [1]
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]
header.append(_('Asset groups'))
writer.writerow(header)
for asset in assets:
groups = ','.join([group.name for group in asset.groups.all()])
data = [getattr(asset, field.name) for field in fields]
data.append(groups)
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}
mapping_reverse[_('Asset groups')] = 'groups'
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)
try:
id_ = int(id_)
except ValueError:
id_ = 0
asset = get_object_or_none(Asset, id=id_)
for k, v in asset_dict.items():
if k == 'idc':
v = get_object_or_none(IDC, name=v)
elif k == 'is_active':
v = bool(v)
elif k == 'admin_user':
v = get_object_or_none(AdminUser, name=v)
elif k in ['port', 'cabinet_pos', 'cpu_count', 'cpu_cores']:
try:
v = int(v)
except ValueError:
v = 0
elif k == 'groups':
groups_name = v.split(',')
v = AssetGroup.objects.filter(name__in=groups_name)
else:
continue
asset_dict[k] = v
if not asset:
try:
groups = asset_dict.pop('groups')
if len(Asset.objects.filter(hostname=asset_dict.get('hostname'))):
raise Exception(_('already exists'))
asset = Asset.objects.create(**asset_dict)
asset.groups.set(groups)
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 k == 'groups':
asset.groups.set(v)
continue
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)))
if assets:
update_assets_hardware_info.delay([asset._to_secret_json() for asset in assets])
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)

111
apps/assets/views/group.py Normal file
View File

@@ -0,0 +1,111 @@
# coding:utf-8
from __future__ import absolute_import, unicode_literals
from django.utils.translation import ugettext 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, SingleObjectMixin
from django.shortcuts import get_object_or_404, reverse, redirect
from .. import forms
from ..models import Asset, AssetGroup, AdminUser, IDC, SystemUser
from ..hands import AdminUserRequiredMixin
__all__ = ['AssetGroupCreateView', 'AssetGroupDetailView',
'AssetGroupUpdateView', 'AssetGroupListView',
'AssetGroupDeleteView',
]
class AssetGroupCreateView(AdminUserRequiredMixin, CreateView):
model = AssetGroup
form_class = forms.AssetGroupForm
template_name = 'assets/asset_group_create.html'
success_url = reverse_lazy('assets:asset-group-list')
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Create asset group'),
'assets_count': 0,
}
kwargs.update(context)
return super(AssetGroupCreateView, self).get_context_data(**kwargs)
def form_valid(self, form):
asset_group = form.save()
assets_id_list = self.request.POST.getlist('assets', [])
assets = [get_object_or_404(Asset, id=int(asset_id))
for asset_id in assets_id_list]
asset_group.created_by = self.request.user.username or 'Admin'
asset_group.assets.add(*tuple(assets))
asset_group.save()
return super(AssetGroupCreateView, self).form_valid(form)
class AssetGroupListView(AdminUserRequiredMixin, TemplateView):
template_name = 'assets/asset_group_list.html'
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Asset group list'),
'assets': Asset.objects.all(),
'system_users': SystemUser.objects.all(),
'keyword': self.request.GET.get('keyword', '')
}
kwargs.update(context)
return super(AssetGroupListView, self).get_context_data(**kwargs)
class AssetGroupDetailView(AdminUserRequiredMixin, DetailView):
model = AssetGroup
template_name = 'assets/asset_group_detail.html'
context_object_name = 'asset_group'
def get_context_data(self, **kwargs):
assets_remain = Asset.objects.exclude(id__in=self.object.assets.all())
system_users = SystemUser.objects.all()
system_users_remain = SystemUser.objects.exclude(id__in=system_users)
context = {
'app': _('Assets'),
'action': _('Asset group detail'),
'assets_remain': assets_remain,
'assets': [asset for asset in Asset.objects.all()
if asset not in assets_remain],
'system_users': system_users,
'system_users_remain': system_users_remain,
}
kwargs.update(context)
return super(AssetGroupDetailView, self).get_context_data(**kwargs)
class AssetGroupUpdateView(AdminUserRequiredMixin, UpdateView):
model = AssetGroup
form_class = forms.AssetGroupForm
template_name = 'assets/asset_group_create.html'
success_url = reverse_lazy('assets:asset-group-list')
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=AssetGroup.objects.all())
return super(AssetGroupUpdateView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
assets_all = self.object.assets.all()
context = {
'app': _('Assets'),
'action': _('Create asset group'),
'assets_on_list': assets_all,
'assets_count': len(assets_all),
'group_id': self.object.id,
}
kwargs.update(context)
return super(AssetGroupUpdateView, self).get_context_data(**kwargs)
class AssetGroupDeleteView(AdminUserRequiredMixin, DeleteView):
template_name = 'assets/delete_confirm.html'
model = AssetGroup
success_url = reverse_lazy('assets:asset-group-list')

101
apps/assets/views/idc.py Normal file
View File

@@ -0,0 +1,101 @@
# coding:utf-8
from __future__ import absolute_import, unicode_literals
from django.utils.translation import ugettext 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, SingleObjectMixin
from .. import forms
from ..models import Asset, AssetGroup, AdminUser, IDC, SystemUser
from ..hands import AdminUserRequiredMixin
__all__ = ['IDCListView', 'IDCCreateView', 'IDCUpdateView',
'IDCDetailView', 'IDCDeleteView', 'IDCAssetsView']
class IDCListView(AdminUserRequiredMixin, TemplateView):
template_name = 'assets/idc_list.html'
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('IDC list'),
# 'keyword': self.request.GET.get('keyword', '')
}
kwargs.update(context)
return super(IDCListView, self).get_context_data(**kwargs)
class IDCCreateView(AdminUserRequiredMixin, CreateView):
model = IDC
form_class = forms.IDCForm
template_name = 'assets/idc_create_update.html'
success_url = reverse_lazy('assets:idc-list')
def get_context_data(self, **kwargs):
context = {
'app': _('assets'),
'action': _('Create IDC'),
}
kwargs.update(context)
return super(IDCCreateView, self).get_context_data(**kwargs)
def form_valid(self, form):
idc = form.save(commit=False)
idc.created_by = self.request.user.username or 'System'
idc.save()
return super(IDCCreateView, self).form_valid(form)
class IDCUpdateView(AdminUserRequiredMixin, UpdateView):
model = IDC
form_class = forms.IDCForm
template_name = 'assets/idc_create_update.html'
context_object_name = 'idc'
success_url = reverse_lazy('assets:idc-list')
def form_valid(self, form):
idc = form.save(commit=False)
idc.save()
return super(IDCUpdateView, self).form_valid(form)
def get_context_data(self, **kwargs):
context = {
'app': _('assets'),
'action': _('Update IDC'),
}
kwargs.update(context)
return super(IDCUpdateView, self).get_context_data(**kwargs)
class IDCDetailView(AdminUserRequiredMixin, DetailView):
model = IDC
template_name = 'assets/idc_detail.html'
context_object_name = 'idc'
class IDCAssetsView(AdminUserRequiredMixin, DetailView):
model = IDC
template_name = 'assets/idc_assets.html'
context_object_name = 'idc'
def get_context_data(self, **kwargs):
assets_remain = Asset.objects.exclude(id__in=self.object.assets.all())
context = {
'app': _('Assets'),
'action': _('Asset detail'),
'groups': AssetGroup.objects.all(),
'system_users': SystemUser.objects.all(),
'assets_remain': assets_remain,
'assets': [asset for asset in Asset.objects.all() if asset not in assets_remain],
}
kwargs.update(context)
return super(IDCAssetsView, self).get_context_data(**kwargs)
class IDCDeleteView(AdminUserRequiredMixin, DeleteView):
model = IDC
template_name = 'assets/delete_confirm.html'
success_url = reverse_lazy('assets:idc-list')

View File

@@ -0,0 +1,147 @@
# ~*~ coding: utf-8 ~*~
from __future__ import absolute_import, unicode_literals
from django.utils.translation import ugettext as _
from django.conf import settings
from django.db import transaction
from django.views.generic import TemplateView, ListView
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, SingleObjectMixin
from .. import forms
from ..models import Asset, AssetGroup, SystemUser
from ..hands import AdminUserRequiredMixin
from perms.utils import associate_system_users_and_assets
__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(SystemUserListView, self).get_context_data(**kwargs)
class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
model = SystemUser
form_class = forms.SystemUserForm
template_name = 'assets/system_user_create.html'
success_url = reverse_lazy('assets:system-user-list')
@transaction.atomic
def post(self, request, *args, **kwargs):
return super(SystemUserCreateView, self).post(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Create system user'),
}
kwargs.update(context)
return super(SystemUserCreateView, self).get_context_data(**kwargs)
def get_success_message(self, cleaned_data):
url = reverse_lazy('assets:system-user-detail',
kwargs={'pk': self.object.pk}),
success_message = _(
'Create system user <a href="{url}">{name}</a> '
'successfully.'.format(url=url, name=self.object.name)
)
return success_message
class SystemUserUpdateView(AdminUserRequiredMixin, UpdateView):
model = SystemUser
form_class = forms.SystemUserUpdateForm
template_name = 'assets/system_user_update.html'
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Update system user')
}
kwargs.update(context)
return super(SystemUserUpdateView, self).get_context_data(**kwargs)
def form_valid(self, form):
response = super(SystemUserUpdateView, self).form_valid(form)
system_user = self.object
assets = system_user.assets.all()
asset_groups = system_user.asset_groups.all()
associate_system_users_and_assets([system_user], assets, asset_groups, force=True)
return response
def get_success_url(self):
success_url = reverse_lazy('assets:system-user-detail',
kwargs={'pk': self.object.pk})
return success_url
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')
}
kwargs.update(context)
return super(SystemUserDetailView, self).get_context_data(**kwargs)
class SystemUserDeleteView(AdminUserRequiredMixin, DeleteView):
model = SystemUser
template_name = 'assets/delete_confirm.html'
success_url = reverse_lazy('assets:system-user-list')
class SystemUserAssetView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
template_name = 'assets/system_user_asset.html'
context_object_name = 'system_user'
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=SystemUser.objects.all())
return super(SystemUserAssetView, self).get(request, *args, **kwargs)
def get_asset_groups(self):
return self.object.asset_groups.all()
# Todo: queryset default order by connectivity, need ops support
def get_queryset(self):
return list(self.object.get_assets())
def get_context_data(self, **kwargs):
asset_groups = self.get_asset_groups()
assets = self.get_queryset()
context = {
'app': 'assets',
'action': 'System user asset',
'assets_remain': [asset for asset in Asset.objects.all() if asset not in assets],
'asset_groups': asset_groups,
'asset_groups_remain': [asset_group for asset_group in AssetGroup.objects.all()
if asset_group not in asset_groups]
}
kwargs.update(context)
return super(SystemUserAssetView, self).get_context_data(**kwargs)

1
apps/audits/__init__.py Normal file
View File

@@ -0,0 +1 @@

92
apps/audits/api.py Normal file
View File

@@ -0,0 +1,92 @@
# ~*~ coding: utf-8 ~*~
#
from __future__ import absolute_import, unicode_literals
from rest_framework import generics, viewsets
from rest_framework_bulk import BulkModelViewSet
from audits.backends import command_store, record_store
from audits.backends.command.serializers import CommandLogSerializer
from audits.backends.record.serializers import RecordSerializer
from . import models, serializers
from .hands import IsSuperUserOrAppUser, IsAppUser
class ProxyLogReceiveView(generics.CreateAPIView):
queryset = models.ProxyLog.objects.all()
serializer_class = serializers.ProxyLogSerializer
permission_classes = (IsAppUser,)
def get_serializer(self, *args, **kwargs):
kwargs['data']['terminal'] = self.request.user.terminal.name
return super(ProxyLogReceiveView, self).get_serializer(*args, **kwargs)
class ProxyLogViewSet(viewsets.ModelViewSet):
"""User proxy to backend server need call this api.
params: {
"username": "",
"name": "",
"hostname": "",
"ip": "",
"terminal": "",
"login_type": "",
"system_user": "",
"was_failed": "",
"date_start": ""
}
"""
queryset = models.ProxyLog.objects.all()
serializer_class = serializers.ProxyLogSerializer
permission_classes = (IsSuperUserOrAppUser,)
class CommandLogViewSet(BulkModelViewSet):
"""接受app发送来的command log, 格式如下
{
"proxy_log_id": 23,
"user": "admin",
"asset": "localhost",
"system_user": "web",
"command_no": 1,
"command": "whoami",
"output": "d2hvbWFp", # base64.b64encode(s)
"timestamp": 1485238673.0
}
"""
queryset = command_store.all()
serializer_class = CommandLogSerializer
permission_classes = (IsSuperUserOrAppUser,)
class RecordLogViewSet(BulkModelViewSet):
"""接受app发送来的record log, 格式如下
{
"proxy_log_id": 23,
"output": "d2hvbWFp", # base64.b64encode(s)
"timestamp": 1485238673.0
}
"""
serializer_class = RecordSerializer
permission_classes = (IsSuperUserOrAppUser,)
def get_queryset(self):
filter_kwargs = {}
proxy_log_id = self.request.query_params.get('proxy_log_id')
data_from_ts = self.request.query_params.get('date_from_ts')
if proxy_log_id:
filter_kwargs['proxy_log_id'] = proxy_log_id
if data_from_ts:
filter_kwargs['date_from_ts'] = data_from_ts
if filter_kwargs:
return record_store.filter(**filter_kwargs)
else:
return record_store.all()

7
apps/audits/apps.py Normal file
View File

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

View File

@@ -0,0 +1,10 @@
from importlib import import_module
from django.conf import settings
command_engine = import_module(settings.COMMAND_STORE_BACKEND)
command_store = command_engine.CommandStore()
record_engine = import_module(settings.RECORD_STORE_BACKEND)
record_store = record_engine.RecordStore()
from .command.serializers import CommandLogSerializer

View File

@@ -0,0 +1,19 @@
# coding: utf-8
import abc
class CommandBase(object):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def save(self, proxy_log_id, user, asset, system_user,
command_no, command, output, timestamp):
pass
@abc.abstractmethod
def filter(self, date_from_ts=None, date_to_ts=None, user='',
asset='', system_user='', command='', proxy_log_id=0):
pass

View File

@@ -0,0 +1,45 @@
# ~*~ coding: utf-8 ~*~
from .base import CommandBase
from audits.models import CommandLog
class CommandStore(CommandBase):
model = CommandLog
queryset = []
def save(self, proxy_log_id, user, asset, system_user,
command_no, command, output, timestamp):
self.model.objects.create(
proxy_log_id=proxy_log_id, user=user, asset=asset,
system_user=system_user, command_no=command_no,
command=command, output=output, timestamp=timestamp
)
def filter(self, date_from_ts=None, date_to_ts=None, user='',
asset='', system_user='', command='', proxy_log_id=0):
filter_kwargs = {}
if date_from_ts:
filter_kwargs['timestamp__gte'] = date_from_ts
if date_to_ts:
filter_kwargs['timestamp__lte'] = date_to_ts
if user:
filter_kwargs['user'] = user
if asset:
filter_kwargs['asset'] = asset
if system_user:
filter_kwargs['system_user'] = system_user
if command:
filter_kwargs['command__icontains'] = command
if proxy_log_id:
filter_kwargs['proxy_log_id'] = proxy_log_id
if filter_kwargs:
self.queryset = self.model.objects.filter(**filter_kwargs)
return self.queryset
def all(self):
"""返回所有数据"""
return self.model.objects.iterator()

View File

@@ -0,0 +1,21 @@
# ~*~ coding: utf-8 ~*~
import base64
from rest_framework import serializers
from audits.models import CommandLog
from audits.backends import command_store
class CommandLogSerializer(serializers.ModelSerializer):
"""使用这个类作为基础Command Log Serializer类, 用来序列化"""
class Meta:
model = CommandLog
fields = '__all__'
def create(self, validated_data):
try:
output = validated_data['output']
validated_data['output'] = base64.b64decode(output)
except IndexError:
pass
return command_store.save(**dict(validated_data))

View File

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

View File

@@ -0,0 +1,14 @@
# coding: utf-8
import abc
class RecordBase(object):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def save(self, proxy_log_id, output, timestamp):
pass
@abc.abstractmethod
def filter(self, date_from_ts=None, proxy_log_id=None):
pass

View File

@@ -0,0 +1,31 @@
# ~*~ coding: utf-8 ~*~
from .base import RecordBase
from audits.models import RecordLog
class RecordStore(RecordBase):
model = RecordLog
queryset = []
def save(self, proxy_log_id, output, timestamp):
return self.model.objects.create(
proxy_log_id=proxy_log_id, output=output, timestamp=timestamp
)
def filter(self, date_from_ts=None, proxy_log_id=''):
filter_kwargs = {}
if date_from_ts:
filter_kwargs['timestamp__gte'] = date_from_ts
if proxy_log_id:
filter_kwargs['proxy_log_id'] = proxy_log_id
if filter_kwargs:
self.queryset = self.model.objects.filter(**filter_kwargs)
return self.queryset
def all(self):
"""返回所有数据"""
return self.model.objects.all()

View File

@@ -0,0 +1,20 @@
# ~*~ coding: utf-8 ~*~
import base64
from rest_framework import serializers
from audits.models import RecordLog
from audits.backends import record_store
class RecordSerializer(serializers.ModelSerializer):
"""使用这个类作为基础Command Log Serializer类, 用来序列化"""
class Meta:
model = RecordLog
fields = '__all__'
def create(self, validated_data):
try:
output = validated_data['output']
validated_data['output'] = base64.b64decode(output)
except IndexError:
pass
return record_store.save(**dict(validated_data))

8
apps/audits/hands.py Normal file
View File

@@ -0,0 +1,8 @@
# ~*~ coding: utf-8 ~*~
#
from users.utils import AdminUserRequiredMixin
from users.models import User
from assets.models import Asset, SystemUser
from users.permissions import IsSuperUserOrAppUser, IsAppUser
from applications.models import Terminal

94
apps/audits/models.py Normal file
View File

@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
#
from __future__ import unicode_literals
from django.db import models
from django.utils.translation import ugettext_lazy as _
class LoginLog(models.Model):
LOGIN_TYPE_CHOICE = (
('W', 'Web'),
('ST', 'SSH Terminal'),
('WT', 'Web Terminal')
)
username = models.CharField(max_length=20, verbose_name=_('Username'))
name = models.CharField(max_length=20, blank=True, verbose_name=_('Name'))
login_type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2,
verbose_name=_('Login type'))
login_ip = models.GenericIPAddressField(verbose_name=_('Login ip'))
login_city = models.CharField(max_length=254, blank=True, null=True,
verbose_name=_('Login city'))
user_agent = models.CharField(max_length=254, blank=True, null=True,
verbose_name=_('User agent'))
date_login = models.DateTimeField(auto_now_add=True,
verbose_name=_('Date login'))
class Meta:
db_table = 'login_log'
ordering = ['-date_login', 'username']
class ProxyLog(models.Model):
LOGIN_TYPE_CHOICE = (
('ST', 'SSH Terminal'),
('WT', 'Web Terminal'),
)
user = models.CharField(max_length=32, verbose_name=_('User'))
asset = models.CharField(max_length=32, verbose_name=_('Asset'))
system_user = models.CharField(max_length=32, verbose_name=_('System user'))
login_type = models.CharField(
choices=LOGIN_TYPE_CHOICE, max_length=2, blank=True,
null=True, verbose_name=_('Login type'))
terminal = models.CharField(
max_length=32, blank=True, null=True, verbose_name=_('Terminal'))
is_failed = models.BooleanField(
default=False, verbose_name=_('Did connect failed'))
is_finished = models.BooleanField(
default=False, verbose_name=_('Is finished'))
date_start = models.DateTimeField(
auto_created=True, verbose_name=_('Date start'))
date_finished = models.DateTimeField(
null=True, verbose_name=_('Date finished'))
def __unicode__(self):
return '%s-%s-%s' % (self.user, self.asset, self.system_user)
def commands(self):
from audits.backends import command_store
return command_store.filter(proxy_log_id=self.id)
class Meta:
ordering = ['-date_start', 'user']
class CommandLog(models.Model):
proxy_log_id = models.IntegerField(db_index=True)
user = models.CharField(max_length=48, db_index=True)
asset = models.CharField(max_length=128, db_index=True)
system_user = models.CharField(max_length=48, db_index=True)
command_no = models.IntegerField()
command = models.TextField(max_length=767, blank=True)
output = models.TextField(blank=True)
timestamp = models.FloatField(db_index=True)
def __unicode__(self):
return '%s: %s' % (self.id, self.command)
class Meta:
ordering = ['command_no', 'command']
class RecordLog(models.Model):
proxy_log_id = models.IntegerField(db_index=True)
output = models.TextField(verbose_name=_('Output'))
timestamp = models.FloatField(db_index=True)
def __unicode__(self):
return 'Record: %s' % self.proxy_log_id
class Meta:
ordering = ['timestamp']

View File

@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
#
from __future__ import absolute_import, unicode_literals
from rest_framework import serializers
from common.utils import timesince
from . import models
class ProxyLogSerializer(serializers.ModelSerializer):
time = serializers.SerializerMethodField()
command_length = serializers.SerializerMethodField()
class Meta:
model = models.ProxyLog
fields = '__all__'
@staticmethod
def get_time(obj):
if not obj.is_finished:
return ''
else:
return timesince(obj.date_start, since=obj.date_finished)
@staticmethod
def get_command_length(obj):
return 2

12
apps/audits/tasks.py Normal file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env python
# ~*~ coding: utf-8 ~*~
#
from celery import shared_task
from .utils import write_login_log
@shared_task
def write_login_log_async(*args, **kwargs):
write_login_log(*args, **kwargs)

View File

@@ -0,0 +1,109 @@
{% extends '_base_list.html' %}
{% load i18n %}
{% load static %}
{% load common_tags %}
{% block content_left_head %}
<link href="{% static "css/plugins/footable/footable.core.css" %}" rel="stylesheet">
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
<style>
#search_btn {
margin-bottom: 0;
}
</style>
{% endblock %}
{% block table_search %}
<form id="search_form" method="get" action="" class="pull-right form-inline">
<div class="form-group" id="date">
<div class="input-daterange input-group" id="datepicker">
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_from" value="{{ date_from }}">
<span class="input-group-addon">to</span>
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_to" value="{{ date_to }}">
</div>
</div>
<div class="input-group">
<select class="select2 form-control" name="username">
<option value="">{% trans 'User' %}</option>
{% for u in user_list %}
<option value="{{ u.username }}" {% if username == u.username %} selected {% endif %}>{{ u.username }}</option>
{% endfor %}
</select>
</div>
<div class="input-group">
<select class="select2 form-control" name="ip">
<option value="">{% trans 'Asset' %}</option>
{% for a in asset_list %}
<option value="{{ a.ip }}" {% if ip == a.ip %} selected {% endif %}>{{ a.ip }}</option>
{% endfor %}
</select>
</div>
<div class="input-group">
<select class="select2 form-control" name="system_user">
<option value="">{% trans 'System user' %}</option>
{% for s in system_user_list %}
<option value="{{ s.username }}" {% if s.username == system_user %} selected {% endif %}>{{ s.username }}</option>
{% endfor %}
</select>
</div>
<div class="input-group">
<input type="text" class="form-control input-sm" name="command" placeholder="Command" value="{{ command }}">
</div>
<div class="input-group">
<div class="input-group-btn">
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
搜索
</button>
</div>
</div>
</form>
{% endblock %}
{% block table_container %}
<table class="footable table table-stripped toggle-arrow-tiny" data-page="false">
<thead>
<tr>
<th data-toggle="true">ID</th>
<th>Command</th>
<th>Username</th>
<th>IP</th>
<th>System user</th>
<th>Proxy log</th>
<th>Datetime</th>
<th data-hide="all">Output</th>
</tr>
</thead>
<tbody>
{% for command in command_list %}
<tr>
<td>{{ command.id }}</td>
<td>{{ command.command }}</td>
<td>{{ command.user }}</td>
<td>{{ command.asset }}</td>
<td>{{ command.system_user }}</td>
<td><a href="{% url 'audits:proxy-log-detail' pk=command.proxy_log_id %}">{{ command.proxy_log_id}}</a></td>
<td>{{ command.timestamp|ts_to_date }}</td>
<td><pre style="border: none; background: none">{{ command.output|to_html|safe }}</pre></td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
{% block custom_foot_js %}
<script src="{% static "js/plugins/footable/footable.all.min.js" %}"></script>
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
<script>
$(document).ready(function () {
$('.footable').footable();
$('.select2').select2();
$('#date .input-daterange').datepicker({
dateFormat: 'mm/dd/yy',
keyboardNavigation: false,
forceParse: false,
autoclose: true
});
});
</script>
{% endblock %}

View File

@@ -0,0 +1,101 @@
{% extends '_base_list.html' %}
{% load i18n %}
{% load static %}
{% load common_tags %}
{% block content_left_head %}
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
<style>
#search_btn {
margin-bottom: 0;
}
</style>
{% endblock %}
{% block table_search %}
<form id="search_form" method="get" action="" class="pull-right form-inline">
<div class="form-group" id="date">
<div class="input-daterange input-group" id="datepicker">
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_from" value="{{ date_from }}">
<span class="input-group-addon">to</span>
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_to" value="{{ date_to }}">
</div>
</div>
<div class="input-group">
<select class="select2 form-control" name="username">
<option value="">{% trans 'Select user' %}</option>
{% for user in user_list %}
<option value="{{ user.username }}" {% if user.username == username %} selected {% endif %}>{{ user.username }}</option>
{% endfor %}
</select>
</div>
<div class="input-group">
<input type="text" class="form-control input-sm" name="keyword" placeholder="Search" value="{{ keyword }}">
</div>
<div class="input-group">
<div class="input-group-btn">
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
搜索
</button>
</div>
</div>
</form>
{% endblock %}
{% block table_head %}
<th class="text-center">{% trans 'ID' %}</th>
<th class="text-center">{% trans 'Username' %}</th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Type' %}</th>
<th class="text-center">{% trans 'UA' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'City' %}</th>
<th class="text-center">{% trans 'Date' %}</th>
{% endblock %}
{% block table_body %}
{% for login_log in login_log_list %}
<tr class="gradeX">
<td class="text-center">
{{ login_log.id }}
{# <a href="{% url 'audits:proxy-log-detail' pk=login_log.id %}">{{ login_log.id }}</a>#}
</td>
<td class="text-center">{{ login_log.username }}</td>
<td class="text-center">{{ login_log.name }}</td>
<td class="text-center">{{ login_log.get_login_type_display }}</td>
{% if login_log.login_type == 'W' %}
<td class="text-center">
<span href="javascript:void(0);" data-toggle="tooltips" title="{{ login_log.user_agent }}">{{ login_log.user_agent | truncatechars:20 }}</span>
</td>
{% else %}
<td class="text-center">{{ login_log.terminal }}</td>
{% endif %}
<td class="text-center">{{ login_log.login_ip }}</td>
<td class="text-center">{{ login_log.login_city }}</td>
<td class="text-center">{{ login_log.date_login }}</td>
</tr>
{% endfor %}
{% endblock %}
{% block custom_foot_js %}
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
<script>
$(document).ready(function() {
$('table').DataTable({
"searching": false,
"bInfo" : false,
"paging": false,
"order": []
});
$('#date .input-daterange').datepicker({
dateFormat: 'mm/dd/yy',
keyboardNavigation: false,
forceParse: false,
autoclose: true
});
$('.select2').select2();
})
</script>
{% endblock %}

View File

@@ -0,0 +1,58 @@
{% load static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="renderer" content="webkit">
{% include '_head_css_js.html' %}
<link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet">
<link href="{% static "css/plugins/footable/footable.core.css" %}" rel="stylesheet">
<script src="{% static 'js/jquery-2.1.1.js' %}"></script>
<script src="{% static 'js/plugins/sweetalert/sweetalert.min.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
</head>
<body>
<div class="wrapper wrapper-content animated fadeInRight">
<div class="tab-content">
<div class="ibox-content">
<input type="text" class="form-control input-sm m-b-xs" id="filter"
placeholder="Search in table">
<table class="footable table table-stripped toggle-arrow-tiny" data-page-size="10" data-filter=#filter>
<thead>
<tr>
<th data-toggle="true">ID</th>
<th>Command</th>
<th data-hide="all">Output</th>
<th>Datetime</th>
</tr>
</thead>
<tbody class="table_body">
{% for command in object_list %}
<tr>
<td>{{ command.command_no }}</td>
<td>{{ command.command }}</td>
<td>{{ command.output_decode |safe }}</td>
<td>{{ command.datetime }}</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td colspan="5">
<ul class="pagination pull-right"></ul>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</body>
<script src="{% static "js/plugins/footable/footable.all.min.js" %}"></script>
<script>
$(document).ready(function () {
$('.footable').footable();
});
</script>
</html>

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