diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index ca3beb996..aa418fee8 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -181,6 +181,12 @@ LOGGING = { 'formatter': 'main', 'filename': os.path.join(PROJECT_DIR, 'logs', 'jumpserver.log') }, + 'ansible_logs': { + 'level': 'DEBUG', + 'class': 'logging.FileHandler', + 'formatter': 'main', + 'filename': os.path.join(PROJECT_DIR, 'logs', 'ansible.log') + }, }, 'loggers': { 'django': { @@ -209,6 +215,10 @@ LOGGING = { 'jumpserver.users.view': { 'handlers': ['console', 'file'], 'level': LOG_LEVEL, + }, + 'ops.ansible_api': { + 'handlers': ['console', 'ansible_logs'], + 'level': LOG_LEVEL, } } } diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index 50a4bb77d..f7db52a50 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -1,3 +1,6 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals + """jumpserver URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: @@ -27,11 +30,15 @@ urlpatterns = [ url(r'^perms/', include('perms.urls.views_urls', namespace='perms')), url(r'^audits/', include('audits.urls.views_urls', namespace='audits')), url(r'^terminal/', include('terminal.urls.views_urls', namespace='terminal')), + url('^ops/', include('ops.urls.view_urls', namespace='ops')), + url(r'^api/users/', include('users.urls.api_urls', namespace='api-users')), url(r'^api/assets/', include('assets.urls.api_urls', namespace='api-assets')), url(r'^api/perms/', include('perms.urls.api_urls', namespace='api-perms')), url(r'^api/audits/', include('audits.urls.api_urls', namespace='api-audits')), url(r'^api/terminal/', include('terminal.urls.api_urls', namespace='api-terminal')), + url(r'^api/ops/', include('ops.urls.api_urls', namespace='api-ops')), + ] diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 58a2c50f2..a04d6205c 100644 Binary files a/apps/locale/zh/LC_MESSAGES/django.mo and b/apps/locale/zh/LC_MESSAGES/django.mo differ diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 0191d8a9c..cda86f2c0 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,220 +8,258 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-09-18 22:57+0800\n" +"POT-Creation-Date: 2016-11-22 21:30+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" +"Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: zh_CN\n" -#: assets/forms.py:26 -msgid "Tags" -msgstr "" - -#: assets/forms.py:57 assets/forms.py:201 +#: assets/forms.py:53 assets/forms.py:208 #: assets/templates/assets/admin_user_detail.html:191 perms/forms.py:27 -#: perms/templates/perms/asset_permission_asset.html:139 users/forms.py:115 +#: perms/templates/perms/asset_permission_asset.html:139 users/forms.py:124 msgid "Select asset groups" msgstr "添加到资产组" -#: assets/forms.py:59 +#: assets/forms.py:56 +#, fuzzy +#| msgid "Select assets" +msgid "Select asset tags" +msgstr "选择资产" + +#: assets/forms.py:58 #, fuzzy #| msgid "System user" msgid "Select asset system users" msgstr "系统" -#: assets/forms.py:60 +#: assets/forms.py:59 #, fuzzy #| msgid "Select assets" msgid "Select asset admin user" msgstr "选择资产" -#: assets/forms.py:71 assets/forms.py:102 assets/forms.py:133 -#: assets/forms.py:191 assets/models.py:347 +#: assets/forms.py:71 assets/forms.py:107 assets/forms.py:140 +#: assets/forms.py:198 assets/forms.py:270 #: perms/templates/perms/asset_permission_create_update.html:40 #: templates/_nav.html:21 msgid "Asset" msgstr "资产" -#: assets/forms.py:74 assets/forms.py:105 assets/forms.py:136 -#: assets/forms.py:194 perms/forms.py:25 users/forms.py:113 +#: assets/forms.py:74 assets/forms.py:110 assets/forms.py:143 +#: assets/forms.py:201 assets/forms.py:273 perms/forms.py:25 users/forms.py:122 msgid "Select assets" msgstr "选择资产" -#: assets/forms.py:124 assets/forms.py:179 assets/forms.py:251 -#: assets/models.py:14 assets/models.py:89 assets/models.py:154 -#: assets/models.py:238 assets/templates/assets/admin_user_detail.html:46 +#: assets/forms.py:96 +#, fuzzy +#| msgid "System user" +msgid "Select asset system user" +msgstr "系统" + +#: assets/forms.py:129 assets/forms.py:186 assets/forms.py:258 +#: assets/models.py:15 assets/models.py:94 assets/models.py:159 +#: assets/models.py:243 assets/templates/assets/admin_user_detail.html:46 #: assets/templates/assets/admin_user_list.html:10 #: assets/templates/assets/asset_group_detail.html:46 #: assets/templates/assets/asset_group_list.html:12 -#: assets/templates/assets/idc_list.html:10 +#: assets/templates/assets/idc_list.html:12 #: assets/templates/assets/system_user_asset_group.html:53 #: assets/templates/assets/system_user_detail.html:51 -#: assets/templates/assets/system_user_list.html:10 perms/models.py:19 +#: assets/templates/assets/system_user_list.html:10 ops/models.py:18 +#: ops/models.py:36 ops/models.py:48 ops/models.py:65 ops/models.py:397 +#: ops/templates/cron/list.html:26 ops/templates/sudo/list.html:26 +#: perms/models.py:19 #: perms/templates/perms/asset_permission_create_update.html:33 #: perms/templates/perms/asset_permission_detail.html:56 #: perms/templates/perms/asset_permission_list.html:12 -#: perms/templates/perms/asset_permission_user.html:66 users/models.py:20 -#: users/models.py:67 users/templates/users/user_asset_permission.html:66 +#: perms/templates/perms/asset_permission_user.html:66 users/models.py:23 +#: users/models.py:75 users/templates/users/_select_user_modal.html:13 +#: users/templates/users/user_asset_permission.html:66 #: users/templates/users/user_detail.html:58 #: users/templates/users/user_granted_asset.html:129 -#: users/templates/users/user_list.html:12 +#: users/templates/users/user_group_detail.html:90 +#: users/templates/users/user_group_list.html:25 +#: users/templates/users/user_list.html:26 msgid "Name" msgstr "名称" -#: assets/forms.py:141 assets/forms.py:206 +#: assets/forms.py:148 assets/forms.py:213 msgid "If also set private key, use that first" msgstr "如果设置私钥,则优先使用私钥" -#: assets/forms.py:180 assets/forms.py:252 assets/models.py:90 -#: assets/models.py:155 assets/templates/assets/admin_user_detail.html:50 +#: assets/forms.py:187 assets/forms.py:259 assets/models.py:95 +#: assets/models.py:160 assets/templates/assets/admin_user_detail.html:50 #: assets/templates/assets/admin_user_list.html:11 #: assets/templates/assets/system_user_detail.html:55 #: assets/templates/assets/system_user_list.html:11 +#: ops/templates/cron/list.html:27 ops/templates/sudo/list.html:27 #: perms/templates/perms/asset_permission_user.html:67 users/forms.py:13 -#: users/models.py:66 users/templates/users/login.html:53 +#: users/models.py:74 users/templates/users/_select_user_modal.html:14 +#: users/templates/users/login.html:53 #: users/templates/users/user_detail.html:62 -#: users/templates/users/user_list.html:13 +#: users/templates/users/user_list.html:27 #: users/templates/users/user_update.html:6 msgid "Username" msgstr "用户名" -#: assets/forms.py:197 assets/templates/assets/asset_detail.html:203 -#: templates/_nav.html:22 +#: assets/forms.py:204 templates/_nav.html:22 msgid "Asset group" msgstr "资产组" -#: assets/models.py:15 assets/templates/assets/idc_list.html:12 +#: assets/models.py:16 msgid "Bandwidth" msgstr "带宽" -#: assets/models.py:16 assets/templates/assets/idc_list.html:13 +#: assets/models.py:17 assets/templates/assets/idc_list.html:14 msgid "Contact" msgstr "联系人" -#: assets/models.py:17 assets/templates/assets/idc_list.html:14 -#: users/models.py:73 users/templates/users/user_detail.html:71 +#: assets/models.py:18 assets/templates/assets/idc_list.html:15 +#: users/models.py:81 users/templates/users/user_detail.html:71 msgid "Phone" msgstr "手机" -#: assets/models.py:18 assets/templates/assets/idc_list.html:15 +#: assets/models.py:19 msgid "Address" msgstr "地址" -#: assets/models.py:19 -msgid "Network" -msgstr "网络" +#: assets/models.py:20 +msgid "Intranet" +msgstr "" -#: assets/models.py:20 assets/models.py:241 assets/models.py:307 +#: assets/models.py:21 +msgid "Extranet" +msgstr "" + +#: assets/models.py:22 assets/models.py:246 assets/models.py:324 msgid "Date added" msgstr "加入日期" -#: assets/models.py:21 +#: assets/models.py:23 msgid "Operator" msgstr "运营商" -#: assets/models.py:22 assets/models.py:59 assets/models.py:97 -#: assets/models.py:168 assets/models.py:240 assets/models.py:305 -#: assets/templates/assets/admin_user_detail.html:58 -#: assets/templates/assets/asset_detail.html:127 +#: assets/models.py:24 assets/models.py:64 assets/models.py:102 +#: assets/models.py:173 assets/models.py:245 assets/models.py:322 +#: assets/models.py:370 assets/templates/assets/admin_user_detail.html:58 +#: assets/templates/assets/asset_detail.html:114 #: assets/templates/assets/asset_group_detail.html:54 +#: assets/templates/assets/asset_tag_detail.html:49 #: assets/templates/assets/system_user_detail.html:101 perms/models.py:29 -#: perms/templates/perms/asset_permission_detail.html:88 users/models.py:82 +#: perms/templates/perms/asset_permission_detail.html:88 users/models.py:90 #: users/templates/users/user_detail.html:90 msgid "Created by" msgstr "创建者" -#: assets/models.py:23 assets/models.py:61 assets/models.py:95 -#: assets/models.py:169 assets/models.py:242 assets/models.py:308 +#: assets/models.py:25 assets/models.py:66 assets/models.py:100 +#: assets/models.py:174 assets/models.py:247 assets/models.py:325 #: assets/templates/assets/admin_user_detail.html:62 #: assets/templates/assets/admin_user_list.html:14 -#: assets/templates/assets/asset_detail.html:135 +#: assets/templates/assets/asset_detail.html:122 #: assets/templates/assets/asset_group_detail.html:58 #: assets/templates/assets/asset_group_list.html:14 #: assets/templates/assets/system_user_asset_group.html:56 #: assets/templates/assets/system_user_detail.html:105 #: assets/templates/assets/system_user_list.html:15 perms/models.py:31 -#: perms/templates/perms/asset_permission_detail.html:92 users/models.py:21 -#: users/models.py:78 users/templates/users/user_detail.html:102 +#: perms/templates/perms/asset_permission_detail.html:92 users/models.py:24 +#: users/models.py:86 users/templates/users/user_detail.html:102 +#: users/templates/users/user_group_detail.html:94 +#: users/templates/users/user_group_list.html:28 msgid "Comment" msgstr "备注" -#: assets/models.py:57 +#: assets/models.py:32 assets/models.py:257 +#, fuzzy +#| msgid "As default" +msgid "Default" +msgstr "默认使用" + +#: assets/models.py:32 users/models.py:224 +msgid "System" +msgstr "系统" + +#: assets/models.py:32 +#, fuzzy +#| msgid "As default" +msgid "Default IDC" +msgstr "默认使用" + +#: assets/models.py:62 msgid "KEY" msgstr "KEY" -#: assets/models.py:58 assets/models.py:346 +#: assets/models.py:63 msgid "VALUE" msgstr "VALUE" -#: assets/models.py:69 assets/models.py:70 +#: assets/models.py:74 assets/models.py:75 msgid "status" msgstr "状态" -#: assets/models.py:69 +#: assets/models.py:74 #, fuzzy #| msgid "Admin user" msgid "In use" msgstr "管理用户" -#: assets/models.py:70 +#: assets/models.py:75 #, fuzzy #| msgid "Auto push" msgid "Out of use" msgstr "自动推送" -#: assets/models.py:71 assets/models.py:72 assets/models.py:73 -#: assets/models.py:74 assets/models.py:75 assets/models.py:76 +#: assets/models.py:76 assets/models.py:77 assets/models.py:78 +#: assets/models.py:79 assets/models.py:80 assets/models.py:81 msgid "type" msgstr "" -#: assets/models.py:71 +#: assets/models.py:76 msgid "Server" msgstr "" -#: assets/models.py:72 +#: assets/models.py:77 msgid "VM" msgstr "" -#: assets/models.py:73 +#: assets/models.py:78 msgid "Switch" msgstr "" -#: assets/models.py:74 +#: assets/models.py:79 #, fuzzy #| msgid "Role" msgid "Router" msgstr "角色" -#: assets/models.py:75 +#: assets/models.py:80 msgid "Firewall" msgstr "" -#: assets/models.py:76 +#: assets/models.py:81 msgid "Storage" msgstr "" -#: assets/models.py:77 assets/models.py:78 assets/models.py:79 +#: assets/models.py:82 assets/models.py:83 assets/models.py:84 msgid "env" msgstr "" -#: assets/models.py:77 +#: assets/models.py:82 msgid "Production" msgstr "" -#: assets/models.py:78 +#: assets/models.py:83 msgid "Development" msgstr "" -#: assets/models.py:79 +#: assets/models.py:84 #, fuzzy msgid "Testing" msgstr "设置" -#: assets/models.py:91 assets/models.py:156 users/forms.py:15 +#: assets/models.py:96 assets/models.py:161 users/forms.py:15 #: users/templates/users/login.html:56 #: users/templates/users/reset_password.html:52 #: users/templates/users/user_create.html:9 @@ -231,172 +269,169 @@ msgstr "设置" msgid "Password" msgstr "密码" -#: assets/models.py:92 assets/models.py:158 +#: assets/models.py:97 assets/models.py:163 msgid "SSH private key" msgstr "ssh密钥" -#: assets/models.py:93 assets/models.py:159 +#: assets/models.py:98 assets/models.py:164 msgid "SSH public key" msgstr "ssh公钥" -#: assets/models.py:94 assets/models.py:160 +#: assets/models.py:99 assets/models.py:165 #: assets/templates/assets/admin_user_create_update.html:43 #: assets/templates/assets/system_user_create_update.html:44 #: assets/templates/assets/system_user_detail.html:71 msgid "As default" msgstr "默认使用" -#: assets/models.py:157 assets/templates/assets/system_user_detail.html:59 +#: assets/models.py:162 assets/templates/assets/system_user_detail.html:59 msgid "Protocol" msgstr "协议" -#: assets/models.py:161 +#: assets/models.py:166 #: assets/templates/assets/system_user_create_update.html:50 #: assets/templates/assets/system_user_detail.html:63 msgid "Auto push" msgstr "自动推送" -#: assets/models.py:162 +#: assets/models.py:167 msgid "Auto update pass/key" msgstr "自动更新密码/密钥" -#: assets/models.py:163 assets/templates/assets/system_user_detail.html:75 +#: assets/models.py:168 assets/templates/assets/system_user_detail.html:75 +#: templates/_nav.html:46 msgid "Sudo" msgstr "Sudo" -#: assets/models.py:164 assets/templates/assets/system_user_detail.html:80 +#: assets/models.py:169 assets/templates/assets/system_user_detail.html:80 msgid "Shell" msgstr "Shell" -#: assets/models.py:165 assets/templates/assets/system_user_detail.html:86 +#: assets/models.py:170 assets/templates/assets/system_user_detail.html:86 #: templates/_header_bar.html:41 templates/_nav.html:4 msgid "Home" msgstr "仪表盘" -#: assets/models.py:166 assets/templates/assets/system_user_detail.html:92 +#: assets/models.py:171 assets/templates/assets/system_user_detail.html:92 msgid "Uid" msgstr "Uid" -#: assets/models.py:252 -#, fuzzy -#| msgid "As default" -msgid "Default" -msgstr "默认使用" - -#: assets/models.py:252 +#: assets/models.py:257 #, fuzzy #| msgid "Create asset group" msgid "Default asset group" msgstr "创建资产组" -#: assets/models.py:279 assets/templates/assets/admin_user_detail.html:92 -#: assets/templates/assets/asset_detail.html:57 +#: assets/models.py:291 assets/templates/assets/admin_user_detail.html:92 +#: assets/templates/assets/asset_detail.html:54 #: assets/templates/assets/asset_group_detail.html:88 -#: assets/templates/assets/asset_list.html:13 +#: assets/templates/assets/asset_list.html:58 +#: assets/templates/assets/asset_tag_detail.html:84 #: assets/templates/assets/system_user_asset.html:50 #: perms/templates/perms/asset_permission_asset.html:67 #: users/templates/users/user_granted_asset.html:67 msgid "IP" msgstr "IP" -#: assets/models.py:280 assets/templates/assets/asset_detail.html:61 +#: assets/models.py:292 assets/templates/assets/asset_detail.html:58 msgid "Other IP" msgstr "其它IP" -#: assets/models.py:281 assets/templates/assets/asset_detail.html:65 +#: assets/models.py:293 assets/templates/assets/asset_detail.html:62 msgid "Remote card IP" msgstr "远控卡IP" -#: assets/models.py:282 assets/templates/assets/admin_user_detail.html:91 -#: assets/templates/assets/asset_detail.html:53 +#: assets/models.py:294 assets/templates/assets/admin_user_detail.html:91 +#: assets/templates/assets/asset_detail.html:50 #: assets/templates/assets/asset_group_detail.html:87 -#: assets/templates/assets/asset_list.html:12 +#: assets/templates/assets/asset_list.html:57 +#: assets/templates/assets/asset_tag_detail.html:83 #: assets/templates/assets/system_user_asset.html:49 #: perms/templates/perms/asset_permission_asset.html:66 #: users/templates/users/user_granted_asset.html:66 msgid "Hostname" msgstr "主机名" -#: assets/models.py:283 assets/templates/assets/admin_user_detail.html:93 -#: assets/templates/assets/asset_detail.html:69 +#: assets/models.py:295 assets/templates/assets/admin_user_detail.html:93 +#: assets/templates/assets/asset_detail.html:66 #: assets/templates/assets/asset_group_detail.html:89 -#: assets/templates/assets/asset_list.html:14 +#: assets/templates/assets/asset_list.html:59 +#: assets/templates/assets/asset_tag_detail.html:85 #: assets/templates/assets/system_user_asset.html:51 #: perms/templates/perms/asset_permission_asset.html:68 #: users/templates/users/user_granted_asset.html:68 msgid "Port" msgstr "端口" -#: assets/models.py:284 +#: assets/models.py:296 assets/templates/assets/asset_detail.html:184 msgid "Asset groups" msgstr "用户组" -#: assets/models.py:286 templates/_nav.html:24 +#: assets/models.py:298 templates/_nav.html:24 msgid "Admin user" msgstr "管理用户" -#: assets/models.py:287 +#: assets/models.py:299 msgid "System User" msgstr "系统用户" -#: assets/models.py:288 templates/_nav.html:23 +#: assets/models.py:301 templates/_nav.html:23 msgid "IDC" msgstr "机房" -#: assets/models.py:289 assets/templates/assets/asset_detail.html:73 -#: assets/templates/assets/asset_detail.html:99 +#: assets/models.py:303 assets/templates/assets/asset_detail.html:70 msgid "Mac address" msgstr "Mac地址" -#: assets/models.py:290 +#: assets/models.py:304 msgid "Brand" msgstr "品牌" -#: assets/models.py:291 assets/templates/assets/asset_detail.html:77 +#: assets/models.py:305 assets/templates/assets/asset_detail.html:74 msgid "CPU" msgstr "CPU" -#: assets/models.py:292 assets/templates/assets/asset_detail.html:81 +#: assets/models.py:306 assets/templates/assets/asset_detail.html:78 msgid "Memory" msgstr "内存" -#: assets/models.py:293 assets/templates/assets/asset_detail.html:85 +#: assets/models.py:307 assets/templates/assets/asset_detail.html:82 msgid "Disk" msgstr "硬盘" -#: assets/models.py:294 assets/templates/assets/asset_detail.html:95 +#: assets/models.py:308 assets/templates/assets/asset_detail.html:86 msgid "OS" msgstr "操作系统" -#: assets/models.py:295 +#: assets/models.py:309 msgid "Cabinet number" msgstr "机柜编号" -#: assets/models.py:296 +#: assets/models.py:310 msgid "Cabinet position" msgstr "机柜层号" -#: assets/models.py:297 assets/templates/assets/asset_detail.html:123 +#: assets/models.py:311 assets/templates/assets/asset_detail.html:110 msgid "Asset number" msgstr "资产编号" -#: assets/models.py:299 assets/templates/assets/asset_detail.html:103 +#: assets/models.py:313 assets/templates/assets/asset_detail.html:90 msgid "Asset status" msgstr "资产状态" -#: assets/models.py:301 assets/templates/assets/asset_detail.html:111 +#: assets/models.py:316 assets/templates/assets/asset_detail.html:98 msgid "Asset type" msgstr "系统类型" -#: assets/models.py:303 assets/templates/assets/asset_detail.html:115 +#: assets/models.py:319 assets/templates/assets/asset_detail.html:102 msgid "Asset environment" msgstr "资产环境" -#: assets/models.py:304 assets/templates/assets/asset_detail.html:119 +#: assets/models.py:321 assets/templates/assets/asset_detail.html:106 msgid "Serial number" msgstr "序列号" -#: assets/models.py:306 assets/templates/assets/asset_detail.html:107 +#: assets/models.py:323 assets/templates/assets/asset_detail.html:94 msgid "Is active" msgstr "是否激活" @@ -416,11 +451,8 @@ msgstr "自动更新密码/密钥" #: assets/templates/assets/admin_user_create_update.html:53 #: assets/templates/assets/admin_user_detail.html:144 -#: assets/templates/assets/asset_create_update.html:45 -#: assets/templates/assets/asset_detail.html:184 -#: assets/templates/assets/asset_detail.html:192 -#: assets/templates/assets/asset_group_create.html:38 -#: assets/templates/assets/idc_create_update.html:44 +#: assets/templates/assets/asset_create.html:33 +#: assets/templates/assets/asset_update.html:61 #: assets/templates/assets/system_user_create_update.html:71 #: assets/templates/assets/system_user_detail.html:144 #: perms/templates/perms/asset_permission_create_update.html:67 @@ -431,23 +463,26 @@ msgid "Reset" msgstr "重置" #: assets/templates/assets/admin_user_create_update.html:54 -#: assets/templates/assets/asset_create_update.html:46 -#: assets/templates/assets/asset_group_create.html:39 +#: assets/templates/assets/asset_create.html:34 #: assets/templates/assets/asset_group_list.html:51 -#: assets/templates/assets/asset_list.html:64 -#: assets/templates/assets/idc_create_update.html:45 +#: assets/templates/assets/asset_list.html:105 +#: assets/templates/assets/asset_tags_list.html:50 +#: assets/templates/assets/asset_update.html:62 #: assets/templates/assets/system_user_create_update.html:72 +#: ops/templates/cron/list.html:47 ops/templates/sudo/list.html:47 #: perms/templates/perms/asset_permission_create_update.html:68 #: perms/templates/perms/asset_permission_list.html:65 #: users/templates/users/_user.html:71 #: users/templates/users/forgot_password.html:44 #: users/templates/users/user_asset_permission.html:144 -#: users/templates/users/user_list.html:64 +#: users/templates/users/user_group_list.html:40 +#: users/templates/users/user_list.html:47 msgid "Submit" msgstr "提交" #: assets/templates/assets/admin_user_detail.html:19 #: assets/templates/assets/asset_group_detail.html:18 +#: assets/templates/assets/asset_tag_detail.html:18 #: assets/templates/assets/system_user_asset.html:19 #: assets/templates/assets/system_user_asset_group.html:19 #: assets/templates/assets/system_user_detail.html:19 @@ -459,6 +494,7 @@ msgstr "" #: assets/templates/assets/admin_user_detail.html:54 #: assets/templates/assets/asset_group_detail.html:50 +#: assets/templates/assets/asset_tag_detail.html:53 #: assets/templates/assets/system_user_detail.html:97 perms/models.py:30 #: perms/templates/perms/asset_permission_detail.html:84 #, fuzzy @@ -468,6 +504,7 @@ msgstr "加入日期" #: assets/templates/assets/admin_user_detail.html:72 #: assets/templates/assets/asset_group_detail.html:68 +#: assets/templates/assets/asset_tag_detail.html:64 #: assets/templates/assets/system_user_asset_group.html:34 #: perms/templates/perms/asset_permission_asset.html:47 #, fuzzy @@ -477,6 +514,7 @@ msgstr "资产组列表" #: assets/templates/assets/admin_user_detail.html:94 #: assets/templates/assets/asset_group_detail.html:90 +#: assets/templates/assets/asset_tag_detail.html:86 #, fuzzy msgid "Alive" msgstr "激活" @@ -536,7 +574,6 @@ msgid "Replace asset admin user with this admin user" msgstr "" #: assets/templates/assets/admin_user_list.html:9 -#: assets/templates/assets/idc_list.html:9 #: assets/templates/assets/system_user_list.html:9 #, fuzzy #| msgid "IDC" @@ -545,10 +582,13 @@ msgstr "机房" #: assets/templates/assets/admin_user_list.html:12 #: assets/templates/assets/asset_group_list.html:13 -#: assets/templates/assets/idc_list.html:11 +#: assets/templates/assets/asset_tags_list.html:13 +#: assets/templates/assets/idc_list.html:13 #: assets/templates/assets/system_user_asset_group.html:54 #: assets/templates/assets/system_user_list.html:12 -#: users/templates/users/user_list.html:16 +#: ops/templates/cron/list.html:30 ops/templates/sudo/list.html:30 +#: users/templates/users/_select_user_modal.html:17 +#: users/templates/users/user_list.html:30 msgid "Asset num" msgstr "资产数量" @@ -562,128 +602,135 @@ msgid "Script" msgstr "" #: assets/templates/assets/admin_user_list.html:35 +#: assets/templates/assets/asset_detail.html:156 #: assets/templates/assets/system_user_list.html:37 msgid "Refresh" msgstr "" #: assets/templates/assets/admin_user_list.html:36 #: assets/templates/assets/asset_group_list.html:32 -#: assets/templates/assets/asset_list.html:44 -#: assets/templates/assets/idc_list.html:30 +#: assets/templates/assets/asset_list.html:83 +#: assets/templates/assets/asset_tags_list.html:31 +#: assets/templates/assets/idc_list.html:32 #: assets/templates/assets/system_user_list.html:38 +#: ops/templates/cron/list.html:79 ops/templates/sudo/list.html:79 #: perms/templates/perms/asset_permission_list.html:46 -#: users/templates/users/user_list.html:44 +#: users/templates/users/user_detail.html:167 +#: users/templates/users/user_group_list.html:64 +#: users/templates/users/user_list.html:79 msgid "Update" msgstr "更新" #: assets/templates/assets/admin_user_list.html:37 #: assets/templates/assets/asset_group_list.html:33 -#: assets/templates/assets/asset_list.html:45 -#: assets/templates/assets/idc_list.html:31 +#: assets/templates/assets/asset_list.html:84 +#: assets/templates/assets/asset_tags_list.html:32 +#: assets/templates/assets/idc_list.html:33 #: assets/templates/assets/system_user_list.html:39 +#: ops/templates/cron/list.html:80 ops/templates/sudo/list.html:80 #: perms/templates/perms/asset_permission_list.html:47 -#: users/templates/users/user_list.html:45 -#: users/templates/users/user_list.html:46 +#: users/templates/users/user_group_detail.html:129 +#: users/templates/users/user_group_detail.html:132 +#: users/templates/users/user_group_list.html:65 +#: users/templates/users/user_list.html:80 msgid "Delete" msgstr "删除" -#: assets/templates/assets/asset_create_update.html:14 +#: assets/templates/assets/asset_create.html:9 +#: assets/templates/assets/asset_update.html:14 msgid "Basic" msgstr "" -#: assets/templates/assets/asset_create_update.html:21 +#: assets/templates/assets/asset_create.html:16 +#: assets/templates/assets/asset_update.html:21 msgid "Group" msgstr "" -#: assets/templates/assets/asset_create_update.html:26 +#: assets/templates/assets/asset_create.html:21 +#: assets/templates/assets/asset_update.html:26 #, fuzzy #| msgid "Asset number" msgid "Asset user" msgstr "资产编号" -#: assets/templates/assets/asset_create_update.html:31 +#: assets/templates/assets/asset_create.html:26 +#: assets/templates/assets/asset_update.html:52 #: perms/templates/perms/asset_permission_create_update.html:45 #, fuzzy #| msgid "Other IP" msgid "Other" msgstr "其它IP" -#: assets/templates/assets/asset_detail.html:19 +#: assets/templates/assets/asset_detail.html:20 #, fuzzy #| msgid "Asset group list" msgid "Asset detail" msgstr "资产组列表" -#: assets/templates/assets/asset_detail.html:21 -#, fuzzy -#| msgid "Asset number" -msgid "Asset users" -msgstr "资产编号" - -#: assets/templates/assets/asset_detail.html:22 +#: assets/templates/assets/asset_detail.html:23 #, fuzzy #| msgid "Asset group list" msgid "Asset login log" msgstr "资产组列表" -#: assets/templates/assets/asset_detail.html:89 templates/_nav.html:26 -msgid "Label" -msgstr "标签" - -#: assets/templates/assets/asset_detail.html:131 +#: assets/templates/assets/asset_detail.html:118 #: users/templates/users/user_detail.html:94 msgid "Date joined" msgstr "创建日期" -#: assets/templates/assets/asset_detail.html:146 +#: assets/templates/assets/asset_detail.html:133 #: users/templates/users/user_detail.html:113 +#: users/templates/users/user_group_detail.html:115 msgid "Quick modify" msgstr "快速修改" -#: assets/templates/assets/asset_detail.html:152 perms/models.py:27 +#: assets/templates/assets/asset_detail.html:139 +#: ops/templates/cron/list.html:31 ops/templates/sudo/list.html:31 +#: perms/models.py:27 #: perms/templates/perms/asset_permission_create_update.html:47 +#: users/templates/users/_select_user_modal.html:18 #: users/templates/users/user_detail.html:119 -#: users/templates/users/user_list.html:17 +#: users/templates/users/user_list.html:31 #, fuzzy msgid "Active" msgstr "激活" -#: assets/templates/assets/asset_detail.html:166 users/models.py:74 -#: users/templates/users/_user.html:57 -#: users/templates/users/user_detail.html:133 -msgid "Enable OTP" -msgstr "二次验证" +#: assets/templates/assets/asset_detail.html:153 +msgid "Rrefresh hardware" +msgstr "" -#: assets/templates/assets/asset_detail.html:181 -#: users/templates/users/reset_password.html:45 -#: users/templates/users/user_detail.html:148 users/utils.py:99 -msgid "Reset password" -msgstr "重置密码" +#: assets/templates/assets/asset_detail.html:161 +#, fuzzy +#| msgid "Create user" +msgid "Test admin user" +msgstr "创建用户" -#: assets/templates/assets/asset_detail.html:189 -#: users/templates/users/user_detail.html:156 -msgid "Reset ssh key" -msgstr "重置密钥" +#: assets/templates/assets/asset_detail.html:164 +#: assets/templates/assets/asset_detail.html:172 +#, fuzzy +msgid "Test" +msgstr "设置" -#: assets/templates/assets/asset_detail.html:211 users/forms.py:33 -#: users/forms.py:51 users/templates/users/user_detail.html:178 -msgid "Join user groups" +#: assets/templates/assets/asset_detail.html:169 +#, fuzzy +#| msgid "System user" +msgid "Test system users" +msgstr "系统" + +#: assets/templates/assets/asset_detail.html:192 +#, fuzzy +#| msgid "Join user groups" +msgid "Join asset groups" msgstr "添加到用户组" -#: assets/templates/assets/asset_detail.html:220 +#: assets/templates/assets/asset_detail.html:201 #: perms/templates/perms/asset_permission_asset.html:148 #: perms/templates/perms/asset_permission_detail.html:164 #: perms/templates/perms/asset_permission_user.html:148 -#: users/templates/users/user_detail.html:187 +#: users/templates/users/user_detail.html:195 msgid "Join" msgstr "加入" -#: assets/templates/assets/asset_group_create.html:16 -#: assets/templates/assets/asset_group_list.html:5 assets/views.py:80 -#: assets/views.py:150 -msgid "Create asset group" -msgstr "创建资产组" - #: assets/templates/assets/asset_group_detail.html:20 #, fuzzy #| msgid "Asset group" @@ -691,110 +738,155 @@ msgid "Asset group perm" msgstr "资产组" #: assets/templates/assets/asset_group_detail.html:113 +#: assets/templates/assets/asset_tag_detail.html:109 #, fuzzy #| msgid "System user" msgid "Associate system user" msgstr "系统" #: assets/templates/assets/asset_group_detail.html:119 +#: assets/templates/assets/asset_tag_detail.html:115 #, fuzzy #| msgid "System user" msgid "repush system user" msgstr "系统" #: assets/templates/assets/asset_group_detail.html:129 +#: assets/templates/assets/asset_tag_detail.html:125 #, fuzzy #| msgid "System user" msgid "Select system user" msgstr "系统" #: assets/templates/assets/asset_group_detail.html:138 +#: assets/templates/assets/asset_tag_detail.html:134 msgid "Associate" msgstr "" #: assets/templates/assets/asset_group_detail.html:157 +#: assets/templates/assets/asset_tag_detail.html:153 #, fuzzy #| msgid "Asset group" msgid "Add asset to this group" msgstr "资产组" #: assets/templates/assets/asset_group_detail.html:165 +#: assets/templates/assets/asset_tag_detail.html:161 #, fuzzy #| msgid "Select assets" msgid "Select asset user" msgstr "选择资产" #: assets/templates/assets/asset_group_detail.html:174 +#: assets/templates/assets/asset_tag_detail.html:170 #: assets/templates/assets/system_user_asset_group.html:96 #: perms/templates/perms/asset_permission_asset.html:120 #: perms/templates/perms/asset_permission_user.html:120 +#: users/templates/users/user_group_detail.html:124 #, fuzzy #| msgid "Address" msgid "Add" msgstr "地址" +#: assets/templates/assets/asset_group_list.html:5 assets/views.py:157 +#: assets/views.py:234 +msgid "Create asset group" +msgstr "创建资产组" + #: assets/templates/assets/asset_group_list.html:43 -#: assets/templates/assets/asset_list.html:56 +#: assets/templates/assets/asset_list.html:98 +#: assets/templates/assets/asset_tags_list.html:42 +#: ops/templates/cron/list.html:41 ops/templates/sudo/list.html:41 #: perms/templates/perms/asset_permission_list.html:57 -#: users/templates/users/user_list.html:56 +#: users/templates/users/user_group_list.html:36 +#: users/templates/users/user_list.html:41 msgid "Delete selected" msgstr "批量删除" #: assets/templates/assets/asset_group_list.html:44 -#: assets/templates/assets/asset_list.html:57 +#: assets/templates/assets/asset_list.html:99 +#: assets/templates/assets/asset_tags_list.html:43 +#: ops/templates/cron/list.html:42 ops/templates/sudo/list.html:42 #: perms/templates/perms/asset_permission_list.html:58 -#: users/templates/users/user_list.html:57 +#: users/templates/users/user_list.html:42 msgid "Update selected" msgstr "批量更新" #: assets/templates/assets/asset_group_list.html:45 -#: assets/templates/assets/asset_list.html:58 +#: assets/templates/assets/asset_list.html:100 +#: assets/templates/assets/asset_tags_list.html:44 +#: ops/templates/cron/list.html:43 ops/templates/sudo/list.html:43 #: perms/templates/perms/asset_permission_list.html:59 -#: users/templates/users/user_list.html:58 +#: users/templates/users/user_list.html:43 msgid "Deactive selected" msgstr "禁用所选" #: assets/templates/assets/asset_group_list.html:46 -#: assets/templates/assets/asset_list.html:59 +#: assets/templates/assets/asset_list.html:101 +#: assets/templates/assets/asset_tags_list.html:45 #: perms/templates/perms/asset_permission_list.html:60 -#: users/templates/users/user_list.html:59 msgid "Export selected" msgstr "批量导出" -#: assets/templates/assets/asset_list.html:5 +#: assets/templates/assets/asset_list.html:21 msgid "Create asset" msgstr "创建资产" -#: assets/templates/assets/asset_list.html:15 +#: assets/templates/assets/asset_list.html:60 msgid "Type" msgstr "" -#: assets/templates/assets/asset_list.html:16 +#: assets/templates/assets/asset_list.html:61 +#: assets/templates/assets/asset_update.html:31 msgid "Hardware" msgstr "" -#: assets/templates/assets/asset_list.html:17 +#: assets/templates/assets/asset_list.html:62 msgid "Valid" msgstr "" +#: assets/templates/assets/asset_tag_detail.html:45 +#: assets/templates/assets/asset_tags_list.html:12 +#, fuzzy +#| msgid "Name" +msgid "Tag Name" +msgstr "名称" + +#: assets/templates/assets/asset_tags_list.html:5 +#, fuzzy +#| msgid "Create asset" +msgid "Create tag" +msgstr "创建资产" + +#: assets/templates/assets/asset_update.html:40 +#, fuzzy +#| msgid "Confirm delete" +msgid "Configuration" +msgstr "确认删除" + +#: assets/templates/assets/asset_update.html:47 +#, fuzzy +msgid "Location" +msgstr "激活" + #: assets/templates/assets/delete_confirm.html:6 #: perms/templates/perms/delete_confirm.html:6 #: users/templates/users/user_delete_confirm.html:6 msgid "Confirm delete" msgstr "确认删除" -#: assets/templates/assets/idc_create_update.html:16 -#, fuzzy -#| msgid "Created by" -msgid "Create idc" -msgstr "创建者" - -#: assets/templates/assets/idc_list.html:5 +#: assets/templates/assets/idc_list.html:5 assets/views.py:287 #, fuzzy #| msgid "Created by" msgid "Create IDC" msgstr "创建者" +#: assets/templates/assets/idc_list.html:16 +#, fuzzy +#| msgid "Operator" +msgid "operation" +msgstr "运营商" + #: assets/templates/assets/system_user_asset.html:22 #: assets/templates/assets/system_user_detail.html:23 #, fuzzy @@ -864,7 +956,7 @@ msgid "Select asset group" msgstr "添加到资产组" #: assets/templates/assets/system_user_create_update.html:16 -#: assets/templates/assets/system_user_list.html:5 assets/views.py:368 +#: assets/templates/assets/system_user_list.html:5 assets/views.py:469 #, fuzzy #| msgid "Create user" msgid "Create system user" @@ -887,67 +979,375 @@ msgstr "" msgid "Asset group num" msgstr "资产组" -#: assets/templates/assets/system_user_list.html:14 +#: assets/templates/assets/system_user_list.html:14 ops/models.py:69 msgid "Unreachable" msgstr "" -#: assets/views.py:79 assets/views.py:99 assets/views.py:133 -#: assets/views.py:149 assets/views.py:171 assets/views.py:238 -#: assets/views.py:337 assets/views.py:367 assets/views.py:390 -#: assets/views.py:408 templates/_nav.html:18 +#: assets/views.py:156 assets/views.py:182 assets/views.py:214 +#: assets/views.py:233 assets/views.py:257 assets/views.py:339 +#: assets/views.py:438 assets/views.py:468 assets/views.py:491 +#: assets/views.py:509 templates/_nav.html:18 msgid "Assets" msgstr "资产管理" -#: assets/views.py:100 +#: assets/views.py:183 msgid "Asset group list" msgstr "资产组列表" -#: assets/views.py:134 +#: assets/views.py:215 #, fuzzy #| msgid "Asset group list" msgid "Asset group detail" msgstr "资产组列表" -#: assets/views.py:172 +#: assets/views.py:258 msgid "IDC list" msgstr "" -#: assets/views.py:239 +#: assets/views.py:286 assets/views.py:313 +#, fuzzy +#| msgid "Assets" +msgid "assets" +msgstr "资产管理" + +#: assets/views.py:314 +#, fuzzy +#| msgid "Update" +msgid "Update IDC" +msgstr "更新" + +#: assets/views.py:340 #, fuzzy #| msgid "Admin user" msgid "Admin user list" msgstr "管理用户" -#: assets/views.py:275 +#: assets/views.py:376 #, fuzzy, python-format #| msgid "Create user %s success." msgid "Create admin user %s successfully." msgstr "创建用户 %s 成功" -#: assets/views.py:338 +#: assets/views.py:439 #, fuzzy #| msgid "System user" msgid "System user list" msgstr "系统" -#: assets/views.py:374 +#: assets/views.py:475 #, fuzzy, python-format #| msgid "Create user %s success." msgid "Create system user %s successfully." msgstr "创建用户 %s 成功" -#: assets/views.py:391 +#: assets/views.py:492 #, fuzzy #| msgid "Update user" msgid "Update system user" msgstr "编辑用户" -#: assets/views.py:409 +#: assets/views.py:510 #, fuzzy #| msgid "System user" msgid "System user detail" msgstr "系统" +#: assets/views.py:601 assets/views.py:619 assets/views.py:648 +#: assets/views.py:667 +msgid "Tag" +msgstr "" + +#: assets/views.py:602 assets/views.py:620 +#, fuzzy +#| msgid "Asset group list" +msgid "Asset Tags list" +msgstr "资产组列表" + +#: assets/views.py:649 assets/views.py:668 +#, fuzzy +#| msgid "Asset group list" +msgid "Asset Tags detail" +msgstr "资产组列表" + +#: common/mixins.py:28 +msgid "is discard" +msgstr "" + +#: common/mixins.py:29 +msgid "discard time" +msgstr "" + +#: ops/api/exc.py:10 +msgid "Service temporarily unavailable, try again later." +msgstr "" + +#: ops/api/exc.py:15 +msgid "This service maybe implemented in the future, but now not implemented!" +msgstr "" + +#: ops/models.py:17 ops/models.py:35 ops/models.py:47 +msgid "UUID" +msgstr "" + +#: ops/models.py:19 +msgid "Start Time" +msgstr "" + +#: ops/models.py:20 +msgid "End Time" +msgstr "" + +#: ops/models.py:21 +msgid "Exit Code" +msgstr "" + +#: ops/models.py:22 +msgid "Is Completed" +msgstr "" + +#: ops/models.py:23 +#, fuzzy +#| msgid "Hostname" +msgid "Hosts" +msgstr "主机名" + +#: ops/models.py:66 +msgid "Success" +msgstr "" + +#: ops/models.py:67 +msgid "Skipped" +msgstr "" + +#: ops/models.py:68 +msgid "Failed" +msgstr "" + +#: ops/models.py:70 +msgid "NoHost" +msgstr "" + +#: ops/models.py:217 +msgid "Host_Alias" +msgstr "" + +#: ops/models.py:218 ops/models.py:226 ops/models.py:234 ops/models.py:242 +msgid "Host_Items" +msgstr "" + +#: ops/models.py:225 +#, fuzzy +#| msgid "User list" +msgid "User_Alias" +msgstr "用户列表" + +#: ops/models.py:233 +msgid "Command_Alias" +msgstr "" + +#: ops/models.py:241 +msgid "Runas_Alias" +msgstr "" + +#: ops/models.py:253 +#, fuzzy +#| msgid "Password" +msgid "Is_NoPassword" +msgstr "密码" + +#: ops/models.py:267 +msgid "Extra_Item" +msgstr "" + +#: ops/models.py:398 +msgid "Description of a crontab entry" +msgstr "" + +#: ops/models.py:399 +msgid "Month" +msgstr "" + +#: ops/models.py:400 +msgid "Month of the year the job should run ( 1-12, *, */2, etc )" +msgstr "" + +#: ops/models.py:401 +msgid "WeekDay" +msgstr "" + +#: ops/models.py:402 +msgid "" +"Day of the week that the job should run ( 0-6 for Sunday-Saturday, *, etc )" +msgstr "" + +#: ops/models.py:404 +msgid "Day" +msgstr "" + +#: ops/models.py:405 +msgid "Day of the month the job should run ( 1-31, *, */2, etc )" +msgstr "" + +#: ops/models.py:406 +msgid "Hour" +msgstr "" + +#: ops/models.py:407 +msgid "Hour when the job should run ( 0-23, *, */2, etc )" +msgstr "" + +#: ops/models.py:408 +msgid "Minute" +msgstr "" + +#: ops/models.py:409 +msgid "Minute when the job should run ( 0-59, *, */2, etc )" +msgstr "" + +#: ops/models.py:410 +msgid "Job" +msgstr "" + +#: ops/models.py:411 +msgid "" +"The command to execute or, if env is set, the value of environment variable. " +"Required if state=present." +msgstr "" + +#: ops/models.py:413 +#: perms/templates/perms/asset_permission_create_update.html:36 +#: templates/_nav.html:12 templates/_user_profile.html:14 users/models.py:71 +#: users/templates/users/_user_bulk_update_modal.html:14 +msgid "User" +msgstr "用户" + +#: ops/models.py:414 +msgid "The specific user whose crontab should be modified." +msgstr "" + +#: ops/templates/cron/list.html:18 ops/templates/sudo/list.html:18 +#: users/templates/users/user_list.html:18 +#, fuzzy +#| msgid "Create user" +msgid "Import user" +msgstr "创建用户" + +#: ops/templates/cron/list.html:19 ops/templates/sudo/list.html:19 +#: users/templates/users/_user.html:17 users/templates/users/user_create.html:4 +#: users/templates/users/user_list.html:19 users/views.py:102 +msgid "Create user" +msgstr "创建用户" + +#: ops/templates/cron/list.html:28 ops/templates/sudo/list.html:28 +#: users/models.py:78 users/templates/users/_select_user_modal.html:15 +#: users/templates/users/_user_bulk_update_modal.html:9 +#: users/templates/users/user_detail.html:82 +#: users/templates/users/user_list.html:28 +msgid "Role" +msgstr "角色" + +#: ops/templates/cron/list.html:29 ops/templates/sudo/list.html:29 +#: templates/_nav.html:13 users/models.py:77 +#: users/templates/users/_select_user_modal.html:16 +#: users/templates/users/user_detail.html:178 +#: users/templates/users/user_list.html:29 +msgid "User group" +msgstr "用户组" + +#: ops/templates/cron/list.html:32 ops/templates/sudo/list.html:32 +#: users/templates/users/user_group_list.html:29 +#: users/templates/users/user_list.html:32 +#, fuzzy +msgid "Action" +msgstr "激活" + +#: ops/templates/cron/list.html:116 ops/templates/cron/list.html:178 +#: ops/templates/sudo/list.html:116 ops/templates/sudo/list.html:178 +#: users/templates/users/user_detail.html:329 +#: users/templates/users/user_detail.html:354 +#: users/templates/users/user_group_detail.html:169 +#: users/templates/users/user_group_list.html:102 +#: users/templates/users/user_group_list.html:125 +#: users/templates/users/user_list.html:116 +#: users/templates/users/user_list.html:178 +msgid "Are you sure?" +msgstr "" + +#: ops/templates/cron/list.html:117 ops/templates/sudo/list.html:117 +#: users/templates/users/user_list.html:117 +msgid "This will delete the selected users !!!" +msgstr "" + +#: ops/templates/cron/list.html:121 ops/templates/cron/list.html:183 +#: ops/templates/sudo/list.html:121 ops/templates/sudo/list.html:183 +#: templates/_modal.html:16 users/templates/users/user_detail.html:334 +#: users/templates/users/user_detail.html:359 +#: users/templates/users/user_detail.html:383 +#: users/templates/users/user_group_detail.html:174 +#: users/templates/users/user_group_list.html:107 +#: users/templates/users/user_group_list.html:130 +#: users/templates/users/user_list.html:121 +#: users/templates/users/user_list.html:183 +#, fuzzy +#| msgid "Confirm delete" +msgid "Confirm" +msgstr "确认删除" + +# msgid "Deleted!" +# msgstr "删除" +#: ops/templates/cron/list.html:125 ops/templates/cron/list.html:161 +#: ops/templates/sudo/list.html:125 ops/templates/sudo/list.html:161 +#: users/templates/users/user_list.html:125 +#: users/templates/users/user_list.html:161 +#, fuzzy +#| msgid "has been deleted." +msgid "User Deleted." +msgstr "已被删除" + +#: ops/templates/cron/list.html:126 ops/templates/cron/list.html:131 +#: ops/templates/cron/list.html:162 ops/templates/cron/list.html:167 +#: ops/templates/sudo/list.html:126 ops/templates/sudo/list.html:131 +#: ops/templates/sudo/list.html:162 ops/templates/sudo/list.html:167 +#: users/templates/users/user_list.html:126 +#: users/templates/users/user_list.html:131 +#: users/templates/users/user_list.html:162 +#: users/templates/users/user_list.html:167 +#, fuzzy +#| msgid "Delete" +msgid "User Delete" +msgstr "删除" + +#: ops/templates/cron/list.html:130 ops/templates/cron/list.html:166 +#: ops/templates/sudo/list.html:130 ops/templates/sudo/list.html:166 +#: users/templates/users/user_list.html:130 +#: users/templates/users/user_list.html:166 +#, fuzzy +#| msgid "User detail" +msgid "User Deleting failed." +msgstr "用户详情" + +#: ops/templates/cron/list.html:179 ops/templates/sudo/list.html:179 +#: users/templates/users/user_list.html:179 +msgid "This will delete the selected user." +msgstr "" + +#: ops/templates/cron/list.html:215 ops/templates/sudo/list.html:215 +#: users/templates/users/user_list.html:215 +msgid "The selected users has been updated successfully." +msgstr "" + +#: ops/templates/cron/list.html:216 ops/templates/sudo/list.html:216 +#: users/templates/users/user_list.html:216 +#, fuzzy +#| msgid "Update" +msgid "User Updated" +msgstr "更新" + +#: ops/templates/cron/list.html:232 ops/templates/sudo/list.html:232 +#: users/templates/users/user_list.html:232 +msgid "Import User Success." +msgstr "" + #: perms/forms.py:21 #, fuzzy #| msgid "Select assets" @@ -961,7 +1361,7 @@ msgid "Select user groups" msgstr "添加到资产组" #: perms/forms.py:29 perms/templates/perms/asset_permission_detail.html:155 -#: users/forms.py:117 +#: users/forms.py:126 #, fuzzy #| msgid "System user" msgid "Select system users" @@ -974,7 +1374,7 @@ msgid "Private for" msgstr "ssh密钥" #: perms/models.py:28 perms/templates/perms/asset_permission_detail.html:80 -#: users/models.py:81 users/templates/users/user_detail.html:86 +#: users/models.py:89 users/templates/users/user_detail.html:86 msgid "Date expired" msgstr "失效日期" @@ -1022,11 +1422,6 @@ msgstr "" msgid "Create asset permission " msgstr "创建资产组" -#: perms/templates/perms/asset_permission_create_update.html:36 -#: templates/_nav.html:12 templates/_user_profile.html:14 users/models.py:63 -msgid "User" -msgstr "用户" - #: perms/templates/perms/asset_permission_detail.html:60 #: perms/templates/perms/asset_permission_list.html:13 #, fuzzy @@ -1091,7 +1486,7 @@ msgstr "创建权限" msgid "User list of " msgstr "用户列表" -#: perms/templates/perms/asset_permission_user.html:68 users/models.py:68 +#: perms/templates/perms/asset_permission_user.html:68 users/models.py:76 #: users/templates/users/user_detail.html:66 msgid "Email" msgstr "邮件" @@ -1188,24 +1583,16 @@ msgstr "登录" msgid "Close" msgstr "" -#: templates/_modal.html:16 users/templates/users/user_detail.html:304 -#: users/templates/users/user_detail.html:326 -#, fuzzy -#| msgid "Confirm delete" -msgid "Confirm" -msgstr "确认删除" - -#: templates/_nav.html:9 users/views.py:102 users/views.py:115 -#: users/views.py:155 users/views.py:186 users/views.py:211 users/views.py:224 -#: users/views.py:340 +#: templates/_nav.html:9 users/templates/users/user_group_create.html:24 +#: users/templates/users/user_group_detail.html:102 users/views.py:89 +#: users/views.py:102 users/views.py:138 users/views.py:149 users/views.py:159 +#: users/views.py:172 users/views.py:197 users/views.py:219 users/views.py:320 msgid "Users" msgstr "用户管理" -#: templates/_nav.html:13 users/models.py:69 -#: users/templates/users/user_detail.html:170 -#: users/templates/users/user_list.html:15 -msgid "User group" -msgstr "用户组" +#: templates/_nav.html:26 +msgid "Label" +msgstr "标签" #: templates/_nav.html:33 users/templates/users/user_asset_permission.html:23 #: users/templates/users/user_detail.html:24 @@ -1215,27 +1602,35 @@ msgstr "用户组" msgid "Asset permission" msgstr "系统类型" -#: templates/_nav.html:42 +#: templates/_nav.html:43 +msgid "Job Center" +msgstr "作业中心" + +#: templates/_nav.html:47 +msgid "Cron" +msgstr "Cron" + +#: templates/_nav.html:53 msgid "Audits" msgstr "审计" -#: templates/_nav.html:47 +#: templates/_nav.html:58 msgid "File" msgstr "文件" -#: templates/_nav.html:50 +#: templates/_nav.html:61 msgid "File upload" msgstr "文件上传" -#: templates/_nav.html:51 +#: templates/_nav.html:62 msgid "File download" msgstr "文件下载" -#: templates/_nav.html:56 +#: templates/_nav.html:67 msgid "Settings" msgstr "设置" -#: templates/_nav.html:61 +#: templates/_nav.html:72 msgid "Visit us" msgstr "访问官网" @@ -1243,6 +1638,23 @@ msgstr "访问官网" msgid "Profile" msgstr "个人信息" +#: templates/base.html:27 +#, python-format +msgid "" +"\n" +" Your information was incomplete. Please click this link to complete your information.\n" +" " +msgstr "" + +#: templates/base.html:36 +msgid "" +"\n" +" Your ssh-public-key has been expired. Please click this link to update your ssh-public-key.\n" +" " +msgstr "" + #: templates/captcha/image.html:3 msgid "Play CAPTCHA as audio file" msgstr "" @@ -1256,94 +1668,94 @@ msgstr "验证码" msgid "Filters" msgstr "过滤" -#: users/forms.py:68 +#: users/forms.py:33 users/forms.py:58 +#: users/templates/users/user_detail.html:186 +msgid "Join user groups" +msgstr "添加到用户组" + +#: users/forms.py:75 #, fuzzy #| msgid "Name" msgid "name" msgstr "名称" -#: users/forms.py:69 +#: users/forms.py:76 #, fuzzy #| msgid "Avatar" msgid "avatar" msgstr "头像" -#: users/forms.py:70 +#: users/forms.py:77 #, fuzzy #| msgid "Wechat" msgid "wechat" msgstr "微信" -#: users/forms.py:71 +#: users/forms.py:78 #, fuzzy #| msgid "Phone" msgid "phone" msgstr "手机" -#: users/forms.py:72 +#: users/forms.py:79 #, fuzzy #| msgid "Enable OTP" msgid "enable otp" msgstr "二次验证" -#: users/forms.py:77 users/models.py:77 +#: users/forms.py:84 users/models.py:85 msgid "ssh public key" msgstr "ssh公钥" -#: users/forms.py:78 +#: users/forms.py:85 msgid "ssh-rsa AAAA..." msgstr "" -#: users/forms.py:79 +#: users/forms.py:86 msgid "Paste your id_ras.pub here." msgstr "" -#: users/forms.py:90 users/forms.py:93 +#: users/forms.py:91 +msgid "Public key should not be the same as your old one." +msgstr "" + +#: users/forms.py:99 users/forms.py:102 users/serializers.py:32 +#: users/serializers.py:35 #, fuzzy #| msgid "ssh private key" msgid "Not a valid ssh public key" msgstr "ssh密钥" -#: users/models.py:62 users/models.py:206 +#: users/models.py:70 users/models.py:220 msgid "Administrator" msgstr "管理员" -#: users/models.py:70 users/templates/users/user_detail.html:82 -#: users/templates/users/user_list.html:14 -msgid "Role" -msgstr "角色" - -#: users/models.py:71 +#: users/models.py:79 msgid "Avatar" msgstr "头像" -#: users/models.py:72 users/templates/users/user_detail.html:77 +#: users/models.py:80 users/templates/users/user_detail.html:77 msgid "Wechat" msgstr "微信" -#: users/models.py:76 +#: users/models.py:82 users/templates/users/_user.html:57 +#: users/templates/users/user_detail.html:133 +msgid "Enable OTP" +msgstr "二次验证" + +#: users/models.py:84 msgid "ssh private key" msgstr "ssh密钥" -#: users/models.py:209 +#: users/models.py:223 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" -#: users/models.py:210 -msgid "System" -msgstr "系统" - -#: users/serializers.py:55 +#: users/templates/users/_select_user_modal.html:5 #, fuzzy -#| msgid "ssh private key" -msgid "Not a valid ssh private key." -msgstr "ssh密钥" - -#: users/templates/users/_user.html:17 -#: users/templates/users/user_create.html:4 -#: users/templates/users/user_list.html:5 users/views.py:115 -msgid "Create user" -msgstr "创建用户" +#| msgid "Select assets" +msgid "Please Select User" +msgstr "选择资产" #: users/templates/users/_user.html:33 msgid "Account" @@ -1353,33 +1765,87 @@ msgstr "账户" msgid "Security and Role" msgstr "角色安全" -#: users/templates/users/_user_reset_pk_modal.html:4 +#: users/templates/users/_user_bulk_update_modal.html:4 +#, fuzzy +#| msgid "Update user" +msgid "Update User" +msgstr "编辑用户" + +#: users/templates/users/_user_bulk_update_modal.html:6 +msgid "Hint: only change the field you want to update." +msgstr "" + +#: users/templates/users/_user_bulk_update_modal.html:13 +#, fuzzy +#| msgid "Admin user" +msgid "Admin" +msgstr "管理用户" + +#: users/templates/users/_user_bulk_update_modal.html:19 +msgid "Groups" +msgstr "" + +#: users/templates/users/_user_bulk_update_modal.html:21 +#, fuzzy +#| msgid "Select asset groups" +msgid "Select Group" +msgstr "添加到资产组" + +#: users/templates/users/_user_bulk_update_modal.html:31 +#, fuzzy +#| msgid "Enable OTP" +msgid "Enable-OTP" +msgstr "二次验证" + +#: users/templates/users/_user_import_modal.html:4 +msgid "Import User" +msgstr "" + +#: users/templates/users/_user_import_modal.html:6 +msgid "Hint: your excel should organized in the following format." +msgstr "" + +#: users/templates/users/_user_import_modal.html:7 +msgid "* You should have a very worksheet named `users`." +msgstr "" + +#: users/templates/users/_user_import_modal.html:8 +msgid "" +"* Rows in this worksheet: username, email, enable_opt(0, 1), role(one of " +"['Admin', 'User'])" +msgstr "" + +#: users/templates/users/_user_import_modal.html:12 +msgid "Excel" +msgstr "" + +#: users/templates/users/_user_update_pk_modal.html:4 #, fuzzy #| msgid "SSH private key" -msgid "Reset User SSH Private Key" +msgid "Update User SSH Public Key" msgstr "ssh密钥" -#: users/templates/users/first_login.html:16 users/views.py:340 +#: users/templates/users/first_login.html:18 users/views.py:320 #, fuzzy #| msgid "Last login" msgid "First Login" msgstr "最后登录" -#: users/templates/users/first_login.html:33 +#: users/templates/users/first_login.html:35 #, fuzzy #| msgid "System" msgid "Step" msgstr "系统" -#: users/templates/users/first_login.html:55 +#: users/templates/users/first_login.html:57 msgid "first step" msgstr "" -#: users/templates/users/first_login.html:56 +#: users/templates/users/first_login.html:58 msgid "prev step" msgstr "" -#: users/templates/users/first_login.html:58 +#: users/templates/users/first_login.html:60 #, fuzzy #| msgid "Submit" msgid "submit" @@ -1398,6 +1864,11 @@ msgstr "输入您的邮箱, 将会发一封重置短信邮件到您的邮箱中" msgid "Captcha invalid" msgstr "验证码错误" +#: users/templates/users/reset_password.html:45 +#: users/templates/users/user_detail.html:148 users/utils.py:99 +msgid "Reset password" +msgstr "重置密码" + #: users/templates/users/reset_password.html:55 msgid "Password again" msgstr "再次输入密码" @@ -1409,7 +1880,7 @@ msgstr "设置" #: users/templates/users/user_asset_permission.html:20 #: users/templates/users/user_detail.html:21 -#: users/templates/users/user_granted_asset.html:20 users/views.py:186 +#: users/templates/users/user_granted_asset.html:20 users/views.py:149 msgid "User detail" msgstr "用户详情" @@ -1477,49 +1948,69 @@ msgstr "生成重置密码连接,通过邮件发送给用户" msgid "Last login" msgstr "最后登录" -#: users/templates/users/user_detail.html:236 +#: users/templates/users/user_detail.html:156 +msgid "Reset ssh key" +msgstr "重置密钥" + +#: users/templates/users/user_detail.html:164 +#, fuzzy +#| msgid "Update user" +msgid "Update ssh key" +msgstr "编辑用户" + +#: users/templates/users/user_detail.html:246 msgid "UserGroup Update Success!" msgstr "" -#: users/templates/users/user_detail.html:254 -#: users/templates/users/user_detail.html:260 +#: users/templates/users/user_detail.html:270 +#: users/templates/users/user_detail.html:282 #, fuzzy #| msgid "Create account successfully" msgid "Update Successfully!" msgstr "创建账户成功" -#: users/templates/users/user_detail.html:293 +#: users/templates/users/user_detail.html:319 msgid "E-mail sent successfully. An e-mail has been sent to the user\\" msgstr "" -#: users/templates/users/user_detail.html:294 +#: users/templates/users/user_detail.html:320 #, fuzzy #| msgid "Password" msgid "Password-Reset" msgstr "密码" -#: users/templates/users/user_detail.html:299 -#: users/templates/users/user_detail.html:321 -msgid "Are you sure?" -msgstr "" - -#: users/templates/users/user_detail.html:300 -#: users/templates/users/user_detail.html:322 +#: users/templates/users/user_detail.html:330 +#: users/templates/users/user_detail.html:355 msgid "This will reset the user\\" msgstr "" -#: users/templates/users/user_detail.html:315 +#: users/templates/users/user_detail.html:344 msgid "" "The reset-ssh-public-key E-mail has been sent successfully. Please inform " "the user to update his new ssh public key." msgstr "" -#: users/templates/users/user_detail.html:316 +#: users/templates/users/user_detail.html:345 #, fuzzy #| msgid "SSH private key" msgid "SSH-Public-Key Reset" msgstr "ssh密钥" +#: users/templates/users/user_detail.html:372 +msgid "Successfully updated the SSH public key." +msgstr "" + +#: users/templates/users/user_detail.html:373 +#: users/templates/users/user_detail.html:378 +#, fuzzy +#| msgid "SSH private key" +msgid "User SSH Public Key Update" +msgstr "ssh密钥" + +#: users/templates/users/user_detail.html:376 +msgid "Failed to update the user\\" +msgstr "" + #: users/templates/users/user_granted_asset.html:47 #, fuzzy #| msgid "Create asset group" @@ -1532,11 +2023,111 @@ msgstr "创建资产组" msgid "Asset groups granted of " msgstr "资产组列表" -#: users/templates/users/user_group_create.html:16 users/views.py:224 -msgid "Create user group" -msgstr "创建用户组" +#: users/templates/users/user_group_create.html:26 +#, fuzzy +#| msgid "Select assets" +msgid "Select User" +msgstr "选择资产" -#: users/templates/users/user_update.html:3 users/views.py:155 +#: users/templates/users/user_group_create.html:38 +msgid "Cancel" +msgstr "" + +#: users/templates/users/user_group_create.html:39 +#, fuzzy +#| msgid "Confirm delete" +msgid "confirm" +msgstr "确认删除" + +#: users/templates/users/user_group_detail.html:70 users/views.py:219 +#, fuzzy +#| msgid "Asset group list" +msgid "User Group Detail" +msgstr "资产组列表" + +#: users/templates/users/user_group_detail.html:98 +#, fuzzy +#| msgid "Create asset" +msgid "Created at" +msgstr "创建资产" + +#: users/templates/users/user_group_detail.html:121 +#, fuzzy +#| msgid "User" +msgid "Add User" +msgstr "用户" + +#: users/templates/users/user_group_detail.html:170 +msgid "This will delete the current group, but will not delete any user of it." +msgstr "" + +#: users/templates/users/user_group_detail.html:223 +msgid "The selected users has been added to current group." +msgstr "" + +#: users/templates/users/user_group_list.html:18 +#, fuzzy +#| msgid "Asset group" +msgid "Add User Group" +msgstr "资产组" + +#: users/templates/users/user_group_list.html:26 +#, fuzzy +#| msgid "User group" +msgid "User Amount" +msgstr "用户组" + +#: users/templates/users/user_group_list.html:27 +#, fuzzy +#| msgid "Asset group" +msgid "Asset Amount" +msgstr "资产组" + +#: users/templates/users/user_group_list.html:85 +#, fuzzy +#| msgid "Delete" +msgid "Group Deleted." +msgstr "删除" + +#: users/templates/users/user_group_list.html:86 +#: users/templates/users/user_group_list.html:91 +#, fuzzy +#| msgid "Delete" +msgid "Group Delete" +msgstr "删除" + +#: users/templates/users/user_group_list.html:90 +msgid "Group Deleting failed." +msgstr "" + +#: users/templates/users/user_group_list.html:103 +msgid "" +"This will delete the selected group, but will not remove any user in this " +"group." +msgstr "" + +#: users/templates/users/user_group_list.html:126 +msgid "This will delete the selected groups !!!" +msgstr "" + +#: users/templates/users/user_group_list.html:134 +#, fuzzy +#| msgid "User group list" +msgid "UserGroups Deleted." +msgstr "用户组列表" + +#: users/templates/users/user_group_list.html:135 +#: users/templates/users/user_group_list.html:140 +#, fuzzy +#| msgid "User group list" +msgid "UserGroups Delete" +msgstr "用户组列表" + +#: users/templates/users/user_group_list.html:139 +msgid "UserGroup Deleting failed." +msgstr "" + +#: users/templates/users/user_update.html:3 users/views.py:138 msgid "Update user" msgstr "编辑用户" @@ -1567,8 +2158,8 @@ msgid "" " click " "here to set your password\n" "
\n" -" This link is valid for 1 hour. After it expires, request new one\n" +" This link is valid for 1 hour. After it expires, request new one\n" "\n" "
\n" " ---\n" @@ -1609,8 +2200,8 @@ msgid "" " Click " "here reset password\n" "
\n" -" This link is valid for 1 hour. After it expires, request new one\n" +" This link is valid for 1 hour. After it expires, request new one<\n" "\n" "
\n" " ---\n" @@ -1662,57 +2253,90 @@ msgid "" " " msgstr "" -#: users/views.py:73 +#: users/views.py:75 msgid "Logout success" msgstr "退出登录成功" -#: users/views.py:74 +#: users/views.py:76 msgid "Logout success, return login page" msgstr "退出登录成功,返回到登录页面" -#: users/views.py:102 +#: users/views.py:89 msgid "User list" msgstr "用户列表" -#: users/views.py:111 +#: users/views.py:98 #, fuzzy, python-format #| msgid "Create user %s success." msgid "Create user %s successfully." msgstr "创建用户 %s 成功" -#: users/views.py:211 +#: users/views.py:159 msgid "User group list" msgstr "用户组列表" -#: users/views.py:256 +#: users/views.py:172 +msgid "Create user group" +msgstr "创建用户组" + +#: users/views.py:198 +#, fuzzy +#| msgid "Update user" +msgid "Update User Group" +msgstr "编辑用户" + +#: users/views.py:235 msgid "Email address invalid, input again" msgstr "邮箱地址错误,重新输入" -#: users/views.py:267 +#: users/views.py:246 msgid "Send reset password message" msgstr "发送重置密码邮件" -#: users/views.py:268 +#: users/views.py:247 msgid "Send reset password mail success, login your mail box and follow it " msgstr "" "发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" -#: users/views.py:280 +#: users/views.py:259 msgid "Reset password success" msgstr "重置密码成功" -#: users/views.py:281 +#: users/views.py:260 msgid "Reset password success, return to login page" msgstr "重置密码成功,返回到登录页面" -#: users/views.py:297 users/views.py:310 +#: users/views.py:276 users/views.py:289 msgid "Token invalid or expired" msgstr "Token错误或失效" -#: users/views.py:306 +#: users/views.py:285 msgid "Password not same" msgstr "密码不一致" +#: users/views.py:447 +msgid "Invalid file." +msgstr "" + +#: users/views.py:461 +#, fuzzy +#| msgid "ssh private key" +msgid "Not a valid Excel file." +msgstr "ssh密钥" + +#~ msgid "Network" +#~ msgstr "网络" + +#, fuzzy +#~| msgid "Asset number" +#~ msgid "Asset users" +#~ msgstr "资产编号" + +#, fuzzy +#~| msgid "Created by" +#~ msgid "Create idc" +#~ msgstr "创建者" + #~ msgid "Admin password" #~ msgstr "管理员密码" @@ -1721,10 +2345,6 @@ msgstr "密码不一致" #~ msgid "Update system user %s successfully." #~ msgstr "创建用户 %s 成功" -#, fuzzy -#~ msgid "Action" -#~ msgstr "激活" - #, fuzzy #~| msgid "Create perm" #~ msgid "Create perm " @@ -1733,11 +2353,6 @@ msgstr "密码不一致" #~ msgid "Create perm" #~ msgstr "创建权限" -# msgid "Deleted!" -# msgstr "删除" -#~ msgid "has been deleted." -#~ msgstr "已被删除" - #~ msgid "User assets" #~ msgstr "用户资产" diff --git a/apps/ops/ansible_api.py b/apps/ops/ansible_api.py deleted file mode 100644 index f33089ba9..000000000 --- a/apps/ops/ansible_api.py +++ /dev/null @@ -1,310 +0,0 @@ -# ~*~ coding: utf-8 ~*~ -from __future__ import unicode_literals - -import os -import json -from ansible.executor.task_queue_manager import TaskQueueManager -from ansible.inventory import Inventory, Host, Group -from ansible.vars import VariableManager -from ansible.parsing.dataloader import DataLoader -from ansible.executor import playbook_executor -from ansible.utils.display import Display -from ansible.playbook.play import Play -import ansible.constants as default_config -from ansible.plugins.callback import CallbackBase - - -class AnsibleError(StandardError): - pass - - -class Config(object): - """Ansible运行时配置类, 用于初始化Ansible. - """ - def __init__(self, verbosity=None, inventory=None, listhosts=None, subset=None, module_paths=None, extra_vars=None, - forks=None, ask_vault_pass=None, vault_password_files=None, new_vault_password_file=None, - output_file=None, tags=None, skip_tags=None, one_line=None, tree=None, ask_sudo_pass=None, ask_su_pass=None, - sudo=None, sudo_user=None, become=None, become_method=None, become_user=None, become_ask_pass=None, - ask_pass=None, private_key_file=None, remote_user=None, connection=None, timeout=None, ssh_common_args=None, - sftp_extra_args=None, scp_extra_args=None, ssh_extra_args=None, poll_interval=None, seconds=None, check=None, - syntax=None, diff=None, force_handlers=None, flush_cache=None, listtasks=None, listtags=None, module_path=None): - self.verbosity = verbosity - self.inventory = inventory - self.listhosts = listhosts - self.subset = subset - self.module_paths = module_paths - self.extra_vars = extra_vars - self.forks = forks - self.ask_vault_pass = ask_vault_pass - self.vault_password_files = vault_password_files - self.new_vault_password_file = new_vault_password_file - self.output_file = output_file - self.tags = tags - self.skip_tags = skip_tags - self.one_line = one_line - self.tree = tree - self.ask_sudo_pass = ask_sudo_pass - self.ask_su_pass = ask_su_pass - self.sudo = sudo - self.sudo_user = sudo_user - self.become = become - self.become_method = become_method - self.become_user = become_user - self.become_ask_pass = become_ask_pass - self.ask_pass = ask_pass - self.private_key_file = private_key_file - self.remote_user = remote_user - self.connection = connection - self.timeout = timeout - self.ssh_common_args = ssh_common_args - self.sftp_extra_args = sftp_extra_args - self.scp_extra_args = scp_extra_args - self.ssh_extra_args = ssh_extra_args - self.poll_interval = poll_interval - self.seconds = seconds - self.check = check - self.syntax = syntax - self.diff = diff - self.force_handlers = force_handlers - self.flush_cache = flush_cache - self.listtasks = listtasks - self.listtags = listtags - self.module_path = module_path - self.__overwrite_default() - - def __overwrite_default(self): - """上面并不能包含Ansible所有的配置, 如果有其他的配置, - 可以通过替换default_config模块里面的变量进行重载,  - 比如 default_config.DEFAULT_ASK_PASS = False. - """ - default_config.HOST_KEY_CHECKING = False - - -class MyInventory(object): - """Ansible Inventory对象的封装, Inventory是Ansbile中的核心概念(资产清单), - 这个概念和CMDB很像,都是对资产的抽象. 为了简化Inventory的使用, 通过传入资产列表即可初始化Inventory. - """ - - def __init__(self, *assets, **group): - """初始化Inventory对象, args为一个资产列表, kwargs是资产组变量列表, 比如 - args: - [{ - "name": "asset_name", - "ip": "asset_ip", - "port": "asset_port", - "username": "asset_user", - "password": "asset_pass", - "key": "asset_private_key", - "group": "asset_group_name", - ... - }] - kwargs: - "groupName1": {"group_variable1": "value1",...} - "groupName2": {"group_variable1": "value1",...} - """ - self.assets = assets - self.assets_group = group - self.loader = DataLoader() - self.variable_manager = VariableManager() - self.groups = [] - self.inventory = self.gen_inventory() - - def __gen_group(self): - """初始化Ansible Group, 将资产添加到Inventory里面 - :return: None - """ - # 创建Ansible Group. - for asset in self.assets: - g_name = asset.get('group', 'default') - if g_name not in [g.name for g in self.groups]: - group = Group(name=asset.get('group', 'default')) - - self.groups.append(group) - - # 初始化组变量 - for group_name, variables in self.assets_group.iteritems(): - for g in self.groups: - if g.name == group_name: - for v_name, v_value in variables: - g.set_variable(v_name, v_value) - - # 往组里面添加Host - for asset in self.assets: - host = Host(name=asset['name'], port=asset['port']) - host.set_variable('ansible_ssh_host', asset['ip']) - host.set_variable('ansible_ssh_port', asset['port']) - host.set_variable('ansible_ssh_user', asset['username']) - - if asset.get('password'): - host.set_variable('ansible_ssh_pass', asset['password']) - if asset.get('key'): - host.set_variable('ansible_ssh_private_key_file', asset['key']) - - for key, value in asset.iteritems(): - if key not in ["name", "port", "ip", "username", "password", "key"]: - host.set_variable(key, value) - for g in self.groups: - if g.name == asset.get('group', 'default'): - g.add_host(host) - - def validate(self): - pass - - def gen_inventory(self): - self.validate() - i = Inventory(loader=self.loader, variable_manager=self.variable_manager, host_list=[]) - self.__gen_group() - for g in self.groups: - i.add_group(g) - self.variable_manager.set_inventory(i) - return i - - -class PlayBookRunner(object): - """用于执行AnsiblePlaybook的接口.简化Playbook对象的使用 - """ - - def __init__(self, inventory, config, palybook_path, playbook_var, become_pass, verbosity=0): - """ - :param inventory: myinventory实例 - :param config: Config实例 - :param palybook_path: playbook的路径 - :param playbook_var: 执行Playbook时的变量 - :param become_pass: sudo passsword - :param verbosity: --verbosity - """ - - self.options = config - self.options.verbosity = verbosity - self.options.connection = 'smart' - - # 设置verbosity级别, 及命令行的--verbose选项 - self.display = Display() - self.display.verbosity = self.options.verbosity - playbook_executor.verbosity = self.options.verbosity - - # sudo成其他用户的配置 - self.options.become = True - self.options.become_method = 'sudo' - self.options.become_user = 'root' - passwords = {'become_pass': become_pass} - - # 传入playbook的路径,以及执行需要的变量 - inventory.variable_manager.extra_vars = playbook_var - pb_dir = os.path.dirname(__file__) - playbook = "%s/%s" % (pb_dir, palybook_path) - - # 初始化playbook的executor - self.pbex = playbook_executor.PlaybookExecutor( - playbooks=[playbook], - inventory=inventory, - variable_manager=inventory.variable_manager, - loader=inventory.loader, - options=self.options, - passwords=passwords) - - def run(self): - """执行Playbook, 记录执行日志, 处理执行结果. - :return: 对象 - """ - self.pbex.run() - stats = self.pbex._tqm._stats - - # 测试执行是否成功 - run_success = True - hosts = sorted(stats.processed.keys()) - for h in hosts: - t = stats.summarize(h) - if t['unreachable'] > 0 or t['failures'] > 0: - run_success = False - - # TODO: 记录执行日志, 处理执行结果. - - return stats - - -class ADHocRunner(object): - """ADHoc接口 - """ - def __init__(self, inventory, config, become_pass=None, verbosity=0): - """ - :param inventory: myinventory实例 - :param config: Config实例 - :param play_data: - play_data = dict( - name="Ansible Ad-Hoc", - hosts=pattern, - gather_facts=True, - tasks=[dict(action=dict(module='service', args={'name': 'vsftpd', 'state': 'restarted'}), async=async, poll=poll)] - ) - """ - - self.options = config - self.options.verbosity = verbosity - self.options.connection = 'smart' - - # 设置verbosity级别, 及命令行的--verbose选项 - self.display = Display() - self.display.verbosity = self.options.verbosity - playbook_executor.verbosity = self.options.verbosity - - # sudo成其他用户的配置 - self.options.become = True - self.options.become_method = 'sudo' - self.options.become_user = 'root' - self.passwords = {'become_pass': become_pass} - - # 初始化callback插件 - # self.results_callback = ResultCallback() - - # 初始化Play - play_source = { - "name": "Ansible Play", - "hosts": "*", - "gather_facts": "no", - "tasks": [ - dict(action=dict(module='shell', args='id'), register='shell_out'), - dict(action=dict(module='debug', args=dict(msg='{{shell_out.stdout}}'))) - ] - } - - self.play = Play().load(play_source, variable_manager=inventory.variable_manager, loader=inventory.loader) - self.inventory = inventory - - def run(self): - """执行ADHoc 记录日志, 处理结果 - """ - tqm = None - # TODO:日志和结果分析 - try: - tqm = TaskQueueManager( - inventory=self.inventory.inventory, - variable_manager=self.inventory.variable_manager, - loader=self.inventory.loader, - stdout_callback=default_config.DEFAULT_STDOUT_CALLBACK, - options=self.options, - passwords=self.passwords - ) - - result = tqm.run(self.play) - return result - finally: - if tqm: - tqm.cleanup() - - -if __name__ == "__main__": - conf = Config() - assets = [{ - "name": "localhost", - "ip": "localhost", - "port": "22", - "username": "yumaojun", - "password": "xxx", - "key": "asset_private_key", - }] - inv = MyInventory(*assets) - print inv.inventory.get_group('default').get_hosts() - hoc = ADHocRunner(inv, conf, 'xxx') - hoc.run() - diff --git a/apps/ops/api.py b/apps/ops/api.py deleted file mode 100644 index ecbf4289f..000000000 --- a/apps/ops/api.py +++ /dev/null @@ -1,3 +0,0 @@ -# ~*~ coding: utf-8 ~*~ -# - diff --git a/apps/ops/api/__init__.py b/apps/ops/api/__init__.py new file mode 100644 index 000000000..c8b15abe0 --- /dev/null +++ b/apps/ops/api/__init__.py @@ -0,0 +1 @@ +from views import * \ No newline at end of file diff --git a/apps/ops/api/exc.py b/apps/ops/api/exc.py new file mode 100644 index 000000000..81deb805c --- /dev/null +++ b/apps/ops/api/exc.py @@ -0,0 +1,16 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals, print_function + +from rest_framework.exceptions import APIException +from django.utils.translation import ugettext as _ + + +class ServiceUnavailable(APIException): + status_code = default_code = 503 + default_detail = _('Service temporarily unavailable, try again later.') + + +class ServiceNotImplemented(APIException): + status_code = default_code = 501 + default_detail = _('This service maybe implemented in the future, but now not implemented!') + diff --git a/apps/ops/api/permissions.py b/apps/ops/api/permissions.py new file mode 100644 index 000000000..0fc0d0861 --- /dev/null +++ b/apps/ops/api/permissions.py @@ -0,0 +1,19 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals + +from rest_framework import permissions + + +class AdminUserRequired(permissions.BasePermission): + """ + Custom permission to only allow admin user to access the resource. + """ + + def has_object_permission(self, request, view, obj): + # Read permissions are allowed to any request, + # so we'll always allow GET, HEAD or OPTIONS requests. + if request.method in permissions.SAFE_METHODS: + return True + + # Write permissions are only allowed to the admin role. + return request.user.is_staff diff --git a/apps/ops/api/serializers.py b/apps/ops/api/serializers.py new file mode 100644 index 000000000..24abe7e2a --- /dev/null +++ b/apps/ops/api/serializers.py @@ -0,0 +1,65 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals + +from ops.models import * +from rest_framework import serializers + + +class HostAliaSerializer(serializers.ModelSerializer): + + class Meta: + model = HostAlia + + +class CmdAliaSerializer(serializers.ModelSerializer): + + class Meta: + model = CmdAlia + + +class UserAliaSerializer(serializers.ModelSerializer): + + class Meta: + model = UserAlia + + +class RunasAliaSerializer(serializers.ModelSerializer): + + class Meta: + model = RunasAlia + + +class ExtraconfSerializer(serializers.ModelSerializer): + + class Meta: + model = Extra_conf + + +class PrivilegeSerializer(serializers.ModelSerializer): + + class Meta: + model = Privilege + + +class SudoSerializer(serializers.ModelSerializer): + + class Meta: + model = Sudo + + +class CronTableSerializer(serializers.ModelSerializer): + + class Meta: + model = CronTable + +class TaskSerializer(serializers.ModelSerializer): + sub_tasks = serializers.PrimaryKeyRelatedField(many=True, read_only=True) + + class Meta: + model = Task + read_only_fields = ('record',) + +class SubTaskSerializer(serializers.ModelSerializer): + + class Meta: + model = SubTask diff --git a/apps/ops/api/views.py b/apps/ops/api/views.py new file mode 100644 index 000000000..eaacb2b76 --- /dev/null +++ b/apps/ops/api/views.py @@ -0,0 +1,79 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals +from rest_framework import viewsets + +from serializers import * +from permissions import * + +__all__ = ["HostAliaViewSet", + "CmdAliaViewSet", + "UserAliaViewSet", + "RunasAliaViewSet", + "ExtraconfViewSet", + "PrivilegeViewSet", + "SudoViewSet", + "CronTableViewSet", + "TaskViewSet", + "SubTaskViewSet", + ] + + +class HostAliaViewSet(viewsets.ModelViewSet): + queryset = HostAlia.objects.all() + serializer_class = HostAliaSerializer + permission_classes = (AdminUserRequired,) + + +class CmdAliaViewSet(viewsets.ModelViewSet): + queryset = CmdAlia.objects.all() + serializer_class = CmdAliaSerializer + permission_classes = (AdminUserRequired,) + + +class UserAliaViewSet(viewsets.ModelViewSet): + queryset = UserAlia.objects.all() + serializer_class = UserAliaSerializer + permission_classes = (AdminUserRequired,) + + +class RunasAliaViewSet(viewsets.ModelViewSet): + queryset = RunasAlia.objects.all() + serializer_class = RunasAliaSerializer + permission_classes = (AdminUserRequired,) + + +class ExtraconfViewSet(viewsets.ModelViewSet): + queryset = Extra_conf.objects.all() + serializer_class = ExtraconfSerializer + permission_classes = (AdminUserRequired,) + + +class PrivilegeViewSet(viewsets.ModelViewSet): + queryset = Privilege.objects.all() + serializer_class = PrivilegeSerializer + permission_classes = (AdminUserRequired,) + + +class SudoViewSet(viewsets.ModelViewSet): + queryset = Sudo.objects.all() + serializer_class = SudoSerializer + permission_classes = (AdminUserRequired,) + + +class CronTableViewSet(viewsets.ModelViewSet): + queryset = CronTable.objects.all() + serializer_class = CronTableSerializer + permission_classes = (AdminUserRequired,) + +class TaskViewSet(viewsets.ModelViewSet): + queryset = Task.objects.all() + serializer_class = TaskSerializer + permission_classes = (AdminUserRequired,) + +class SubTaskViewSet(viewsets.ModelViewSet): + queryset = SubTask.objects.all() + serializer_class = SubTaskSerializer + permission_classes = (AdminUserRequired,) + + + diff --git a/apps/ops/models.py b/apps/ops/models.py deleted file mode 100644 index bd4b2abe9..000000000 --- a/apps/ops/models.py +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import unicode_literals - -from django.db import models - -# Create your models here. diff --git a/apps/ops/models/__init__.py b/apps/ops/models/__init__.py new file mode 100644 index 000000000..b7bfa1e0d --- /dev/null +++ b/apps/ops/models/__init__.py @@ -0,0 +1,6 @@ +from ansible import * +from cron import * +from sudo import * +from utils import * +from task import * + diff --git a/apps/ops/models/ansible.py b/apps/ops/models/ansible.py new file mode 100644 index 000000000..3cf059644 --- /dev/null +++ b/apps/ops/models/ansible.py @@ -0,0 +1,291 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals, absolute_import + +import logging +import json + +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +__all__ = ["TaskRecord", "AnsiblePlay", "AnsibleTask", "AnsibleHostResult"] + + +logger = logging.getLogger(__name__) + + +class TaskRecord(models.Model): + uuid = models.CharField(max_length=128, verbose_name=_('UUID'), primary_key=True) + name = models.CharField(max_length=128, blank=True, verbose_name=_('Name')) + start = models.DateTimeField(auto_now_add=True, verbose_name=_('Start Time')) + end = models.DateTimeField(blank=True, null=True, verbose_name=_('End Time')) + exit_code = models.IntegerField(default=0, verbose_name=_('Exit Code')) + completed = models.BooleanField(default=False, verbose_name=_('Is Completed')) + hosts = models.TextField(blank=True, null=True, verbose_name=_('Hosts')) + + def __unicode__(self): + return "%s" % self.uuid + + @property + def total_hosts(self): + return self.hosts.split(',') + + @classmethod + def generate_fake(cls, count=20): + from random import seed + from uuid import uuid4 + import forgery_py + + seed() + for i in range(count): + tasker = cls(uuid=str(uuid4()), + name=forgery_py.name.full_name(), + ) + try: + tasker.save() + logger.debug('Generate fake tasker: %s' % tasker.name) + except Exception as e: + print('Error: %s, continue...' % e.message) + continue + + +class AnsiblePlay(models.Model): + tasker = models.ForeignKey(TaskRecord, related_name='plays', blank=True, null=True) + uuid = models.CharField(max_length=128, verbose_name=_('UUID'), primary_key=True) + name = models.CharField(max_length=128, verbose_name=_('Name')) + + def __unicode__(self): + return "%s<%s>" % (self.name, self.uuid) + + def to_dict(self): + return {"uuid": self.uuid, "name": self.name} + + @classmethod + def generate_fake(cls, count=20): + from random import seed, choice + from uuid import uuid4 + import forgery_py + + seed() + for i in range(count): + play = cls(uuid=str(uuid4()), + name=forgery_py.name.full_name(), + ) + try: + play.tasker = choice(TaskRecord.objects.all()) + play.save() + logger.debug('Generate fake play: %s' % play.name) + except Exception as e: + print('Error: %s, continue...' % e.message) + continue + + +class AnsibleTask(models.Model): + play = models.ForeignKey(AnsiblePlay, related_name='tasks', blank=True, null=True) + uuid = models.CharField(max_length=128, verbose_name=_('UUID'), primary_key=True) + name = models.CharField(max_length=128, blank=True, verbose_name=_('Name')) + + def __unicode__(self): + return "%s<%s>" % (self.name, self.uuid) + + def to_dict(self): + return {"uuid": self.uuid, "name": self.name} + + def failed(self): + pass + + def success(self): + pass + + @classmethod + def generate_fake(cls, count=20): + from random import seed, choice + from uuid import uuid4 + import forgery_py + + seed() + for i in range(count): + task = cls(uuid=str(uuid4()), + name=forgery_py.name.full_name(), + ) + try: + task.play = choice(AnsiblePlay.objects.all()) + task.save() + logger.debug('Generate fake play: %s' % task.name) + except Exception as e: + print('Error: %s, continue...' % e.message) + continue + + +class AnsibleHostResult(models.Model): + task = models.ForeignKey(AnsibleTask, related_name='host_results', blank=True, null=True) + name = models.CharField(max_length=128, blank=True, verbose_name=_('Name')) + success = models.TextField(blank=True, verbose_name=_('Success')) + skipped = models.TextField(blank=True, verbose_name=_('Skipped')) + failed = models.TextField(blank=True, verbose_name=_('Failed')) + unreachable = models.TextField(blank=True, verbose_name=_('Unreachable')) + no_host = models.TextField(blank=True, verbose_name=_('NoHost')) + + def __unicode__(self): + return "%s %s<%s>" % (self.name, str(self.is_success), self.task.uuid) + + @property + def is_failed(self): + if self.failed or self.unreachable or self.no_host: + return True + return False + + @property + def is_success(self): + return not self.is_failed + + @property + def _success_data(self): + if self.success: + return json.loads(self.success) + elif self.skipped: + return json.loads(self.skipped) + + @property + def _failed_data(self): + if self.failed: + return json.loads(self.failed) + elif self.unreachable: + return json.loads(self.unreachable) + elif self.no_host: + return {"msg": self.no_host} + + @property + def failed_msg(self): + return self._failed_data.get("msg") + + @staticmethod + def __filter_disk(ansible_devices, exclude_devices): + """ + 过滤磁盘设备,丢弃掉不需要的设备 + + :param ansible_devices: 对应的facts字段 + :param exclude_devices: 一个需要被丢弃的设备,匹配规则是startwith, 比如需要丢弃sr0子类的 ['sr'] + :return: 过滤获取的结果 + """ + for start_str in exclude_devices: + for key in ansible_devices.keys(): + if key.startswith(start_str): + ansible_devices.pop(key) + return ansible_devices + + @staticmethod + def __filter_interface(ansible_interfaces, exclude_interface): + """ + 过滤网卡设备,丢弃掉不需要的网卡, 比如lo + + :param ansible_interface: 对应的facts字段 + :param exclude_interface: 一个需要被丢弃的设备,匹配规则是startwith, 比如需要丢弃lo子类的 ['lo'] + :return: 过滤获取的结果 + """ + for interface in ansible_interfaces: + for start_str in exclude_interface: + if interface.startswith(start_str): + i = ansible_interfaces.index(interface) + ansible_interfaces.pop(i) + return ansible_interfaces + + @staticmethod + def __gather_interface(facts, interfaces): + """ + 收集所有interface的具体信息 + + :param facts: ansible faces + :param interfaces: 需要收集的intreface列表 + :return: interface的详情 + """ + result = {} + for key in interfaces: + gather_key = "ansible_" + key + if gather_key in facts.keys(): + result[key] = facts.get(gather_key) + return result + + def __deal_setup(self): + """ + 处理ansible setup模块收集到的数据,提取资产需要的部分 + + :return: {"msg": , "data": }, 注意msg是异常信息, 有msg时 data为None + """ + result = self._success_data + module_name = result['invocation'].get('module_name') if result.get('invocation') else None + if module_name is not None: + if module_name != "setup": + return {"msg": "the property only for ansible setup module result!, can't support other module", "data":None} + else: + data = {} + facts =result.get('ansible_facts') + interfaces = self.__filter_interface(facts.get('ansible_interfaces'), ['lo']) + + cpu_describe = "%s %s" % (facts.get('ansible_processor')[0], facts.get('ansible_processor')[1]) if len(facts.get('ansible_processor')) >= 2 else "" + + data['sn'] = facts.get('ansible_product_serial') + data['env'] = facts.get('ansible_env') + data['os'] = "%s %s(%s)" % (facts.get('ansible_distribution'), + facts.get('ansible_distribution_version'), + facts.get('ansible_distribution_release')) + data['mem'] = facts.get('ansible_memtotal_mb') + data['cpu'] = "%s %d核" % (cpu_describe, facts.get('ansible_processor_count')) + data['disk'] = self.__filter_disk(facts.get('ansible_devices'), ['sr']) + data['interface'] = self.__gather_interface(facts, interfaces) + return {"msg": None, "data": data} + else: + return {"msg": "there result isn't ansible setup module result! can't process this data format", "data": None} + + @property + def deal_setup(self): + try: + return self.__deal_setup() + except Exception as e: + return {"msg": "deal with setup data failed, %s" % e.message, "data": None} + + def __deal_ping(self): + """ + 处理ansible ping模块收集到的数据 + + :return: {"msg": , "data": {"success": }}, 注意msg是异常信息, 有msg时 data为None + """ + result = self._success_data + module_name = result['invocation'].get('module_name') if result.get('invocation') else None + if module_name is not None: + if module_name != "ping": + return {"msg": "the property only for ansible setup module result!, can't support other module", "data":None} + else: + ping = True if result.get('ping') == "pong" else False + + return {"msg": None, "data": {"success": ping}} + else: + return {"msg": "there isn't module_name field! can't process this data format", "data": None} + + @property + def deal_ping(self): + try: + return self.__deal_ping() + except Exception as e: + return {"msg": "deal with ping data failed, %s" % e.message, "data": None} + + @classmethod + def generate_fake(cls, count=20): + from random import seed, choice + import forgery_py + + seed() + for i in range(count): + result = cls(name=forgery_py.name.full_name(), + success=forgery_py.lorem_ipsum.sentence(), + failed=forgery_py.lorem_ipsum.sentence(), + skipped=forgery_py.lorem_ipsum.sentence(), + unreachable=forgery_py.lorem_ipsum.sentence(), + no_host=forgery_py.lorem_ipsum.sentence(), + ) + try: + result.task = choice(AnsibleTask.objects.all()) + result.save() + logger.debug('Generate fake HostResult: %s' % result.name) + except Exception as e: + print('Error: %s, continue...' % e.message) + continue diff --git a/apps/ops/models/cron.py b/apps/ops/models/cron.py new file mode 100644 index 000000000..766c46ece --- /dev/null +++ b/apps/ops/models/cron.py @@ -0,0 +1,61 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals, absolute_import + +import logging + +from django.db import models +from assets.models import Asset +from django.utils.translation import ugettext_lazy as _ + +logger = logging.getLogger(__name__) + +__all__ = ["CronTable"] + + +class CronTable(models.Model): + name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Name'), + help_text=_("Description of a crontab entry")) + month = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Month'), + help_text=_("Month of the year the job should run ( 1-12, *, */2, etc )")) + weekday = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('WeekDay'), + help_text=_("Day of the week that the job should run" + " ( 0-6 for Sunday-Saturday, *, etc )")) + day = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Day'), + help_text=_("Day of the month the job should run ( 1-31, *, */2, etc )")) + hour = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hour'), + help_text=_("Hour when the job should run ( 0-23, *, */2, etc )")) + minute = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Minute'), + help_text=_("Minute when the job should run ( 0-59, *, */2, etc )")) + job = models.CharField(max_length=4096, blank=True, null=True, verbose_name=_('Job'), + help_text=_("The command to execute or, if env is set, the value of " + "environment variable. Required if state=present.")) + user = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('User'), + help_text=_("The specific user whose crontab should be modified.")) + asset = models.ForeignKey(Asset, null=True, blank=True, related_name='crontables') + + @property + def describe(self): + return "http://docs.ansible.com/ansible/cron_module.html" + + @classmethod + def generate_fake(cls, count=20): + from random import seed, choice + import forgery_py + + seed() + for i in range(count): + cron = cls(name=forgery_py.name.full_name(), + month=str(choice(range(1,13))), + weekday=str(choice(range(0,7))), + day=str(choice(range(1,32))), + hour=str(choice(range(0,24))), + minute=str(choice(range(0,60))), + job=forgery_py.lorem_ipsum.sentence(), + user=forgery_py.name.first_name(), + ) + try: + cron.save() + logger.debug('Generate fake cron: %s' % cron.name) + except Exception as e: + print('Error: %s, continue...' % e.message) + continue \ No newline at end of file diff --git a/apps/ops/models/sudo.py b/apps/ops/models/sudo.py new file mode 100644 index 000000000..13713064c --- /dev/null +++ b/apps/ops/models/sudo.py @@ -0,0 +1,321 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals, absolute_import + +import logging + +from jinja2 import Template +from django.db import models +from django.utils.timezone import now +from assets.models import Asset, AssetGroup +from django.utils.translation import ugettext_lazy as _ + +logger = logging.getLogger(__name__) + +__all__ = ["HostAlia", "UserAlia", "CmdAlia", "RunasAlia", "Privilege", "Extra_conf", "Sudo"] + + +class HostAlia(models.Model): + name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Host_Alias')) + host_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) + + def __unicode__(self): + return self.name + + @classmethod + def generate_fake(cls, count=20): + from random import seed + import forgery_py + + seed() + for i in range(count): + hostA = cls(name=forgery_py.name.full_name(), + host_items=forgery_py.lorem_ipsum.sentence(), + ) + try: + hostA.save() + logger.debug('Generate fake host alia: %s' % hostA.name) + except Exception as e: + print('Error: %s, continue...' % e.message) + continue + + +class UserAlia(models.Model): + name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('User_Alias')) + user_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) + + def __unicode__(self): + return self.name + + @classmethod + def generate_fake(cls, count=20): + from random import seed + import forgery_py + + seed() + for i in range(count): + userA = cls(name=forgery_py.name.full_name(), + user_items=forgery_py.lorem_ipsum.sentence(), + ) + try: + userA.save() + logger.debug('Generate fake host alia: %s' % userA.name) + except Exception as e: + print('Error: %s, continue...' % e.message) + continue + + +class CmdAlia(models.Model): + name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Command_Alias')) + cmd_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) + + def __unicode__(self): + return self.name + + @classmethod + def generate_fake(cls, count=20): + from random import seed + import forgery_py + + seed() + for i in range(count): + cmdA = cls(name=forgery_py.name.full_name(), + cmd_items=forgery_py.lorem_ipsum.sentence(), + ) + try: + cmdA.save() + logger.debug('Generate fake command alia: %s' % cmdA.name) + except Exception as e: + print('Error: %s, continue...' % e.message) + continue + + +class RunasAlia(models.Model): + name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Runas_Alias')) + runas_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) + + def __unicode__(self): + return self.name + + @classmethod + def generate_fake(cls, count=20): + from random import seed + import forgery_py + + seed() + for i in range(count): + runas = cls(name=forgery_py.name.full_name(), + runas_items=forgery_py.lorem_ipsum.sentence(), + ) + try: + runas.save() + logger.debug('Generate fake RunAs alia: %s' % runas.name) + except Exception as e: + print('Error: %s, continue...' % e.message) + continue + + +class Privilege(models.Model): + name = models.CharField(max_length=128, unique=True, verbose_name=_('Name')) + user = models.ForeignKey(UserAlia, blank=True, null=True, related_name='privileges') + host = models.ForeignKey(HostAlia, blank=True, null=True, related_name='privileges') + runas = models.ForeignKey(RunasAlia, blank=True, null=True, related_name='privileges') + command = models.ForeignKey(CmdAlia, blank=True, null=True, related_name='privileges') + nopassword = models.BooleanField(default=True, verbose_name=_('Is_NoPassword')) + comment = models.TextField(blank=True, null=True, verbose_name=_('Comment')) + + def __unicode__(self): + return "[%s %s %s %s %s]" % (self.user.name, + self.host.name, + self.runas.name, + self.command.name, + self.nopassword) + + def to_tuple(self): + return self.user.name, self.host.name, self.runas.name, self.command.name, self.nopassword + + @classmethod + def generate_fake(cls, count=20): + from random import seed, choice + import forgery_py + + seed() + for i in range(count): + pri = cls(name=forgery_py.name.full_name(), + comment=forgery_py.lorem_ipsum.sentence(), + ) + try: + pri.user = choice(UserAlia.objects.all()) + pri.host = choice(HostAlia.objects.all()) + pri.runas = choice(RunasAlia.objects.all()) + pri.command = choice(CmdAlia.objects.all()) + pri.save() + logger.debug('Generate fake privileges: %s' % pri.name) + except Exception as e: + print('Error: %s, continue...' % e.message) + continue + + +class Extra_conf(models.Model): + line = models.TextField(blank=True, null=True, verbose_name=_('Extra_Item'), + help_text=_('The extra sudo config line.')) + + def __unicode__(self): + return self.line + + +class Sudo(models.Model): + """ + Sudo配置文件对象, 用于配置sudo的配置文件 + + :param extra_lines: [, ,...] + :param privileges: [(user, host, runas, command, nopassword),] + """ + + name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'), + help_text=_('Name for this sudo')) + created_time = models.DateTimeField(verbose_name=_('Created Time'), auto_created=True, + help_text=_('The create time of this sudo')) + modify_time = models.DateTimeField(auto_now=True, verbose_name=_('Modify Time'), + help_text=_('The recent modify time of this sudo')) + assets = models.ManyToManyField(Asset, blank=True, related_name='sudos') + asset_groups = models.ManyToManyField(AssetGroup, blank=True, related_name='sudos') + extra_lines = models.ManyToManyField(Extra_conf, related_name='sudos', blank=True) + privilege_items = models.ManyToManyField(Privilege, related_name='sudos', blank=True) + + @property + def all_assets(self): + assets = list(self.assets.all()) + for group in self.asset_groups.all(): + for asset in group.assets.all(): + if asset not in assets: + assets.append(asset) + return assets + + @property + def users(self): + return {privilege.user.name: privilege.user.user_items.split(',') for privilege in self.privilege_items.all()} + + @property + def commands(self): + return {privilege.command.name: privilege.command.cmd_items.split(',') for privilege in self.privilege_items.all()} + + @property + def hosts(self): + return {privilege.host.name: privilege.host.host_items.split(',') for privilege in self.privilege_items.all()} + + @property + def runas(self): + return {privilege.runas.name: privilege.runas.runas_items.split(',') for privilege in self.privilege_items.all()} + + @property + def extras(self): + return [extra.line for extra in self.extra_lines.all()] + + @property + def privileges(self): + return [privilege.to_tuple() for privilege in self.privilege_items.all()] + + @property + def content(self): + template = Template(self.__sudoers_jinja2_tmp__) + context = {"User_Alias": self.users, + "Cmnd_Alias": self.commands, + "Host_Alias": self.hosts, + "Runas_Alias": self.runas, + "Extra_Lines": self.extras, + "Privileges": self.privileges} + return template.render(context) + + @property + def __sudoers_jinja2_tmp__(self): + return """# management by JumpServer +# This file MUST be edited with the 'visudo' command as root. +# +# Please consider adding local content in /etc/sudoers.d/ instead of +# directly modifying this file. +# +# See the man page for details on how to write a sudoers file. +# +Defaults env_reset +Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + +# JumpServer Generate Other Configure is here +{% if Extra_Lines -%} +{% for line in Extra_Lines -%} +{{ line }} +{% endfor %} +{%- endif %} + +# Host alias specification +{% if Host_Alias -%} +{% for flag, items in Host_Alias.iteritems() -%} +Host_Alias {{ flag }} = {{ items|join(', ') }} +{% endfor %} +{%- endif %} + +# User alias specification +{% if User_Alias -%} +{% for flag, items in User_Alias.iteritems() -%} +User_Alias {{ flag }} = {{ items|join(', ') }} +{% endfor %} +{%- endif %} + + +# Cmnd alias specification +{% if Cmnd_Alias -%} +{% for flag, items in Cmnd_Alias.iteritems() -%} +Cmnd_Alias {{ flag }} = {{ items|join(', ') }} +{% endfor %} +{%- endif %} + +# Run as alias specification +{% if Runas_Alias -%} +{% for flag, items in Runas_Alias.iteritems() -%} +Runas_Alias {{ flag }} = {{ items|join(', ') }} +{% endfor %} +{%- endif %} + +# User privilege specification +root ALL=(ALL:ALL) ALL + +# JumpServer Generate User privilege is here. +# Note privileges is a tuple list like [(user, host, runas, command, nopassword),] +{% if Privileges -%} +{% for User_Flag, Host_Flag, Runas_Flag, Command_Flag, NopassWord in Privileges -%} +{% if NopassWord -%} +{{ User_Flag }} {{ Host_Flag }}=({{ Runas_Flag }}) NOPASSWD: {{ Command_Flag }} +{%- else -%} +{{ User_Flag }} {{ Host_Flag }}=({{ Runas_Flag }}) {{ Command_Flag }} +{%- endif %} +{% endfor %} +{%- endif %} + +# Members of the admin group may gain root privileges +%admin ALL=(ALL) ALL + +# Allow members of group sudo to execute any command +%sudo ALL=(ALL:ALL) ALL + +# See sudoers(5) for more information on "#include" directives: + +#includedir /etc/sudoers.d +""" + + @classmethod + def generate_fake(cls, count=20): + from random import seed, choice + import forgery_py + + seed() + for i in range(count): + sudo = cls(name=forgery_py.name.full_name(), + created_time=now() + ) + try: + sudo.save() + sudo.privilege_items = [choice(Privilege.objects.all())] + sudo.save() + logger.debug('Generate fake cron: %s' % sudo.name) + except Exception as e: + print('Error: %s, continue...' % e.message) + continue \ No newline at end of file diff --git a/apps/ops/models/task.py b/apps/ops/models/task.py new file mode 100644 index 000000000..fc35e0665 --- /dev/null +++ b/apps/ops/models/task.py @@ -0,0 +1,58 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals, absolute_import + +import logging + +from uuid import uuid4 +from assets.models import Asset +from ops.models import TaskRecord +from ops.utils.ansible_api import ADHocRunner, Config + +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +__all__ = ["Task", "SubTask"] + + +logger = logging.getLogger(__name__) + + +class Task(models.Model): + record = models.OneToOneField(TaskRecord) + name = models.CharField(max_length=128, blank=True, verbose_name=_('Name')) + is_gather_facts = models.BooleanField(default=False,verbose_name=_('Is Gather Ansible Facts')) + assets = models.ManyToManyField(Asset, related_name='tasks') + + def __unicode__(self): + return "%s" % self.name + + @property + def ansible_assets(self): + return [] + + def run(self): + conf = Config() + gather_facts = "yes" if self.is_gather_facts else "no" + play_source = { + "name": "Ansible Play", + "hosts": "default", + "gather_facts": gather_facts, + "tasks": [ + dict(action=dict(module='ping')), + ] + } + hoc = ADHocRunner(conf, play_source, *self.ansible_assets) + uuid = "tasker-" + uuid4().hex + ext_code, result = hoc.run("test_task", uuid) + print(ext_code) + print(result) + + +class SubTask(models.Model): + task = models.ForeignKey(Task, related_name='sub_tasks', verbose_name=_('Ansible Task')) + module_name = models.CharField(max_length=128, verbose_name=_('Ansible Module Name')) + module_args = models.CharField(max_length=512, blank=True, verbose_name=_("Ansible Module Args")) + register = models.CharField(max_length=128, blank=True, verbose_name=_('Ansible Task Register')) + + def __unicode__(self): + return "%s %s" % (self.module_name, self.module_args) \ No newline at end of file diff --git a/apps/ops/models/utils.py b/apps/ops/models/utils.py new file mode 100644 index 000000000..17c7cbab1 --- /dev/null +++ b/apps/ops/models/utils.py @@ -0,0 +1,14 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals + +from ansible import * +from cron import * +from sudo import * + +__all__ = ["generate_fake"] + + +def generate_fake(): + for cls in (TaskRecord, AnsiblePlay, AnsibleTask, AnsibleHostResult, CronTable, + HostAlia, UserAlia, CmdAlia, RunasAlia, Privilege, Sudo): + cls.generate_fake() \ No newline at end of file diff --git a/apps/ops/run_tasks.py b/apps/ops/run_tasks.py deleted file mode 100644 index 4c3115fac..000000000 --- a/apps/ops/run_tasks.py +++ /dev/null @@ -1,9 +0,0 @@ -from .tasks import longtime_add -import time - -result = longtime_add.delay(1,2) -print 'Task finished? ', result.ready() -print 'Task result: ', result.result -time.sleep(10) -print 'Task finished? ', result.ready() -print 'Task result: ', result.result \ No newline at end of file diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py deleted file mode 100644 index ae4b23f01..000000000 --- a/apps/ops/tasks.py +++ /dev/null @@ -1,19 +0,0 @@ -from __future__ import absolute_import -import time - -from celery import shared_task -from common import celery_app - - -@shared_task -def longtime_add(x, y): - print 'long time task begins' - # sleep 5 seconds - time.sleep(5) - print 'long time task finished' - return x + y - - -@celery_app.task(name='hello-world') -def hello(): - print('hello world!') diff --git a/apps/ops/tasks/__init__.py b/apps/ops/tasks/__init__.py new file mode 100644 index 000000000..6fe598964 --- /dev/null +++ b/apps/ops/tasks/__init__.py @@ -0,0 +1 @@ +from taskers import * \ No newline at end of file diff --git a/apps/ops/tasks/_celery_tasks.py b/apps/ops/tasks/_celery_tasks.py new file mode 100644 index 000000000..13fe8d0c1 --- /dev/null +++ b/apps/ops/tasks/_celery_tasks.py @@ -0,0 +1,48 @@ +from __future__ import absolute_import, unicode_literals + +from celery import shared_task + +from common import celery_app +from ops.utils.ansible_api import Config, ADHocRunner + + +@shared_task(name="get_asset_hardware_info") +def get_asset_hardware_info(task_name, task_uuid, *assets): + conf = Config() + play_source = { + "name": "Get host hardware information", + "hosts": "default", + "gather_facts": "no", + "tasks": [ + dict(action=dict(module='setup')) + ] + } + hoc = ADHocRunner(conf, play_source, *assets) + ext_code, result = hoc.run(task_name, task_uuid) + return ext_code, result + + +@shared_task(name="asset_test_ping_check") +def asset_test_ping_check(task_name, task_uuid, *assets): + conf = Config() + play_source = { + "name": "Test host connection use ping", + "hosts": "default", + "gather_facts": "no", + "tasks": [ + dict(action=dict(module='ping')) + ] + } + hoc = ADHocRunner(conf, play_source, *assets) + ext_code, result = hoc.run(task_name, task_uuid) + return ext_code, result + + +@shared_task(name="add_user_to_assert") +def add_user_to_asset(): + pass + + +@celery_app.task(name='hello-world') +def hello(): + print('hello world!') diff --git a/apps/ops/tasks/taskers.py b/apps/ops/tasks/taskers.py new file mode 100644 index 000000000..3a724b049 --- /dev/null +++ b/apps/ops/tasks/taskers.py @@ -0,0 +1,126 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals + +from ops.tasks import _celery_tasks + +from ops.models import TaskRecord +from uuid import uuid1 +from celery.result import AsyncResult + +__all__ = ["get_result", + "start_get_hardware_info", + "start_ping_test", + "get_hardware_info", + "get_ping_test"] + + +def get_result(task_id): + result = AsyncResult(task_id) + if result.ready(): + return {"Completed": True, "data": result.get()} + else: + return {"Completed": False, "data": None} + + +def __get_result_by_tasker_id(tasker_uuid, deal_method): + tasker = TaskRecord.objects.get(uuid=tasker_uuid) + total = tasker.total_hosts + total_len = len(total) + host_results = [] + + # 存储数据 + for play in tasker.plays.all(): + for t in play.tasks.all(): + task = {'name': t.name, 'uuid': t.uuid, 'percentage': 0, 'completed': {'success': {}, 'failed': {}}} + completed = [] + count = 0 + for h in t.host_results.all(): + completed.append(h.name) + count += 1 + if h.is_success: + result = getattr(h, deal_method) + if result.get('msg') is None: + task['completed']['success'][h.name] = result.get('data') + else: + task['completed']['failed'][h.name] = result.get('msg') + else: + task['completed']['failed'][h.name] = h.failed_msg + + # 计算进度 + task['percentage'] = float(count * 100 / total_len) + task['waited'] = list(set(total) - set(completed)) + + host_results.append(task) + + return host_results + + +def start_get_hardware_info(*assets): + name = "Get host hardware information" + uuid = "tasker-" + uuid1().hex + _celery_tasks.get_asset_hardware_info.delay(name, uuid, *assets) + return uuid + + +def __get_hardware_info(tasker_uuid): + return __get_result_by_tasker_id(tasker_uuid, 'deal_setup') + + +def get_hardware_info(tasker_uuid): + """ + + :param assets: 资产列表 + :return: 返回数据结构样列 + {u'data': [{u'completed': { + u'failed': {u'192.168.232.135': u'Authentication failure.'}, + u'success': {u'192.168.1.119': {u'cpu': u'GenuineIntel Intel Xeon E312xx (Sandy Bridge) 6\u6838', + u'disk': {: }, + u'env': {: }, + u'interface': {: }, + u'mem': 3951, + u'os': u'Ubuntu 16.04(xenial)', + u'sn': u'NA'}}}, + u'name': u'', + u'percentage': 100.0, + u'uuid': u'87cfedfe-ba55-44ff-bc43-e7e73b869ca1', + u'waited': []} + ], + u'msg': None} + """ + try: + return {"msg": None, "data": __get_hardware_info(tasker_uuid)} + except Exception as e: + return {"msg": "query data failed!, %s" % e.message, "data": None} + + +def start_ping_test(*assets): + name = "Test host connection" + uuid = "tasker-" + uuid1().hex + _celery_tasks.asset_test_ping_check.delay(name, uuid, *assets) + return uuid + + +def __get_ping_test(tasker_uuid): + return __get_result_by_tasker_id(tasker_uuid, 'deal_ping') + + +def get_ping_test(tasker_uuid): + """ + + :param assets: 资产列表 + :return: 返回数据结构样列 + {u'data': [{u'completed': { + u'failed': {u'192.168.232.135': u'Authentication failure.'}, + u'success': {u'192.168.1.119': {u'success': True}}}, + u'name': u'', + u'percentage': 100.0, + u'uuid': u'3e6e0d3b-bee0-4383-b19e-bec6ba55d346', + u'waited': []} + ], + u'msg': None} + """ + try: + return {"msg": None, "data": __get_ping_test(tasker_uuid)} + except Exception as e: + return {"msg": "query data failed!, %s" % e.message, "data": None} + diff --git a/apps/ops/templates/cron/_cron.html b/apps/ops/templates/cron/_cron.html new file mode 100644 index 000000000..59d03e5ee --- /dev/null +++ b/apps/ops/templates/cron/_cron.html @@ -0,0 +1,97 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load static %} +{% load bootstrap %} +{% block custom_head_css_js %} + + + +{% endblock %} + +{% block content %} +
+
+
+
+
+
{% block user_template_title %}{% trans 'Create user' %}{% endblock %}
+ +
+
+
+ {% csrf_token %} +

{% trans 'Account' %}

+ {% block username %} {% endblock %} + {{ form.email|bootstrap_horizontal }} + {{ form.name|bootstrap_horizontal }} + {{ form.groups|bootstrap_horizontal }} + +
+ {% block password %} {% endblock %} + +
+

{% trans 'Security and Role' %}

+ {{ form.role|bootstrap_horizontal }} +
+ +
+
+ + +
+ {{ form.date_expired.errors }} +
+
+{# {{ form.date_expired|bootstrap_horizontal }}#} +
+ +
+ {{ form.enable_otp }} +
+
+
+

{% trans 'Profile' %}

+ {{ form.phone|bootstrap_horizontal }} + {{ form.wechat|bootstrap_horizontal }} + {{ form.comment|bootstrap_horizontal }} +
+
+
+ + +
+
+
+
+
+
+
+
+{% endblock %} +{% block custom_foot_js %} + + +{% endblock %} diff --git a/apps/ops/templates/cron/create.html b/apps/ops/templates/cron/create.html new file mode 100644 index 000000000..291a5656a --- /dev/null +++ b/apps/ops/templates/cron/create.html @@ -0,0 +1,16 @@ +{% extends 'cron/_cron.html' %} +{% load i18n %} +{% load bootstrap %} +{% block user_template_title %}{% trans "Create user" %}{% endblock %} +{% block username %} + {{ form.username|bootstrap_horizontal }} +{% endblock %} +{% block password %} +

{% trans 'Password' %}

+
+ +
+ {% trans 'Reset link will be generated and sent to the user. ' %} +
+
+{% endblock %} \ No newline at end of file diff --git a/apps/ops/templates/cron/detail.html b/apps/ops/templates/cron/detail.html new file mode 100644 index 000000000..8089f917b --- /dev/null +++ b/apps/ops/templates/cron/detail.html @@ -0,0 +1,282 @@ +{% extends 'base.html' %} +{% load common_tags %} +{% load users_tags %} +{% load static %} +{% load i18n %} + +{% block custom_head_css_js %} + + + + +{% endblock %} +{% block content %} +
+
+
+
+ +
+
+
+
+ {{ cron.name }} +
+ + + + + + + + + + +
+
+
+ + + + + + + +
{% trans 'Name' %}:{{ cron.name }}
+
+
+
+
+
+
+ {% trans 'Quick modify' %} +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
{% trans 'Active' %}: +
+
+ + +
+
+
{% trans 'Enable OTP' %}: +
+
+ + +
+
+
{% trans 'Reset password' %}: + + + +
{% trans 'Reset ssh key' %}: + + + +
{% trans 'Update ssh key' %}: + + + +
+
+
+ +
+
+ {% trans 'User group' %} +
+
+ + + + + + + + + + + + {% for group in user_object.groups.all %} + + + + + {% endfor %} + +
+ +
+ +
{{ group.name }} + +
+
+
+
+
+
+
+
+
+ {% include 'users/_user_update_pk_modal.html' %} +{% endblock %} +{% block custom_foot_js %} + +{% endblock %} diff --git a/apps/ops/templates/cron/list.html b/apps/ops/templates/cron/list.html new file mode 100644 index 000000000..d1a2c78bc --- /dev/null +++ b/apps/ops/templates/cron/list.html @@ -0,0 +1,231 @@ +{% extends '_base_list.html' %} +{% load i18n static %} +{% block table_search %} +{% endblock %} +{% block table_container %} + +{##} + + + + + + + + + + + + + +
+{#
#} + +
{% trans 'Name' %}{% trans 'Time(minute-hour-day-month-weekday)' %}{% trans 'Job' %}{% trans 'User' %}{% trans 'Action' %}
+
+
+ +
+ +
+
+
+{% include "users/_user_bulk_update_modal.html" %} +{#{% include "users/_user_import_modal.html" %}#} +{% endblock %} +{% block content_bottom_left %}{% endblock %} +{% block custom_foot_js %} + + +{% endblock %} + diff --git a/apps/ops/templates/cron/update.html b/apps/ops/templates/cron/update.html new file mode 100644 index 000000000..f033e1ed8 --- /dev/null +++ b/apps/ops/templates/cron/update.html @@ -0,0 +1,20 @@ +{% extends 'cron/_cron.html' %} +{% load i18n %} +{% block user_template_title %}{% trans "Update user" %}{% endblock %} +{% block username %} +
+ +
+ +
+
+{% endblock %} +{% block password %} +

{% trans 'Password' %}

+
+ +
+ +
+
+{% endblock %} diff --git a/apps/ops/templates/sudo/_sudo.html b/apps/ops/templates/sudo/_sudo.html new file mode 100644 index 000000000..59d03e5ee --- /dev/null +++ b/apps/ops/templates/sudo/_sudo.html @@ -0,0 +1,97 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load static %} +{% load bootstrap %} +{% block custom_head_css_js %} + + + +{% endblock %} + +{% block content %} +
+
+
+
+
+
{% block user_template_title %}{% trans 'Create user' %}{% endblock %}
+ +
+
+
+ {% csrf_token %} +

{% trans 'Account' %}

+ {% block username %} {% endblock %} + {{ form.email|bootstrap_horizontal }} + {{ form.name|bootstrap_horizontal }} + {{ form.groups|bootstrap_horizontal }} + +
+ {% block password %} {% endblock %} + +
+

{% trans 'Security and Role' %}

+ {{ form.role|bootstrap_horizontal }} +
+ +
+
+ + +
+ {{ form.date_expired.errors }} +
+
+{# {{ form.date_expired|bootstrap_horizontal }}#} +
+ +
+ {{ form.enable_otp }} +
+
+
+

{% trans 'Profile' %}

+ {{ form.phone|bootstrap_horizontal }} + {{ form.wechat|bootstrap_horizontal }} + {{ form.comment|bootstrap_horizontal }} +
+
+
+ + +
+
+
+
+
+
+
+
+{% endblock %} +{% block custom_foot_js %} + + +{% endblock %} diff --git a/apps/ops/templates/sudo/create.html b/apps/ops/templates/sudo/create.html new file mode 100644 index 000000000..19727e102 --- /dev/null +++ b/apps/ops/templates/sudo/create.html @@ -0,0 +1,16 @@ +{% extends 'sudo/_sudo.html' %} +{% load i18n %} +{% load bootstrap %} +{% block user_template_title %}{% trans "Create user" %}{% endblock %} +{% block username %} + {{ form.username|bootstrap_horizontal }} +{% endblock %} +{% block password %} +

{% trans 'Password' %}

+
+ +
+ {% trans 'Reset link will be generated and sent to the user. ' %} +
+
+{% endblock %} \ No newline at end of file diff --git a/apps/ops/templates/sudo/detail.html b/apps/ops/templates/sudo/detail.html new file mode 100644 index 000000000..95faa39e5 --- /dev/null +++ b/apps/ops/templates/sudo/detail.html @@ -0,0 +1,282 @@ +{% extends 'base.html' %} +{% load common_tags %} +{% load users_tags %} +{% load static %} +{% load i18n %} + +{% block custom_head_css_js %} + + + + +{% endblock %} +{% block content %} +
+
+
+
+ +
+
+
+
+ {{ sudo.name }} +
+ + + + + + + + + + +
+
+
+ + + + + + + +
{% trans 'Name' %}:{{ sudo.name }}
+
+
+
+
+
+
+ {% trans 'Quick modify' %} +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
{% trans 'Active' %}: +
+
+ + +
+
+
{% trans 'Enable OTP' %}: +
+
+ + +
+
+
{% trans 'Reset password' %}: + + + +
{% trans 'Reset ssh key' %}: + + + +
{% trans 'Update ssh key' %}: + + + +
+
+
+ +
+
+ {% trans 'User group' %} +
+
+ + + + + + + + + + + + {% for group in user_object.groups.all %} + + + + + {% endfor %} + +
+ +
+ +
{{ group.name }} + +
+
+
+
+
+
+
+
+
+ {% include 'users/_user_update_pk_modal.html' %} +{% endblock %} +{% block custom_foot_js %} + +{% endblock %} diff --git a/apps/ops/templates/sudo/list.html b/apps/ops/templates/sudo/list.html new file mode 100644 index 000000000..8da993251 --- /dev/null +++ b/apps/ops/templates/sudo/list.html @@ -0,0 +1,226 @@ +{% extends '_base_list.html' %} +{% load i18n static %} +{% block table_search %} +{% endblock %} +{% block table_container %} + +{##} + + + + + + + + + + + + + +
+ + {% trans 'Name' %}{% trans 'Privileges' %}{% trans 'Extra Lines' %}{% trans 'Action' %}
+
+
+ +
+ +
+
+
+{#{% include "users/_user_bulk_update_modal.html" %}#} +{#{% include "users/_user_import_modal.html" %}#} +{% endblock %} +{% block content_bottom_left %}{% endblock %} +{% block custom_foot_js %} + + +{% endblock %} + diff --git a/apps/ops/templates/sudo/update.html b/apps/ops/templates/sudo/update.html new file mode 100644 index 000000000..3172d23f4 --- /dev/null +++ b/apps/ops/templates/sudo/update.html @@ -0,0 +1,20 @@ +{% extends 'sudo/_sudo.html' %} +{% load i18n %} +{% block user_template_title %}{% trans "Update user" %}{% endblock %} +{% block username %} +
+ +
+ +
+
+{% endblock %} +{% block password %} +

{% trans 'Password' %}

+
+ +
+ +
+
+{% endblock %} diff --git a/apps/ops/templates/task/_task.html b/apps/ops/templates/task/_task.html new file mode 100644 index 000000000..59d03e5ee --- /dev/null +++ b/apps/ops/templates/task/_task.html @@ -0,0 +1,97 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load static %} +{% load bootstrap %} +{% block custom_head_css_js %} + + + +{% endblock %} + +{% block content %} +
+
+
+
+
+
{% block user_template_title %}{% trans 'Create user' %}{% endblock %}
+ +
+
+
+ {% csrf_token %} +

{% trans 'Account' %}

+ {% block username %} {% endblock %} + {{ form.email|bootstrap_horizontal }} + {{ form.name|bootstrap_horizontal }} + {{ form.groups|bootstrap_horizontal }} + +
+ {% block password %} {% endblock %} + +
+

{% trans 'Security and Role' %}

+ {{ form.role|bootstrap_horizontal }} +
+ +
+
+ + +
+ {{ form.date_expired.errors }} +
+
+{# {{ form.date_expired|bootstrap_horizontal }}#} +
+ +
+ {{ form.enable_otp }} +
+
+
+

{% trans 'Profile' %}

+ {{ form.phone|bootstrap_horizontal }} + {{ form.wechat|bootstrap_horizontal }} + {{ form.comment|bootstrap_horizontal }} +
+
+
+ + +
+
+
+
+
+
+
+
+{% endblock %} +{% block custom_foot_js %} + + +{% endblock %} diff --git a/apps/ops/templates/task/create.html b/apps/ops/templates/task/create.html new file mode 100644 index 000000000..19727e102 --- /dev/null +++ b/apps/ops/templates/task/create.html @@ -0,0 +1,16 @@ +{% extends 'sudo/_sudo.html' %} +{% load i18n %} +{% load bootstrap %} +{% block user_template_title %}{% trans "Create user" %}{% endblock %} +{% block username %} + {{ form.username|bootstrap_horizontal }} +{% endblock %} +{% block password %} +

{% trans 'Password' %}

+
+ +
+ {% trans 'Reset link will be generated and sent to the user. ' %} +
+
+{% endblock %} \ No newline at end of file diff --git a/apps/ops/templates/task/detail.html b/apps/ops/templates/task/detail.html new file mode 100644 index 000000000..95faa39e5 --- /dev/null +++ b/apps/ops/templates/task/detail.html @@ -0,0 +1,282 @@ +{% extends 'base.html' %} +{% load common_tags %} +{% load users_tags %} +{% load static %} +{% load i18n %} + +{% block custom_head_css_js %} + + + + +{% endblock %} +{% block content %} +
+
+
+
+ +
+
+
+
+ {{ sudo.name }} +
+ + + + + + + + + + +
+
+
+ + + + + + + +
{% trans 'Name' %}:{{ sudo.name }}
+
+
+
+
+
+
+ {% trans 'Quick modify' %} +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
{% trans 'Active' %}: +
+
+ + +
+
+
{% trans 'Enable OTP' %}: +
+
+ + +
+
+
{% trans 'Reset password' %}: + + + +
{% trans 'Reset ssh key' %}: + + + +
{% trans 'Update ssh key' %}: + + + +
+
+
+ +
+
+ {% trans 'User group' %} +
+
+ + + + + + + + + + + + {% for group in user_object.groups.all %} + + + + + {% endfor %} + +
+ +
+ +
{{ group.name }} + +
+
+
+
+
+
+
+
+
+ {% include 'users/_user_update_pk_modal.html' %} +{% endblock %} +{% block custom_foot_js %} + +{% endblock %} diff --git a/apps/ops/templates/task/list.html b/apps/ops/templates/task/list.html new file mode 100644 index 000000000..7109e4fe8 --- /dev/null +++ b/apps/ops/templates/task/list.html @@ -0,0 +1,225 @@ +{% extends '_base_list.html' %} +{% load i18n static %} +{% block table_search %} +{% endblock %} +{% block table_container %} + +{##} + + + + + + + + + + + + + + +
+ + {% trans 'Name' %}{% trans 'UUID' %}{% trans 'Start' %}{% trans 'Completed' %}{% trans 'Action' %}
+
+
+ +
+ +
+
+
+{% endblock %} +{% block content_bottom_left %}{% endblock %} +{% block custom_foot_js %} + + +{% endblock %} + diff --git a/apps/ops/templates/task/update.html b/apps/ops/templates/task/update.html new file mode 100644 index 000000000..3172d23f4 --- /dev/null +++ b/apps/ops/templates/task/update.html @@ -0,0 +1,20 @@ +{% extends 'sudo/_sudo.html' %} +{% load i18n %} +{% block user_template_title %}{% trans "Update user" %}{% endblock %} +{% block username %} +
+ +
+ +
+
+{% endblock %} +{% block password %} +

{% trans 'Password' %}

+
+ +
+ +
+
+{% endblock %} diff --git a/apps/ops/tests/__init__.py b/apps/ops/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/ops/tests.py b/apps/ops/tests/tests.py similarity index 100% rename from apps/ops/tests.py rename to apps/ops/tests/tests.py diff --git a/apps/ops/urls/__init__.py b/apps/ops/urls/__init__.py index 8b1378917..e69de29bb 100644 --- a/apps/ops/urls/__init__.py +++ b/apps/ops/urls/__init__.py @@ -1 +0,0 @@ - diff --git a/apps/ops/urls/api_urls.py b/apps/ops/urls/api_urls.py index d0c6b5204..77141ed35 100644 --- a/apps/ops/urls/api_urls.py +++ b/apps/ops/urls/api_urls.py @@ -1,7 +1,21 @@ -# coding:utf-8 +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals -from django.conf.urls import url -from rest_framework import routers +from rest_framework.routers import DefaultRouter +from ops import api as v1_api +__all__ = ["urlpatterns"] +api_router = DefaultRouter() +api_router.register(r'v1/host_alia', v1_api.HostAliaViewSet) +api_router.register(r'v1/user_alia', v1_api.UserAliaViewSet) +api_router.register(r'v1/cmd_alia', v1_api.CmdAliaViewSet) +api_router.register(r'v1/runas_alia', v1_api.RunasAliaViewSet) +api_router.register(r'v1/extra_conf', v1_api.ExtraconfViewSet) +api_router.register(r'v1/privilege', v1_api.PrivilegeViewSet) +api_router.register(r'v1/sudo', v1_api.SudoViewSet) +api_router.register(r'v1/cron', v1_api.CronTableViewSet) +api_router.register(r'v1/task', v1_api.TaskViewSet) +api_router.register(r'v1/subtask', v1_api.SubTaskViewSet) +urlpatterns = api_router.urls \ No newline at end of file diff --git a/apps/ops/urls/view_urls.py b/apps/ops/urls/view_urls.py new file mode 100644 index 000000000..9e9c68253 --- /dev/null +++ b/apps/ops/urls/view_urls.py @@ -0,0 +1,28 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals + + +from django.conf.urls import url +from ops import views as page_view + +__all__ = ["urlpatterns"] + +urlpatterns = [ + # Resource Sudo url + url(r'^sudo/list$', page_view.SudoListView.as_view(), name='page-sudo-list'), + url(r'^sudo/create$', page_view.SudoCreateView.as_view(), name='page-sudo-create'), + url(r'^sudo/(?P[0-9]+)/detail$', page_view.SudoDetailView.as_view(), name='page-sudo-detail'), + url(r'^sudo/(?P[0-9]+)/update$', page_view.SudoUpdateView.as_view(), name='page-sudo-update'), + + # Resource Cron url + url(r'^cron/list$', page_view.CronListView.as_view(), name='page-cron-list'), + url(r'^cron/create$', page_view.CronCreateView.as_view(), name='page-cron-create'), + url(r'^cron/(?P[0-9]+)/detail$', page_view.CronDetailView.as_view(), name='page-cron-detail'), + url(r'^cron/(?P[0-9]+)/update$', page_view.CronUpdateView.as_view(), name='page-cron-update'), + + # TResource Task url + url(r'^task/list$', page_view.TaskListView.as_view(), name='page-task-list'), + url(r'^task/create$', page_view.TaskCreateView.as_view(), name='page-task-create'), + url(r'^task/(?P[0-9]+)/detail$', page_view.TaskDetailView.as_view(), name='page-task-detail'), + url(r'^task/(?P[0-9]+)/update$', page_view.TaskUpdateView.as_view(), name='page-task-update'), +] \ No newline at end of file diff --git a/apps/ops/urls/views_urls.py b/apps/ops/urls/views_urls.py deleted file mode 100644 index a29072e4f..000000000 --- a/apps/ops/urls/views_urls.py +++ /dev/null @@ -1,8 +0,0 @@ -# coding:utf-8 - -from django.conf.urls import url -from .. import views - -app_name = 'ops' - - diff --git a/apps/ops/utils.py b/apps/ops/utils.py deleted file mode 100644 index c84951fd7..000000000 --- a/apps/ops/utils.py +++ /dev/null @@ -1,2 +0,0 @@ -# ~*~ coding: utf-8 ~*~ -# diff --git a/apps/ops/utils/__init__.py b/apps/ops/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/ops/utils/ansible_api.py b/apps/ops/utils/ansible_api.py new file mode 100644 index 000000000..3f3345c5f --- /dev/null +++ b/apps/ops/utils/ansible_api.py @@ -0,0 +1,522 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals, print_function + +import os +import json +import logging +import traceback +import ansible.constants as default_config + +from uuid import uuid4 +from django.utils import timezone +from ansible.executor.task_queue_manager import TaskQueueManager +from ansible.inventory import Inventory, Host, Group +from ansible.vars import VariableManager +from ansible.parsing.dataloader import DataLoader +from ansible.executor import playbook_executor +from ansible.utils.display import Display +from ansible.playbook.play import Play +from ansible.plugins.callback import CallbackBase + +from ops.models import TaskRecord, AnsiblePlay, AnsibleTask, AnsibleHostResult + +__all__ = ["ADHocRunner", "Config"] + + +logger = logging.getLogger(__name__) + + +class AnsibleError(StandardError): + pass + + +class Config(object): + """Ansible运行时配置类, 用于初始化Ansible的一些默认配置. + """ + def __init__(self, verbosity=None, inventory=None, listhosts=None, subset=None, module_paths=None, extra_vars=None, + forks=10, ask_vault_pass=False, vault_password_files=None, new_vault_password_file=None, + output_file=None, tags=None, skip_tags=None, one_line=None, tree=None, ask_sudo_pass=False, ask_su_pass=False, + sudo=None, sudo_user=None, become=None, become_method=None, become_user=None, become_ask_pass=False, + ask_pass=False, private_key_file=None, remote_user=None, connection="smart", timeout=10, ssh_common_args=None, + sftp_extra_args=None, scp_extra_args=None, ssh_extra_args=None, poll_interval=None, seconds=None, check=False, + syntax=None, diff=None, force_handlers=None, flush_cache=None, listtasks=None, listtags=None, module_path=None): + self.verbosity = verbosity + self.inventory = inventory + self.listhosts = listhosts + self.subset = subset + self.module_paths = module_paths + self.extra_vars = extra_vars + self.forks = forks + self.ask_vault_pass = ask_vault_pass + self.vault_password_files = vault_password_files + self.new_vault_password_file = new_vault_password_file + self.output_file = output_file + self.tags = tags + self.skip_tags = skip_tags + self.one_line = one_line + self.tree = tree + self.ask_sudo_pass = ask_sudo_pass + self.ask_su_pass = ask_su_pass + self.sudo = sudo + self.sudo_user = sudo_user + self.become = become + self.become_method = become_method + self.become_user = become_user + self.become_ask_pass = become_ask_pass + self.ask_pass = ask_pass + self.private_key_file = private_key_file + self.remote_user = remote_user + self.connection = connection + self.timeout = timeout + self.ssh_common_args = ssh_common_args + self.sftp_extra_args = sftp_extra_args + self.scp_extra_args = scp_extra_args + self.ssh_extra_args = ssh_extra_args + self.poll_interval = poll_interval + self.seconds = seconds + self.check = check + self.syntax = syntax + self.diff = diff + self.force_handlers = force_handlers + self.flush_cache = flush_cache + self.listtasks = listtasks + self.listtags = listtags + self.module_path = module_path + self.__overwrite_default() + + def __overwrite_default(self): + """上面并不能包含Ansible所有的配置, 如果有其他的配置, + 可以通过替换default_config模块里面的变量进行重载,  + 比如 default_config.DEFAULT_ASK_PASS = False. + """ + default_config.HOST_KEY_CHECKING = False + + +class InventoryMixin(object): + """提供生成Ansible inventory对象的方法 + """ + + def gen_inventory(self): + """用于生成动态构建Ansible Inventory. + self.hosts: [ + {"host": , + "port": , + "user": , + "pass": , + "key": , + "group": + "other_host_var": }, + {...}, + ] + self.group_vars: { + "groupName1": {"var1": , "var2": , ...}, + "groupName2": {"var1": , "var2": , ...}, + } + + :return: 返回一个Ansible的inventory对象 + """ + + # TODO: 验证输入 + + # 创建Ansible Group,如果没有则创建default组 + for asset in self.hosts: + g_name = asset.get('group', 'default') + if g_name not in [g.name for g in self.groups]: + group = Group(name=g_name) + self.groups.append(group) + + # 添加组变量到相应的组上 + for group_name, variables in self.group_vars.iteritems(): + for g in self.groups: + if g.name == group_name: + for v_name, v_value in variables.iteritems(): + g.set_variable(v_name, v_value) + + # 往组里面添加Host + for asset in self.hosts: + # 添加Host链接的常用变量(host,port,user,pass,key) + host = Host(name=asset['name'], port=asset['port']) + host.set_variable('ansible_host', asset['ip']) + host.set_variable('ansible_port', asset['port']) + host.set_variable('ansible_user', asset['username']) + + # 添加密码和秘钥 + if asset.get('password'): + host.set_variable('ansible_ssh_pass', asset['password']) + if asset.get('key'): + host.set_variable('ansible_ssh_private_key_file', asset['key']) + + # 添加become支持 + become = asset.get("become", None) + if become is not None: + host.set_variable("ansible_become", True) + host.set_variable("ansible_become_method", become.get('method')) + host.set_variable("ansible_become_user", become.get('user')) + host.set_variable("ansible_become_pass", become.get('pass')) + else: + host.set_variable("ansible_become", False) + + # 添加其他Host的额外变量 + for key, value in asset.iteritems(): + if key not in ["name", "port", "ip", "username", "password", "key"]: + host.set_variable(key, value) + + # 将host添加到组里面 + for g in self.groups: + if g.name == asset.get('group', 'default'): + g.add_host(host) + + # 将组添加到Inventory里面,生成真正的inventory对象 + inventory = Inventory(loader=self.loader, variable_manager=self.variable_manager, host_list=[]) + for g in self.groups: + inventory.add_group(g) + self.variable_manager.set_inventory(inventory) + return inventory + + +class CallbackModule(CallbackBase): + """处理和分析Ansible运行结果,并保存数据. + """ + CALLBACK_VERSION = 2.0 + CALLBACK_TYPE = 'stdout' + CALLBACK_NAME = 'json' + + def __init__(self, tasker_id, display=None): + super(CallbackModule, self).__init__(display) + self.results = [] + self.output = {} + self.tasker_id = tasker_id + + def _new_play(self, play): + """将Play保持到数据里面 + """ + ret = { + 'tasker': self.tasker_id, + 'name': play.name, + 'uuid': str(play._uuid), + 'tasks': [] + } + + try: + tasker = TaskRecord.objects.get(uuid=self.tasker_id) + play = AnsiblePlay(tasker, name=ret['name'], uuid=ret['uuid']) + play.save() + except Exception as e: + traceback.print_exc() + logger.error("Save ansible play uuid to database error!, %s" % e.message) + + return ret + + def _new_task(self, task): + """将Task保持到数据库里,需要和Play进行关联 + """ + ret = { + 'name': task.name, + 'uuid': str(task._uuid), + 'failed': {}, + 'unreachable': {}, + 'skipped': {}, + 'no_hosts': {}, + 'success': {} + } + + try: + play = AnsiblePlay.objects.get(uuid=self.__play_uuid) + task = AnsibleTask(play=play, uuid=ret['uuid'], name=ret['name']) + task.save() + except Exception as e: + logger.error("Save ansible task uuid to database error!, %s" % e.message) + + return ret + + @property + def __task_uuid(self): + return self.results[-1]['tasks'][-1]['uuid'] + + @property + def __play_uuid(self): + return self.results[-1]['uuid'] + + def save_task_result(self, result, status): + try: + task = AnsibleTask.objects.get(uuid=self.__task_uuid) + host_result = AnsibleHostResult(task=task, name=result._host) + if status == "failed": + host_result.failed = json.dumps(result._result) + elif status == "unreachable": + host_result.unreachable = json.dumps(result._result) + elif status == "skipped": + host_result.skipped = json.dumps(result._result) + elif status == "success": + host_result.success = json.dumps(result._result) + else: + logger.error("No such status(failed|unreachable|skipped|success), please check!") + host_result.save() + except Exception as e: + logger.error("Save Ansible host result to database error!, %s" % e.message) + + @staticmethod + def save_no_host_result(task): + try: + task = AnsibleTask.objects.get(uuid=task._uuid) + host_result = AnsibleHostResult(task=task, no_host="no host to run this task") + host_result.save() + except Exception as e: + logger.error("Save Ansible host result to database error!, %s" % e.message) + + def v2_runner_on_failed(self, result, ignore_errors=False): + self.save_task_result(result, "failed") + host = result._host + self.results[-1]['tasks'][-1]['failed'][host.name] = result._result + + def v2_runner_on_unreachable(self, result): + self.save_task_result(result, "unreachable") + host = result._host + self.results[-1]['tasks'][-1]['unreachable'][host.name] = result._result + + def v2_runner_on_skipped(self, result): + self.save_task_result(result, "skipped") + host = result._host + self.results[-1]['tasks'][-1]['skipped'][host.name] = result._result + + def v2_runner_on_no_hosts(self, task): + self.save_no_host_result(task) + self.results[-1]['tasks'][-1]['no_hosts']['msg'] = "no host to run this task" + + def v2_runner_on_ok(self, result): + self.save_task_result(result, "success") + host = result._host + self.results[-1]['tasks'][-1]['success'][host.name] = result._result + + def v2_playbook_on_play_start(self, play): + self.results.append(self._new_play(play)) + + def v2_playbook_on_task_start(self, task, is_conditional): + self.results[-1]['tasks'].append(self._new_task(task)) + + def v2_playbook_on_stats(self, stats): + """AdHoc模式下这个钩子不会执行 + """ + hosts = sorted(stats.processed.keys()) + + summary = {} + for h in hosts: + s = stats.summarize(h) + summary[h] = s + + self.output['plays'] = self.results + self.output['stats'] = summary + print("summary: %s", summary) + + +class PlayBookRunner(InventoryMixin): + """用于执行AnsiblePlaybook的接口.简化Playbook对象的使用. + """ + + def __init__(self, config, palybook_path, playbook_var, become_pass, *hosts, **group_vars): + """ + + :param config: Config实例 + :param palybook_path: playbook的路径 + :param playbook_var: 执行Playbook时的变量 + :param become_pass: sudo passsword + :param hosts: 可变位置参数, 为一个资产列表, 每一个资产用dict表示, 以下是这个dict必须包含的key + [{ + "name": "asset_name", + "ip": "asset_ip", + "port": "asset_port", + "username": "asset_user", + "password": "asset_pass", + "key": "asset_private_key", + "group": "asset_group_name", + ... + }] + :param group_vars: 可变关键字参数, 是资产组变量, 记录对应的资产组变量 + "groupName1": {"group_variable1": "value1",...} + "groupName2": {"group_variable1": "value1",...} + """ + + self.options = config + + # 设置verbosity级别, 及命令行的--verbose选项 + self.display = Display() + self.display.verbosity = self.options.verbosity + playbook_executor.verbosity = self.options.verbosity + + # sudo成其他用户的配置 + self.options.become = True + self.options.become_method = 'sudo' + self.options.become_user = 'root' + passwords = {'become_pass': become_pass} + + # 传入playbook的路径,以及执行需要的变量 + pb_dir = os.path.dirname(__file__) + playbook = "%s/%s" % (pb_dir, palybook_path) + + # 生成Ansible inventory, 这些变量Mixin都会用到 + self.hosts = hosts + self.group_vars = group_vars + self.loader = DataLoader() + self.variable_manager = VariableManager() + self.groups = [] + self.variable_manager.extra_vars = playbook_var + self.inventory = self.gen_inventory() + + # 初始化playbook的executor + self.pbex = playbook_executor.PlaybookExecutor( + playbooks=[playbook], + inventory=self.inventory, + variable_manager=self.variable_manager, + loader=self.loader, + options=self.options, + passwords=passwords) + + def run(self): + """执行Playbook, 记录执行日志, 处理执行结果. + :return: 对象 + """ + self.pbex.run() + stats = self.pbex._tqm._stats + + # 测试执行是否成功 + run_success = True + hosts = sorted(stats.processed.keys()) + for h in hosts: + t = stats.summarize(h) + if t['unreachable'] > 0 or t['failures'] > 0: + run_success = False + + # TODO: 记录执行日志, 处理执行结果. + + return stats + + +class ADHocRunner(InventoryMixin): + """ADHoc接口 + """ + def __init__(self, play_data, config=None, *hosts, **group_vars): + """ + :param hosts: 见PlaybookRunner参数 + :param group_vars: 见PlaybookRunner参数 + :param config: Config实例 + + :param play_data: + play_data = dict( + name="Ansible Ad-Hoc", + hosts=pattern, + gather_facts=True, + tasks=[dict(action=dict(module='service', args={'name': 'vsftpd', 'state': 'restarted'}), async=async, poll=poll)] + ) + """ + self.options = config if config != None else Config() + + # 设置verbosity级别, 及命令行的--verbose选项 + self.display = Display() + self.display.verbosity = self.options.verbosity + + # sudo的配置移到了Host级别去了,因此这里不再需要处理 + self.passwords = None + + # 生成Ansible inventory, 这些变量Mixin都会用到 + self.hosts = hosts + self.group_vars = group_vars + self.loader = DataLoader() + self.variable_manager = VariableManager() + self.groups = [] + self.inventory = self.gen_inventory() + + self.play = Play().load(play_data, variable_manager=self.variable_manager, loader=self.loader) + + @staticmethod + def update_db_tasker(tasker_id, ext_code): + try: + tasker = TaskRecord.objects.get(uuid=tasker_id) + tasker.end = timezone.now() + tasker.completed = True + tasker.exit_code = ext_code + tasker.save() + except Exception as e: + logger.error("Update Tasker Status into database error!, %s" % e.message) + + def create_db_tasker(self, name, uuid): + try: + hosts = [host.get('name') for host in self.hosts] + tasker = TaskRecord(name=name, uuid=uuid, hosts=','.join(hosts), start=timezone.now()) + tasker.save() + except Exception as e: + logger.error("Save Tasker to database error!, %s" % e.message) + + def run(self, tasker_name, tasker_uuid): + """执行ADHoc, 执行完后, 修改AnsiblePlay的状态为完成状态. + + :param tasker_uuid 用于标示此次task + """ + # 初始化callback插件,以及Tasker + + self.create_db_tasker(tasker_name, tasker_uuid) + self.results_callback = CallbackModule(tasker_uuid) + + tqm = None + # TODO:日志和结果分析 + try: + tqm = TaskQueueManager( + inventory=self.inventory, + variable_manager=self.variable_manager, + loader=self.loader, + stdout_callback=self.results_callback, + options=self.options, + passwords=self.passwords + ) + ext_code = tqm.run(self.play) + result = self.results_callback.results + + # 任务运行结束, 标示任务完成 + self.update_db_tasker(tasker_uuid, ext_code) + + ret = json.dumps(result) + return ext_code, ret + + finally: + if tqm: + tqm.cleanup() + + +def test_run(): + conf = Config() + assets = [ + { + "name": "192.168.1.119", + "ip": "192.168.1.119", + "port": "22", + "username": "root", + "password": "tongfang_test", + "key": "asset_private_key", + }, + { + "name": "192.168.232.135", + "ip": "192.168.232.135", + "port": "22", + "username": "yumaojun", + "password": "xxx", + "key": "asset_private_key", + "become": {"method": "sudo", "user": "root", "pass": "xxx"} + }, + ] + # 初始化Play + play_source = { + "name": "Ansible Play", + "hosts": "default", + "gather_facts": "no", + "tasks": [ + dict(action=dict(module='ping')), + ] + } + hoc = ADHocRunner(conf, play_source, *assets) + uuid = "tasker-" + uuid4().hex + ext_code, result = hoc.run("test_task", uuid) + print(ext_code) + print(result) + + +if __name__ == "__main__": + test_run() diff --git a/apps/ops/utils/mixins.py b/apps/ops/utils/mixins.py new file mode 100644 index 000000000..9f925a420 --- /dev/null +++ b/apps/ops/utils/mixins.py @@ -0,0 +1,13 @@ +# ~*~ coding: utf-8 ~*~ + + +class CreateSudoPrivilegesMixin(object): + + def create_privilege(self): + pass + + +class ListSudoPrivilegesMixin(object): + + def get_all_privilege(self): + pass \ No newline at end of file diff --git a/apps/ops/views.py b/apps/ops/views.py index 91ea44a21..cf07aee59 100644 --- a/apps/ops/views.py +++ b/apps/ops/views.py @@ -1,3 +1,87 @@ -from django.shortcuts import render +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals -# Create your views here. +from django.conf import settings +from django.views.generic.list import ListView, MultipleObjectMixin +from django.views.generic.edit import CreateView, DeleteView, UpdateView +from django.views.generic.detail import DetailView, SingleObjectMixin + +from users.utils import AdminUserRequiredMixin +from ops.utils.mixins import CreateSudoPrivilegesMixin, ListSudoPrivilegesMixin +from ops.models import * + + +class SudoListView(AdminUserRequiredMixin, ListSudoPrivilegesMixin, ListView): + paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + model = Sudo + context_object_name = 'sudos' + template_name = 'sudo/list.html' + + +class SudoCreateView(AdminUserRequiredMixin, CreateSudoPrivilegesMixin, CreateView): + model = Sudo + template_name = 'sudo/create.html' + + +class SudoUpdateView(AdminUserRequiredMixin, UpdateView): + model = Sudo + template_name = 'sudo/update.html' + + +class SudoDetailView(DetailView): + model = Sudo + context_object_name = 'sudo' + template_name = 'sudo/detail.html' + + +class CronListView(AdminUserRequiredMixin, ListView): + paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + model = CronTable + context_object_name = 'crons' + template_name = 'cron/list.html' + + +class CronCreateView(AdminUserRequiredMixin, CreateView): + model = CronTable + template_name = 'cron/create.html' + + +class CronUpdateView(AdminUserRequiredMixin, UpdateView): + model = CronTable + template_name = 'cron/update.html' + + +class CronDetailView(DetailView): + model = CronTable + context_object_name = 'cron' + template_name = 'cron/detail.html' + +class TaskListView(AdminUserRequiredMixin, ListView): + paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + model = Task + context_object_name = 'tasks' + template_name = 'task/list.html' + + def get_context_data(self, **kwargs): + context = { + 'task': 'Assets', + 'action': 'Create asset', + } + kwargs.update(context) + return super(TaskListView, self).get_context_data(**kwargs) + + +class TaskCreateView(AdminUserRequiredMixin, CreateView): + model = Task + template_name = 'task/create.html' + + +class TaskUpdateView(AdminUserRequiredMixin, UpdateView): + model = Task + template_name = 'task/update.html' + + +class TaskDetailView(DetailView): + model = Task + context_object_name = 'task' + template_name = 'task/detail.html' diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 126de0bbe..c7a3144a6 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -331,3 +331,35 @@ jumpserver.initDataTable = function (options) { return table; }; + +/** + * 替换所有匹配exp的字符串为指定字符串 + * @param exp 被替换部分的正则 + * @param newStr 替换成的字符串 + */ +String.prototype.replaceAll = function (exp, newStr) { + return this.replace(new RegExp(exp, "gm"), newStr); +}; + +/** + * 原型:字符串格式化 + * @param args 格式化参数值 + */ +String.prototype.format = function(args) { + var result = this; + if (arguments.length < 1) { + return result; + } + + var data = arguments; + if (arguments.length == 1 && typeof (args) == "object") { + data = args; + } + for ( var key in data) { + var value = data[key]; + if (undefined != value) { + result = result.replaceAll("\\{" + key + "\\}", value); + } + } + return result; +} diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 4d62e185d..0ee86f8a4 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -34,11 +34,24 @@ +
  • {% trans 'Terminal' %} - +  
  • + +
  • + + {% trans 'Job Center' %} + + +
  • +
  • {% trans 'Audits' %}