├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── dzhops ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py ├── hostlist ├── __init__.py ├── admin.py ├── form.py ├── models.py ├── tests.py ├── urls.py └── views.py ├── index ├── __init__.py ├── admin.py ├── forms.py ├── models.py ├── tests.py ├── urls.py └── views.py ├── manage.py ├── managekeys ├── __init__.py ├── admin.py ├── models.py ├── tests.py ├── urls.py ├── utils.py └── views.py ├── newtest ├── __init__.py ├── admin.py ├── models.py ├── tests.py ├── urls.py └── views.py ├── record ├── __init__.py ├── admin.py ├── models.py ├── tests.py ├── urls.py └── views.py ├── replacedata ├── __init__.py ├── admin.py ├── forms.py ├── models.py ├── tests.py ├── urls.py └── views.py ├── saltstack ├── __init__.py ├── admin.py ├── models.py ├── saltapi.py ├── tests.py ├── urls.py ├── util.py └── views.py ├── scripts ├── data_acquisition.py ├── prc.py └── slapi.py ├── static ├── bootstrap │ └── 3.3.4 │ │ ├── css │ │ ├── bootstrap-theme.css │ │ ├── bootstrap-theme.css.map │ │ ├── bootstrap-theme.min.css │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ └── bootstrap.min.css │ │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ │ └── js │ │ ├── bootstrap.js │ │ ├── bootstrap.min.js │ │ └── npm.js ├── img │ ├── 8.png │ ├── button.gif │ ├── dataloading.gif │ ├── favicon.ico │ └── zhaogb.jpg ├── otherjs │ ├── cloud.js │ ├── docs.min.js │ ├── docs.min.js.20150901 │ ├── dzhops.js │ ├── ga.js │ ├── ie-emulation-modes-warning.js │ ├── ie10-viewport-bug-workaround.js │ └── jquery.min.js └── theme │ ├── cover.css │ ├── dashboard.css │ ├── dzhops.css │ └── theme.css └── templates ├── asset_list.html ├── base.html ├── index.html ├── index_upload.html ├── manage_keys.html ├── profile.html ├── record_detail.html ├── record_list.html ├── registration ├── logged_out.html └── login.html ├── repair_history_data.html ├── salt_deploy.html ├── salt_execute.html ├── salt_routine.html ├── salt_update.html └── test.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | 59 | .idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dzhops 2 | + 使用Django框架开发的Salt Stack Web UI 3 | + 开发语言: python; 4 | + 后端框架: Django; 5 | + 前端框架:bootstrap/jquery; 6 | 7 | ## 环境 8 | + RHEL 6.5 x86_64 9 | + salt-master 2015.5.3 10 | + salt-minion 2015.5.3 11 | + salt-api 2015.5.3 12 | + Django 1.6.8 13 | + python 2.6.6 14 | + MySQL 5.5 15 | 16 | ## dzhops更新记录 17 | 1. SaltStack相关功能(部署、更新、维护、远程)代码重构; 18 | 2. 视图文件拆分,新建立app:saltstack/record/managekeys; 19 | 3. 使用json格式通过接口传递数据,提高代码重用率; 20 | 4. 远程操作的jid及返回结果相关信息入库; 21 | 5. 对返回结果按IP进行排序; 22 | 6. 返回结果展示按钮增加上下距离; 23 | 7. 前端各选项左右对齐; 24 | 8. 远程命令执行返回结果显示优化; 25 | 26 | ## 功能介绍 27 | 1. **登陆页面** 28 | ![登陆](https://github.com/Hasal/dzhops_picture/blob/master/dzhops_pic/login.png) 29 | 2. **首页**,显示SaltMaster所在服务器及相关组件状态信息 30 | 目前监控数据,都是通过独立的信息采集脚本完成,需要做计划任务。 31 | ![仪表盘](https://github.com/Hasal/dzhops_picture/blob/master/dzhops_pic/index.png) 32 | 3. **主机列表** 33 | 进入主机列表界面,可以选择机房和维护人员;服务器相关的信息支持自动采集; 34 | ![主机列表](https://github.com/Hasal/dzhops_picture/blob/master/dzhops_pic/asset.png) 35 | 4. **SaltStack** 36 | 可完成如下功能:服务器初始化(如模块部署等)、程序、配置更新、日常维护操作、批量远程命令执行,当对Minion执行操作时,会记录本次目标Minion的数量,然后与返回结果的Minion数量进行对比,找出哪些没有返回结果;当接收到返回结果后,使用bootstrap的模态框显示结果,其中蓝色表示执行成功,红色表示有失败存在,可以点击标签查看详细情况; 37 | ![模块部署](https://github.com/Hasal/dzhops_picture/blob/master/dzhops_pic/deploy.png) 38 | ![模块部署-返回结果-模态框展开-失败情况](https://github.com/Hasal/dzhops_picture/blob/master/dzhops_pic/deploy_show.png) 39 | ![模块部署-返回结果-模态框展开-成功情况](https://github.com/Hasal/dzhops_picture/blob/master/dzhops_pic/deploy_show_success.png) 40 | ![远程命令执行](https://github.com/Hasal/dzhops_picture/blob/master/dzhops_pic/execute.png) 41 | 5. **MinionKeys管理** 42 | 可以分别选择已接受、待接受、已拒绝,并且可以选择机房及维护人员,进行对应的管理操作; 43 | ![MinionKeys管理](https://github.com/Hasal/dzhops_picture/blob/master/dzhops_pic/manage.png) 44 | 6. **操作记录** 45 | 可以记录每次操作执行人的账号、操作、目标、及jid,并可以通过jid查看该次操作的返回结果详细情况。 46 | ![操作记录](https://github.com/Hasal/dzhops_picture/blob/master/dzhops_pic/record.png) 47 | ![操作记录-详细](https://github.com/Hasal/dzhops_picture/blob/master/dzhops_pic/record_detail.png) 48 | 49 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Administrator' 2 | -------------------------------------------------------------------------------- /dzhops/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hasal/dzhops/fcd16adc61a941dccdaebee156b545784a5e96a8/dzhops/__init__.py -------------------------------------------------------------------------------- /dzhops/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for dzhops project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.6/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.6/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 14 | 15 | 16 | # Quick-start development settings - unsuitable for production 17 | # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ 18 | 19 | # SECURITY WARNING: keep the secret key used in production secret! 20 | SECRET_KEY = '2vuicou20ne9t&h)#c2!7sz4cc+6hcfxfmy!4agjs7@7-$nddq' 21 | 22 | # SECURITY WARNING: don't run with debug turned on in production! 23 | DEBUG = True 24 | 25 | TEMPLATE_DEBUG = True 26 | 27 | ALLOWED_HOSTS = [] 28 | 29 | # cookie timeout 30 | SESSION_SAVE_EVERY_REQUEST = True 31 | SESSION_EXPIRE_AT_BROWSER_CLOSE = True 32 | 33 | # Application definition 34 | 35 | INSTALLED_APPS = ( 36 | 'django.contrib.admin', 37 | 'django.contrib.auth', 38 | 'django.contrib.contenttypes', 39 | 'django.contrib.sessions', 40 | 'django.contrib.messages', 41 | 'django.contrib.staticfiles', 42 | 'index', 43 | 'dzhops', 44 | 'hostlist', 45 | 'replacedata', 46 | 'saltstack', 47 | 'record', 48 | 'newtest', 49 | ) 50 | 51 | MIDDLEWARE_CLASSES = ( 52 | 'django.contrib.sessions.middleware.SessionMiddleware', 53 | 'django.middleware.locale.LocaleMiddleware', 54 | 'django.middleware.common.CommonMiddleware', 55 | 'django.middleware.csrf.CsrfViewMiddleware', 56 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 57 | 'django.contrib.messages.middleware.MessageMiddleware', 58 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 59 | ) 60 | 61 | ROOT_URLCONF = 'dzhops.urls' 62 | 63 | WSGI_APPLICATION = 'dzhops.wsgi.application' 64 | 65 | 66 | # Database 67 | # https://docs.djangoproject.com/en/1.6/ref/settings/#databases 68 | 69 | DATABASES = { 70 | 'default': { 71 | 'ENGINE': 'django.db.backends.mysql', 72 | 'NAME': 'dzhops', 73 | 'USER': 'dzhops', 74 | 'PORT': 33066, 75 | 'HOST': 'db-dev.dzh-inc.com', 76 | 'PASSWORD': 'dzhops', 77 | } 78 | } 79 | 80 | # Internationalization 81 | # https://docs.djangoproject.com/en/1.6/topics/i18n/ 82 | 83 | LANGUAGE_CODE = 'en-us' 84 | 85 | TIME_ZONE = 'Asia/Shanghai' 86 | 87 | USE_I18N = True 88 | 89 | USE_L10N = True 90 | 91 | USE_TZ = False 92 | 93 | 94 | # Static files (CSS, JavaScript, Images) 95 | # https://docs.djangoproject.com/en/1.6/howto/static-files/ 96 | 97 | 98 | STATICFILES_DIRS = ( 99 | BASE_DIR + '/static', 100 | ) 101 | 102 | STATIC_URL = '/static/' 103 | TEMPLATE_DIRS = ( 104 | os.path.join(BASE_DIR, 'templates'), 105 | ) 106 | 107 | # RETURNS_MYSQL = { 108 | # 'default': 109 | # { 110 | # 'ENGINE': 'django.db.backends.mysql', 111 | # 'NAME': 'salt', 112 | # 'USER': 'salt', 113 | # 'PORT': 3306, 114 | # 'HOST': '192.168.220.201', 115 | # 'PASSWORD': 'salt' 116 | # } 117 | # } 118 | 119 | # salt-api setting 120 | SALT_API = { 121 | 'url': 'http://10.15.201.102:18000/', 122 | 'user': 'zhaogb', 123 | 'password': 'zhaogb' 124 | } 125 | 126 | # log setting 127 | LOGGING = { 128 | 'version': 1, 129 | 'disable_existing_loggers': True, 130 | 'formatters': { 131 | 'standard': { 132 | 'format': '%(asctime)s %(levelname)s %(module)s %(funcName)s %(lineno)d %(message)s' 133 | }, 134 | 'simple': { 135 | 'format': '%(levelname)s %(message)s' 136 | }, 137 | }, 138 | 'filters': { 139 | 'require_debug_false': { 140 | '()': 'django.utils.log.RequireDebugFalse' 141 | }, 142 | }, 143 | 'handlers': { 144 | 'mail_admins': { 145 | 'level': 'ERROR', 146 | 'filters': ['require_debug_false'], 147 | 'class': 'django.utils.log.AdminEmailHandler' 148 | }, 149 | 'log_handler': { 150 | 'level': 'DEBUG', 151 | 'class': 'logging.handlers.RotatingFileHandler', 152 | 'filename': os.path.join(BASE_DIR, 'log', 'dzhops.log'), 153 | 'formatter': 'standard', 154 | }, 155 | }, 156 | 'loggers': { 157 | 'django.request': { 158 | 'handlers': ['mail_admins'], 159 | 'level': 'ERROR', 160 | 'propagate': True, 161 | }, 162 | 'dzhops': { 163 | 'handlers': ['log_handler'], 164 | 'level': 'DEBUG', 165 | 'propagate': False 166 | }, 167 | } 168 | } 169 | 170 | -------------------------------------------------------------------------------- /dzhops/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | 3 | from django.contrib import admin 4 | admin.autodiscover() 5 | 6 | urlpatterns = patterns( 7 | '', 8 | # Examples: 9 | # url(r'^$', 'dzhops.views.home', name='home'), 10 | # url(r'^blog/', include('blog.urls')), 11 | url(r'^$', 'index.views.index', name='index'), 12 | url(r'^accounts/', include('index.urls')), 13 | url(r'^salt/', include('saltstack.urls')), 14 | url(r'^record/', include('record.urls')), 15 | url(r'^keys/', include('managekeys.urls')), 16 | url(r'^hostlist/', include('hostlist.urls')), 17 | url(r'^data/', include('replacedata.urls')), 18 | url(r'^admin/', include(admin.site.urls)), 19 | url(r'^test/', include('newtest.urls')), 20 | ) 21 | -------------------------------------------------------------------------------- /dzhops/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for dzhops project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dzhops.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /hostlist/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hasal/dzhops/fcd16adc61a941dccdaebee156b545784a5e96a8/hostlist/__init__.py -------------------------------------------------------------------------------- /hostlist/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from hostlist.models import HostList, DzhUser, DataCenter, NetworkOperator, ProvinceArea, Category 3 | 4 | # Register your models here. 5 | 6 | admin.site.register(HostList) 7 | admin.site.register(DzhUser) 8 | admin.site.register(DataCenter) 9 | admin.site.register(NetworkOperator) 10 | admin.site.register(ProvinceArea) 11 | admin.site.register(Category) -------------------------------------------------------------------------------- /hostlist/form.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django import forms 3 | from hostlist.models import * 4 | 5 | 6 | class HostsListForm(forms.ModelForm): 7 | class Meta: 8 | model = HostList 9 | widgets = { 10 | 'hostname': forms.TextInput(attrs={'class': 'form-control'}), 11 | 'ip': forms.TextInput(attrs={'class': 'form-control'}), 12 | 'category': forms.TextInput(attrs={'class': 'form-control'}), 13 | 'dc_cn': forms.TextInput(attrs={'class': 'form-control'}), 14 | 'engineer': forms.TextInput(attrs={'class': 'form-control'}), 15 | 'mac_addr': forms.TextInput(attrs={'class': 'form-control'}), 16 | 'main_source_ip': forms.TextInput(attrs={'class': 'form-control'}), 17 | 'backup_source_ip': forms.TextInput(attrs={'class': 'form-control'}), 18 | 'lic_date': forms.TextInput(attrs={'class': 'form-control'}), 19 | 'lic_status': forms.TextInput(attrs={'class': 'form-control'}), 20 | 'remark': forms.TextInput(attrs={'class': 'form-control'}), 21 | } 22 | -------------------------------------------------------------------------------- /hostlist/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.db import models 3 | 4 | 5 | # Create your models here. 6 | class DzhUser(models.Model): 7 | username = models.CharField(max_length=30, blank=True, verbose_name=u'用户名') 8 | engineer = models.CharField(max_length=30, blank=True, verbose_name=u'维护人员') 9 | 10 | def __unicode__(self): 11 | return u'%s %s' % (self.username, self.engineer) 12 | 13 | 14 | class DataCenter(models.Model): 15 | dc_en = models.CharField(max_length=30, blank=True, verbose_name=u'机房简称') 16 | dc_cn = models.CharField(max_length=30, blank=True, verbose_name=u'机房全称') 17 | 18 | def __unicode__(self): 19 | return u'%s %s' % (self.dc_en, self.dc_cn) 20 | 21 | 22 | class NetworkOperator(models.Model): 23 | no_en = models.CharField(max_length=30, blank=True, verbose_name=u'运营商简称') 24 | no_cn = models.CharField(max_length=30, blank=True, verbose_name=u'运营商全称') 25 | 26 | def __unicode__(self): 27 | return u'%s %s' % (self.no_en, self.no_cn) 28 | 29 | 30 | class ProvinceArea(models.Model): 31 | pa_en = models.CharField(max_length=30, blank=True, verbose_name=u'省份地区简称') 32 | pa_cn = models.CharField(max_length=30, blank=True, verbose_name=u'省份地区全称') 33 | 34 | def __unicode__(self): 35 | return u'%s %s' % (self.pa_en, self.pa_cn) 36 | 37 | 38 | class Category(models.Model): 39 | category_en = models.CharField(max_length=30, blank=True, verbose_name=u'类别简称') 40 | category_cn = models.CharField(max_length=30, blank=True, verbose_name=u'类别全称') 41 | 42 | def __unicode__(self): 43 | return u'%s %s' % (self.category_en, self.category_cn) 44 | 45 | 46 | class HostList(models.Model): 47 | ip = models.CharField(max_length=15, blank=True, verbose_name=u'IP地址') 48 | hostname = models.CharField(max_length=30, verbose_name=u'主机名') 49 | minion_id = models.CharField(max_length=60, verbose_name=u'MinionID') 50 | no_cn = models.CharField(max_length=30, verbose_name=u'运营商全称') 51 | category_cn = models.CharField(max_length=30, blank=True, verbose_name=u'类别') 52 | pa_cn = models.CharField(max_length=30, verbose_name=u'地区全称') 53 | dc_cn = models.CharField(max_length=30, blank=True, verbose_name=u'机房全称') 54 | engineer = models.CharField(max_length=30, blank=True, verbose_name=u'维护人员') 55 | mac_addr = models.CharField(max_length=20, blank=True, verbose_name=u'MAC地址') 56 | main_source_ip = models.CharField(max_length=30, blank=True, verbose_name=u'主行情源') 57 | backup_source_ip = models.CharField(max_length=30, blank=True, verbose_name=u'备行情源') 58 | # dccn = models.ForeignKey(dataCenter, related_name='datacenter_hostlist') 59 | lic_date = models.CharField(max_length=30, blank=True, verbose_name=u'授权日期') 60 | lic_status = models.CharField(max_length=30, blank=True, verbose_name=u'授权状态') 61 | # engineer = models.ForeignKey(dzhuser, related_name='dzhuser_hostlist') 62 | id_ip = models.CharField(max_length=15, blank=True, verbose_name=u'MinionID中的IP地址') 63 | ip_same = models.CharField(max_length=10, blank=True, verbose_name=u'IP地址一致性') 64 | remark = models.TextField(max_length=200, blank=True, verbose_name=u'备注') 65 | 66 | def __unicode__(self): 67 | return u'%s %s %s %s %s' % (self.hostname, self.ip, self.category_cn, self.dc_cn, self.engineer) 68 | 69 | class Meta: 70 | ordering = ['minionid'] 71 | -------------------------------------------------------------------------------- /hostlist/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /hostlist/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.conf.urls import patterns, include, url 3 | from django.contrib import admin 4 | admin.autodiscover() 5 | 6 | urlpatterns = patterns('hostlist.views', 7 | # Examples: 8 | # url(r'^$', 'oms.views.home', name='home'), 9 | # url(r'^blog/', include('blog.urls')), 10 | url(r'^asset/$', 'assetList', name='asset_list'), 11 | url(r'^asset/api/$', 'assetListAPI', name='asset_api'), 12 | ) 13 | -------------------------------------------------------------------------------- /hostlist/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.shortcuts import render 3 | from django.http import HttpResponse, JsonResponse 4 | from django.contrib.auth.decorators import login_required 5 | from django.views.decorators.http import require_GET 6 | from hostlist.models import DzhUser, DataCenter, HostList 7 | 8 | import logging 9 | 10 | log = logging.getLogger('dzhops') 11 | 12 | 13 | @login_required 14 | def asset_list(request): 15 | """ 16 | 展示所有服务器信息; 17 | :param request: 18 | :return: 19 | """ 20 | # user = request.user.username 21 | dc_dict = {} 22 | engi_dict = {} 23 | 24 | dc = DataCenter.objects.all() 25 | for i in dc: 26 | dc_dict[i.dcen] = i.dccn 27 | eg = DzhUser.objects.all() 28 | for j in eg: 29 | engi_dict[j.username] = j.engineer 30 | db_result = HostList.objects.all() 31 | srv_list = filter_data(db_result) 32 | 33 | return render( 34 | request, 35 | 'asset_list.html', 36 | { 37 | 'dc_dict': dc_dict, 38 | 'engi_dict': engi_dict, 39 | 'serv_list': srv_list 40 | } 41 | ) 42 | 43 | 44 | @login_required 45 | @require_GET 46 | def asset_list_api(request): 47 | """ 48 | 当前端选择机房与维护人员的时候,通过该接口提交请求并返回数据; 49 | :param request: 50 | :return: 51 | """ 52 | user = request.user.username 53 | 54 | dc = request.GET.get('dcen', '') 55 | eg = request.GET.get('engi', '') 56 | result = [] 57 | if dc == 'All_DC' and eg == 'ALL_ENGI': 58 | result = HostList.objects.all() 59 | elif dc == 'All_DC' and eg != 'ALL_ENGI': 60 | eg_result = DzhUser.objects.get(username=eg) 61 | result = HostList.objects.filter(engineer=eg_result.engineer) 62 | elif dc != 'All_DC' and eg == 'ALL_ENGI': 63 | dc_result = DataCenter.objects.get(dcen=dc) 64 | result = HostList.objects.filter(dccn=dc_result.dccn) 65 | elif dc != 'All_DC' and eg != 'ALL_ENGI': 66 | eg_result = DzhUser.objects.get(username=eg) 67 | dc_result = DataCenter.objects.get(dcen=dc) 68 | result = HostList.objects.filter(dccn=dc_result.dccn, engineer=eg_result.engineer) 69 | else: 70 | log.error('Unexpected execution here.') 71 | 72 | srv_list = filter_data(result) 73 | return JsonResponse(srv_list) 74 | 75 | 76 | def filter_data(models_data): 77 | ip_list = [] 78 | srv_list = [] 79 | srv_dict = {} 80 | 81 | for i in models_data: 82 | ip_list.append(i.ip) 83 | srv_dict[i.ip] = [ 84 | i.ip, 85 | i.hostname, 86 | i.minionid, 87 | i.nocn, 88 | i.catagorycn, 89 | i.pacn, 90 | i.dccn, 91 | i.engineer 92 | ] 93 | ip_list.sort() 94 | for ip in ip_list: 95 | srv_list.append(srv_dict.get(ip)) 96 | return srv_list 97 | -------------------------------------------------------------------------------- /index/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hasal/dzhops/fcd16adc61a941dccdaebee156b545784a5e96a8/index/__init__.py -------------------------------------------------------------------------------- /index/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /index/forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django import forms 4 | 5 | class UploadFileForm(forms.Form): 6 | ''' 7 | Upload user picture for profile web. 8 | ''' 9 | file = forms.FileField(required=False) 10 | 11 | class ChangePasswordForms(forms.Form): 12 | ''' 13 | change password for user; 14 | ''' 15 | password_old = forms.CharField(max_length=32, widget=forms.PasswordInput(attrs={'class': 'form-control'})) 16 | password_new = forms.CharField(max_length=32, widget=forms.PasswordInput(attrs={'class': 'form-control'})) 17 | password_new_again = forms.CharField(max_length=32, widget=forms.PasswordInput(attrs={'class': 'form-control'})) 18 | 19 | def clean_password_new_again(self): 20 | password_new = self.cleaned_data['password_new'] 21 | password_new_again = self.cleaned_data['password_new_again'] 22 | if password_new_again != password_new: 23 | raise forms.ValidationError(u"新密码不一致!") 24 | return password_new, password_new_again -------------------------------------------------------------------------------- /index/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | 5 | 6 | class MiniKeys(models.Model): 7 | nowtime = models.DateTimeField(blank=True, null=True) 8 | miniall = models.IntegerField(blank=True, null=True) 9 | minion = models.IntegerField(blank=True, null=True) 10 | miniout = models.IntegerField(blank=True, null=True) 11 | keyall = models.IntegerField(blank=True, null=True) 12 | keypre = models.IntegerField(blank=True, null=True) 13 | keyrej = models.IntegerField(blank=True, null=True) 14 | def __unicode__(self): 15 | return u'%s %s %s %s %s %s %s' %( 16 | self.nowtime, self.miniall, self.minion, self.miniout, self.keyall, self.keypre, self.keyrej) 17 | 18 | class ProcStatus(models.Model): 19 | nowtime = models.DateTimeField(blank=True, null=True) 20 | saltproc = models.IntegerField(blank=True, null=True) 21 | apiproc = models.IntegerField(blank=True, null=True) 22 | myproc = models.IntegerField(blank=True, null=True) 23 | snmproc = models.IntegerField(blank=True, null=True) 24 | def __unicode__(self): 25 | return u'%s %s %s %s %s' %(self.nowtime, self.saltproc, self.apiproc, self.myproc, self.snmproc) 26 | 27 | 28 | class ServStatus(models.Model): 29 | nowtime = models.DateTimeField(blank=True, null=True) 30 | sysone = models.CharField(max_length=10, blank=True) 31 | sysfive = models.CharField(max_length=10, blank=True) 32 | sysfifteen = models.CharField(max_length=10, blank=True) 33 | cpuperc = models.CharField(max_length=10, blank=True) 34 | memtotal = models.CharField(max_length=10, blank=True) 35 | memused = models.CharField(max_length=10, blank=True) 36 | memperc = models.CharField(max_length=10, blank=True) 37 | disktotal = models.CharField(max_length=10, blank=True) 38 | diskused = models.CharField(max_length=10, blank=True) 39 | diskperc = models.CharField(max_length=10, blank=True) 40 | def __unicode__(self): 41 | return u'%s %s %s %s %s %s %s %s %s %s %s' %( 42 | self.nowtime, self.sysone, self.sysfive, self.sysfifteen, self.cpuperc, self.memtotal, self.memused, 43 | self.memperc, self.disktotal, self.diskused, self.diskperc) 44 | 45 | class UploadImageModels(models.Model): 46 | user_image = models.ImageField(upload_to='static/img/', blank=True, null=True) 47 | -------------------------------------------------------------------------------- /index/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /index/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.conf.urls import patterns, include, url 3 | from django.contrib import admin 4 | admin.autodiscover() 5 | from django.contrib.auth.views import login, logout 6 | 7 | urlpatterns = patterns('index.views', 8 | # Examples: 9 | # url(r'^$', 'oms.views.home', name='home'), 10 | # url(r'^blog/', include('blog.urls')), 11 | 12 | url(r'^login/$', login, name='login'), 13 | url(r'^profile/$', 'profile', name='profile'), 14 | url(r'^upload/$', 'upload_file', name='upload'), 15 | url(r'^logout/$', logout, name='logout'), 16 | ) 17 | -------------------------------------------------------------------------------- /index/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.shortcuts import render 4 | from django.http import HttpResponseRedirect 5 | from django.contrib.auth.models import User 6 | from django.contrib.auth.decorators import login_required 7 | from django.contrib.auth import authenticate 8 | from dzhops import settings 9 | from index.models import MiniKeys, ProcStatus, ServStatus 10 | from index.forms import ChangePasswordForms, UploadFileForm 11 | from record.models import OperateRecord 12 | # import system libs 13 | import Image 14 | import os 15 | import shutil 16 | import logging 17 | 18 | log = logging.getLogger('dzhops') 19 | 20 | 21 | def imageResize(img, username): 22 | ''' 23 | Image resize to 58 * 58; 24 | :param img: 25 | :return: 26 | ''' 27 | try: 28 | standard_size = (58, 58) 29 | file_type = '.jpg' 30 | user_pic_list = [username, file_type] 31 | log.debug(str(user_pic_list)) 32 | user_pic_str = ''.join(user_pic_list) 33 | log.debug('37') 34 | log.debug('user_pic_path: %s' % str(settings.STATICFILES_DIRS)) 35 | user_pic_path = os.path.join(settings.STATICFILES_DIRS[0], 'img', user_pic_str) 36 | im = Image.open(img) 37 | im_new = im.resize(standard_size, Image.ANTIALIAS) 38 | im_new.save(user_pic_path, 'JPEG', quality=100) 39 | log.info('The user pic resize successed') 40 | try: 41 | all_static = settings.STATIC_ROOT 42 | all_static_url = os.path.join(all_static, 'img') 43 | except AttributeError, e: 44 | all_static_url = '' 45 | log.info('The config settings.py no STATIC_ROOT attribute') 46 | if all_static_url and os.path.exists(all_static_url): 47 | shutil.copy(user_pic_path, all_static_url) 48 | log.info('The user picture copy to all_static_url') 49 | else: 50 | log.debug('The all_static_url is None or do not exist') 51 | except Exception, e: 52 | log.error(str(e)) 53 | 54 | 55 | def handle_uploaded_file(f, filename): 56 | picname = '%s.jpg' % filename 57 | filepath = os.path.join(settings.STATICFILES_DIRS[0], 'img', picname) 58 | with open(filepath, 'wb+') as destination: 59 | for chunk in f.chunks(): 60 | destination.write(chunk) 61 | log.debug('user pic upload and save') 62 | imageResize(filepath, filename) 63 | log.debug('The user picture resize and save') 64 | 65 | 66 | @login_required 67 | def upload_file(request): 68 | user = request.user.username 69 | if request.method == 'POST': 70 | form = UploadFileForm(request.POST, request.FILES) 71 | if form.is_valid(): 72 | handle_uploaded_file(request.FILES['file'], user) 73 | return HttpResponseRedirect('/accounts/upload/') 74 | else: 75 | form = UploadFileForm() 76 | return render( 77 | request, 78 | 'index_upload.html', 79 | {'form': form} 80 | ) 81 | 82 | 83 | @login_required 84 | def profile(request): 85 | ''' 86 | :param request: 87 | :return: 88 | ''' 89 | user = request.user.username 90 | tips = '' 91 | if request.method == 'POST': 92 | form = ChangePasswordForms(request.POST) 93 | if form.is_valid(): 94 | form_data_dict = form.cleaned_data 95 | passwd_old = form_data_dict['password_old'] 96 | passwd_new = form_data_dict['password_new'] 97 | passwd_new_again = form_data_dict['password_new_again'] 98 | 99 | user_auth = authenticate(username=user, password=passwd_old) 100 | if user_auth is not None: 101 | user_pwd = User.objects.get(username=user) 102 | user_pwd.set_password(passwd_new) 103 | user_pwd.save() 104 | tips = u'密码修改成功!' 105 | else: 106 | tips = u'原密码不正确,请重新输入!' 107 | else: 108 | form = ChangePasswordForms() 109 | 110 | tips = tips.encode('utf-8') 111 | return render( 112 | request, 113 | 'profile.html', 114 | { 115 | 'form': form, 116 | 'tips': tips 117 | } 118 | ) 119 | 120 | 121 | @login_required 122 | def index(request): 123 | ''' 124 | index page 125 | ''' 126 | 127 | ret = {} 128 | 129 | try: 130 | server_status = ServStatus.objects.order_by('-id')[0] 131 | # SELECT * from `servstatus` order by id DESC limit 1 132 | 133 | ret['nowtime'] = server_status.nowtime 134 | ret['sysone'] = server_status.sysone 135 | ret['sysfive'] = server_status.sysfive 136 | ret['sysfifteen'] = server_status.sysfifteen 137 | ret['cpuperc'] = server_status.cpuperc 138 | ret['memtotal'] = server_status.memtotal 139 | ret['memused'] = server_status.memused 140 | ret['memperc'] = server_status.memperc 141 | ret['disktotal'] = server_status.disktotal 142 | ret['diskused'] = server_status.diskused 143 | ret['diskperc'] = server_status.diskperc 144 | 145 | # except OperationalError,e: 146 | except Exception, e: 147 | # ret['serv_error_code'] = e[0] 148 | # ret['serv_error_content'] = e[1] 149 | ret['nowtime'] = 'Null' 150 | ret['sysone'] = 'Null' 151 | ret['sysfive'] = 'Null' 152 | ret['sysfifteen'] = 'Null' 153 | ret['cpuperc'] = 'Null' 154 | ret['memtotal'] = 'Null' 155 | ret['memused'] = 'Null' 156 | ret['memperc'] = 'Null' 157 | ret['disktotal'] = 'Null' 158 | ret['diskused'] = 'Null' 159 | ret['diskperc'] = 'Null' 160 | 161 | try: 162 | proc_status = ProcStatus.objects.order_by('-id')[0] 163 | # SELECT * from `procstatus` order by id DESC limit 1 164 | 165 | ret['proctime'] = proc_status.nowtime 166 | 167 | if proc_status.saltproc == 0: 168 | ret['saltst'] = '正常' 169 | elif proc_status.saltproc == 1: 170 | ret['saltst'] = '异常' 171 | else: 172 | ret['saltst'] = 'UNKNOWN' 173 | 174 | if proc_status.apiproc == 0: 175 | ret['apist'] = '正常' 176 | elif proc_status.apiproc == 1: 177 | ret['apist'] = '异常' 178 | else: 179 | ret['apist'] = 'UNKNOWN' 180 | 181 | if proc_status.myproc == 0: 182 | ret['myst'] = '正常' 183 | elif proc_status.myproc == 1: 184 | ret['myst'] = '异常' 185 | else: 186 | ret['myst'] = 'UNKNOWN' 187 | 188 | if proc_status.snmproc == 0: 189 | ret['snmpst'] = '正常' 190 | elif proc_status.snmproc == 1: 191 | ret['snmpst'] = '异常' 192 | else: 193 | ret['snmpst'] = 'UNKNOWN' 194 | 195 | # except OperationalError,e: 196 | except Exception, e: 197 | # ret['proc_error_code'] = e[0] 198 | # ret['proc_error_content'] = e[1] 199 | ret['saltst'] = 'Null' 200 | ret['apist'] = 'Null' 201 | ret['myst'] = 'Null' 202 | ret['snmpst'] = 'Null' 203 | 204 | try: 205 | minion_keys = MiniKeys.objects.order_by('-id')[0] 206 | # SELECT * from `minikeys` order by id DESC limit 1 207 | 208 | ret['mktime'] = minion_keys.nowtime 209 | ret['num_miniall'] = minion_keys.miniall 210 | ret['num_miniup'] = minion_keys.minion 211 | ret['num_minidown'] = minion_keys.miniout 212 | ret['num_mini'] = minion_keys.keyall 213 | ret['num_minipre'] = minion_keys.keypre 214 | ret['num_minirej'] = minion_keys.keyrej 215 | 216 | # except OperationalError,e: 217 | except Exception, e: 218 | # ret['mini_error_code'] = e[0] 219 | # ret['mini_error_content'] = e[1] 220 | ret['mktime'] = 'Null' 221 | ret['num_miniall'] = 'Null' 222 | ret['num_miniup'] = 'Null' 223 | ret['num_minidown'] = 'Null' 224 | ret['num_mini'] = 'Null' 225 | ret['num_minipre'] = 'Null' 226 | ret['num_minirej'] = 'Null' 227 | 228 | streslut = '' 229 | try: 230 | operate_rec = OperateRecord.objects.order_by('-id')[0:8] 231 | for result in operate_rec: 232 | streslut += '%s %s %s %s\n' % (result.nowtime, result.username, result.user_operate, result.simple_tgt) 233 | except Exception, e: 234 | log.error("No data acquired.") 235 | 236 | return render( 237 | request, 238 | 'index.html', 239 | { 240 | 'ret': ret, 241 | 'stret': streslut 242 | } 243 | ) 244 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import sys 5 | 6 | if __name__ == "__main__": 7 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dzhops.settings") 8 | 9 | from django.core.management import execute_from_command_line 10 | 11 | execute_from_command_line(sys.argv) 12 | -------------------------------------------------------------------------------- /managekeys/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hasal/dzhops/fcd16adc61a941dccdaebee156b545784a5e96a8/managekeys/__init__.py -------------------------------------------------------------------------------- /managekeys/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /managekeys/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /managekeys/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /managekeys/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.conf.urls import patterns, include, url 3 | from django.contrib import admin 4 | admin.autodiscover() 5 | 6 | urlpatterns = patterns( 7 | 'managekeys.views', 8 | # Examples: 9 | # url(r'^$', 'dzhops.views.home', name='home'), 10 | # url(r'^blog/', include('blog.urls')), 11 | url(r'^show/$', 'manageMinionKeys', name='keys_show'), 12 | url(r'^api/$', 'manageMinionKeysAPI', name='keys_api'), 13 | url(r'^(?Paccept|delete|reject)/$', 'actionMinionKeys', name='action_keys'), 14 | ) -------------------------------------------------------------------------------- /managekeys/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from hostlist.models import HostList, Dzhuser 3 | import logging 4 | 5 | log = logging.getLogger('dzhops') 6 | 7 | def clearUpMinionKyes(idlist, dc, eg): 8 | ''' 9 | 对Minion id进行整理,返回对应状态、机房、维护人员的minion id; 10 | :param idlist: acp/pre/rej(分别表示已经接受、未接受、已拒绝三个状态) 11 | :param dc: 机房英文简称 12 | :param eg: 维护人员用户名,英文简称; 13 | :return: 过滤后的minion id 组成的列表; 14 | ''' 15 | if dc == 'DC_ALL' and eg == 'EG_ALL': 16 | result = idlist 17 | elif dc != 'DC_ALL' and eg == 'EG_ALL': 18 | result = [] 19 | for id in idlist: 20 | id_dcen = id.split("_") 21 | if id_dcen[3] == dc: 22 | result.append(id) 23 | elif dc == 'DC_ALL' and eg != 'EG_ALL': 24 | eg_id_list = [] 25 | engi_result = Dzhuser.objects.get(username=eg) 26 | data = HostList.objects.filter(engineer=engi_result.engineer) 27 | for row in data: 28 | eg_id_list.append(row.minionid) 29 | result = list(set(idlist).intersection(set(eg_id_list))) 30 | elif dc != 'DC_ALL' and eg != 'EG_ALL': 31 | dc_id_list = [] 32 | eg_id_list = [] 33 | for id in idlist: 34 | id_dcen = id.split("_") 35 | if id_dcen[3] == dc: 36 | dc_id_list.append(id) 37 | engi_result = Dzhuser.objects.get(username=eg) 38 | data = HostList.objects.filter(engineer=engi_result.engineer) 39 | for row in data: 40 | eg_id_list.append(row.minionid) 41 | result = list(set(dc_id_list).intersection(set(eg_id_list))) 42 | else: 43 | result = [] 44 | log.error("Unexpected execution here.") 45 | 46 | return result -------------------------------------------------------------------------------- /managekeys/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.shortcuts import render 3 | from django.http import HttpResponse 4 | from django.contrib.auth.decorators import login_required 5 | from django.http import HttpResponseRedirect 6 | from django.core.urlresolvers import reverse 7 | 8 | from hostlist.models import HostList, DataCenter, Dzhuser 9 | from saltstack.saltapi import SaltAPI 10 | from managekeys.utils import clearUpMinionKyes 11 | 12 | from dzhops import settings 13 | 14 | import logging, json 15 | # Create your views here. 16 | 17 | log = logging.getLogger('dzhops') 18 | 19 | 20 | @login_required 21 | def manageMinionKeys(request): 22 | ''' 23 | 进入页面,首次展示已经接受的所有Minion ID,从这里获取并返回; 24 | :param request: 25 | :return: 26 | ''' 27 | user = request.user.username 28 | serv_list = [] 29 | ip_list = [] 30 | serv_dict = {} 31 | dc_dict = {} 32 | engi_dict ={} 33 | 34 | sapi = SaltAPI( 35 | url=settings.SALT_API['url'], 36 | username=settings.SALT_API['user'], 37 | password=settings.SALT_API['password'] 38 | ) 39 | minions, minions_pre, minions_rej = sapi.allMinionKeys() 40 | # log.debug(str(minions)) 41 | 42 | dcs = DataCenter.objects.all() 43 | for dc in dcs: 44 | dc_dict[dc.dcen] = dc.dccn 45 | egs = Dzhuser.objects.all() 46 | for eg in egs: 47 | engi_dict[eg.username] = eg.engineer 48 | 49 | for id in minions: 50 | id_list = id.split('_') 51 | ip = '.'.join(id_list[4:]) 52 | ip_list.append(ip) 53 | serv_dict[ip] = id 54 | ip_list.sort() 55 | for i in ip_list: 56 | ipid_dict = {} 57 | id = serv_dict.get(i) 58 | ipid_dict[i] = id 59 | serv_list.append(ipid_dict) 60 | del ipid_dict 61 | 62 | return render( 63 | request, 64 | 'manage_keys.html', 65 | { 66 | 'dc_dict': dc_dict, 67 | 'engi_dict': engi_dict, 68 | 'serv_list': serv_list 69 | } 70 | ) 71 | 72 | 73 | @login_required 74 | def manageMinionKeysAPI(request): 75 | ''' 76 | 前端选择不同状态、机房、维护人员下的Minion id相关信息,通过ajax后台请求并刷新页面; 77 | :param request: 78 | :return: [ 79 | {'192.168.220.201': 'CNET_HQ_SH_BETA_192_168_220_201'}, 80 | {'192.168.220.202': 'CNET_HQ_SH_BETA_192_168_220_202'}, 81 | ... 82 | ] 83 | ''' 84 | user = request.user.username 85 | ip_list = [] 86 | serv_list = [] 87 | serv_dict = {} 88 | 89 | if request.method == 'GET': 90 | column = request.GET.get('col', '') 91 | dcen = request.GET.get('dcen', '') 92 | engi = request.GET.get('engi', '') 93 | 94 | sapi = SaltAPI( 95 | url=settings.SALT_API['url'], 96 | username=settings.SALT_API['user'], 97 | password=settings.SALT_API['password'] 98 | ) 99 | minions, minions_pre, minions_rej = sapi.allMinionKeys() 100 | 101 | if column == 'acp': 102 | result = clearUpMinionKyes(minions, dcen, engi) 103 | elif column == 'pre': 104 | result = clearUpMinionKyes(minions_pre, dcen, engi) 105 | elif column == 'rej': 106 | result = clearUpMinionKyes(minions_rej, dcen, engi) 107 | else: 108 | log.error("Unexpected execution here.") 109 | 110 | for id in result: 111 | id_list = id.split('_') 112 | ip = '.'.join(id_list[4:]) 113 | ip_list.append(ip) 114 | serv_dict[ip] = id 115 | ip_list.sort() 116 | for i in ip_list: 117 | ipid_dict = {} 118 | id = serv_dict.get(i) 119 | ipid_dict[i] = id 120 | serv_list.append(ipid_dict) 121 | del ipid_dict 122 | else: 123 | log.error("Request the wrong way, need to GET method.") 124 | 125 | keys_json = json.dumps(serv_list) 126 | 127 | return HttpResponse(keys_json, content_type="application/json") 128 | 129 | 130 | @login_required 131 | def actionMinionKeys(request, action): 132 | ''' 133 | 管理minion keys,根据url捕获操作,如接受、拒绝、删除; 134 | :param request: 'zhaogb-201,zhaogb-202,...,'(注意:有逗号结尾的字符串) 135 | :param action: 通过url捕获的accept/reject/delect; 136 | :return: 137 | ''' 138 | action = action 139 | minion_id = request.GET.get('minion_id') 140 | minion_id_strings = minion_id.strip(',') 141 | 142 | sapi = SaltAPI( 143 | url=settings.SALT_API['url'], 144 | username=settings.SALT_API['user'], 145 | password=settings.SALT_API['password']) 146 | ret = sapi.actionKyes(minion_id_strings, action) 147 | 148 | result_json = json.dumps(ret) 149 | 150 | return HttpResponse(result_json, content_type='application/json') 151 | 152 | @login_required 153 | def minionKeysAccept(request): 154 | ''' 155 | 展示Master已经接受的所有Minion keys; 156 | :param request: 157 | :return: 158 | ''' 159 | user = request.user.username 160 | dccn_list = [] 161 | dc_hosts = {} 162 | 163 | sapi = SaltAPI( 164 | url=settings.SALT_API['url'], 165 | username=settings.SALT_API['user'], 166 | password=settings.SALT_API['password'] 167 | ) 168 | 169 | minions, minions_pre, minions_rej = sapi.allMinionKeys() 170 | minions_set = set(minions) 171 | 172 | dcs = DataCenter.objects.all() 173 | for dc in dcs: 174 | minion_info_dict = {} 175 | hosts = HostList.objects.filter(dccn=dc.dccn) 176 | dccn_list.append(dc.dccn) 177 | for host in hosts: 178 | minion_id = host.minionid 179 | if minion_id in minions_set: 180 | minion_info_dict[minion_id] = host.ip 181 | dc_hosts[dc.dccn] = minion_info_dict 182 | 183 | dccn_list.sort() 184 | 185 | return render( 186 | request, 187 | 'manage_keys_accept.html', 188 | { 189 | 'all_dc_list': dccn_list, 190 | 'all_dc_hosts': dc_hosts 191 | } 192 | ) 193 | 194 | @login_required 195 | def minionKeysUnaccept(request): 196 | ''' 197 | 展示待接受的minion keys; 198 | :param request: 199 | :return: 200 | ''' 201 | user = request.user.username 202 | dccn_list = [] 203 | dc_hosts = {} 204 | 205 | sapi = SaltAPI( 206 | url=settings.SALT_API['url'], 207 | username=settings.SALT_API['user'], 208 | password=settings.SALT_API['password']) 209 | minions, minions_pre, minions_rej = sapi.allMinionKeys() 210 | minions_pre_set = set(minions_pre) 211 | 212 | dcs = DataCenter.objects.all() 213 | for dc in dcs: 214 | minion_info_dict = {} 215 | hosts = HostList.objects.filter(dccn=dc.dccn) 216 | dccn_list.append(dc.dccn) 217 | for host in hosts: 218 | minion_id = host.minionid 219 | if minion_id in minions_pre_set: 220 | minion_info_dict[minion_id] = host.ip 221 | dc_hosts[dc.dccn] = minion_info_dict 222 | dccn_list.sort() 223 | 224 | return render( 225 | request, 226 | 'manage_keys_unaccept.html', 227 | { 228 | 'all_dc_list': dccn_list, 229 | 'all_minions_pre': dc_hosts 230 | } 231 | ) 232 | 233 | @login_required 234 | def minionKeysReject(request): 235 | ''' 236 | 展示已经被拒绝的Minino keys; 237 | :param request: 238 | :return: 239 | ''' 240 | user = request.user.username 241 | dccn_list = [] 242 | dc_hosts = {} 243 | dc_hosts_keys = {} 244 | 245 | sapi = SaltAPI( 246 | url=settings.SALT_API['url'], 247 | username=settings.SALT_API['user'], 248 | password=settings.SALT_API['password']) 249 | minions, minions_pre, minions_rej = sapi.allMinionKeys() 250 | minions_rej_set = set(minions_rej) 251 | 252 | dcs = DataCenter.objects.all() 253 | for dc in dcs: 254 | minion_info_dict = {} 255 | hosts = HostList.objects.filter(dccn=dc.dccn) 256 | dccn_list.append(dc.dccn) 257 | for host in hosts: 258 | minion_id = host.minionid 259 | if minion_id in minions_rej_set: 260 | minion_info_dict[minion_id] = host.ip 261 | dc_hosts[dc.dccn] = minion_info_dict 262 | dccn_list.sort() 263 | 264 | return render( 265 | request, 266 | 'manage_keys_reject.html', 267 | { 268 | 'all_dc_list': dccn_list, 269 | 'all_minions_rej': dc_hosts 270 | } 271 | ) 272 | 273 | @login_required 274 | def deleteMinionKeys(request): 275 | ''' 276 | 删除已经接受的minion keys; 277 | :param request: 278 | :return: 279 | ''' 280 | 281 | minion_id = request.GET.get('minion_id') 282 | minion_id_strings = minion_id.strip(',') 283 | 284 | sapi = SaltAPI( 285 | url=settings.SALT_API['url'], 286 | username=settings.SALT_API['user'], 287 | password=settings.SALT_API['password']) 288 | ret = sapi.deleteKeys(minion_id_strings) 289 | 290 | return HttpResponseRedirect(reverse('keys_show')) 291 | 292 | @login_required 293 | def acceptMinionKeys(request): 294 | ''' 295 | Master将待接受的minion keys接受; 296 | :param request: 297 | :return: 298 | ''' 299 | minion_id = request.GET.get('minion_id') 300 | minion_id_strings = ','.join(minion_id_list) 301 | sapi = SaltAPI( 302 | url=settings.SALT_API['url'], 303 | username=settings.SALT_API['user'], 304 | password=settings.SALT_API['password']) 305 | ret = sapi.acceptKeys(minion_id_strings) 306 | 307 | return HttpResponseRedirect(reverse('keys_unaccept')) 308 | 309 | @login_required 310 | def deleteRejectKeys(request): 311 | ''' 312 | Master删除已经拒绝的minion keys; 313 | :param request: 314 | :return: 315 | ''' 316 | minion_id_list = request.GET.getlist('rejectkeys') 317 | minion_id_strings = ','.join(minion_id_list) 318 | 319 | sapi = SaltAPI( 320 | url=settings.SALT_API['url'], 321 | username=settings.SALT_API['user'], 322 | password=settings.SALT_API['password']) 323 | ret = sapi.deleteKeys(minion_id_strings) 324 | 325 | return HttpResponseRedirect(reverse('keys_reject')) 326 | -------------------------------------------------------------------------------- /newtest/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hasal/dzhops/fcd16adc61a941dccdaebee156b545784a5e96a8/newtest/__init__.py -------------------------------------------------------------------------------- /newtest/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /newtest/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /newtest/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /newtest/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.conf.urls import patterns, include, url 3 | from django.contrib import admin 4 | admin.autodiscover() 5 | 6 | urlpatterns = patterns( 7 | 'newtest.views', 8 | # Examples: 9 | # url(r'^$', 'oms.views.home', name='home'), 10 | # url(r'^blog/', include('blog.urls')), 11 | url(r'^test/$', 'testHtml', name='test_html'), 12 | url(r'^index/$', 'testIndex', name='test_html'), 13 | ) 14 | -------------------------------------------------------------------------------- /newtest/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.contrib.auth.decorators import login_required 3 | # Create your views here. 4 | 5 | @login_required 6 | def testHtml(request): 7 | user = request.user.username 8 | return render( 9 | request, 10 | 'test.html' 11 | ) 12 | 13 | @login_required 14 | def testIndex(request): 15 | user = request.user.username 16 | return render( 17 | request, 18 | 'anew/index.html' 19 | ) -------------------------------------------------------------------------------- /record/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hasal/dzhops/fcd16adc61a941dccdaebee156b545784a5e96a8/record/__init__.py -------------------------------------------------------------------------------- /record/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | from record.models import ReturnRecord, OperateRecord 5 | 6 | admin.site.register(ReturnRecord) 7 | admin.site.register(OperateRecord) -------------------------------------------------------------------------------- /record/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.db import models 3 | 4 | # Create your models here. 5 | 6 | class OperateRecord(models.Model): 7 | ''' 8 | 记录用户操作信息; 9 | ''' 10 | nowtime = models.DateTimeField(blank=True, null=True, verbose_name=u'操作时间') 11 | username = models.CharField(max_length=20, blank=True, verbose_name=u'用户名') 12 | user_operate = models.CharField(max_length=100, blank=True, verbose_name=u'用户操作') 13 | simple_tgt = models.CharField(max_length=30, blank=True, verbose_name=u'目标简述') 14 | jid = models.CharField(max_length=255, blank=True, verbose_name=u'jid') 15 | 16 | def __unicode__(self): 17 | return u'%s %s %s %s %s' %(self.nowtime, self.username, self.user_operate, self.simple_tgt, self.jid) 18 | 19 | class ReturnRecord(models.Model): 20 | ''' 21 | 记录用户操作返回结果信息; 22 | ''' 23 | 24 | jid = models.CharField(max_length=255, blank=True, verbose_name=u'jid') 25 | tgt_total = models.CharField(max_length=10, blank=True, verbose_name=u'目标总数') 26 | tgt_ret = models.CharField(max_length=10, blank=True, verbose_name=u'有返回结果的主机数量') 27 | tgt_succ = models.CharField(max_length=10, blank=True, verbose_name=u'成功的主机数量') 28 | tgt_fail = models.CharField(max_length=10, blank=True, verbose_name=u'失败的主机数量') 29 | tgt_unret = models.CharField(max_length=10, blank=True, verbose_name=u'未返回结果的主机数量') 30 | tgt_unret_list = models.TextField(blank=True, verbose_name=u'未返回结果的主机列表') 31 | 32 | def __unicode__(self): 33 | return u'%s %s %s %s %s %s' % (self.jid, self.tgt_total, self.tgt_ret, self.tgt_succ, self.tgt_fail, self.tgt_unret) -------------------------------------------------------------------------------- /record/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /record/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.conf.urls import patterns, include, url 3 | from django.contrib import admin 4 | admin.autodiscover() 5 | 6 | urlpatterns = patterns( 7 | 'record.views', 8 | # Examples: 9 | # url(r'^$', 'dzhops.views.home', name='home'), 10 | # url(r'^blog/', include('blog.urls')), 11 | url(r'^list/$', 'record', name='record_list'), 12 | url(r'^detail/$', 'recordDetail', name='record_detail'), 13 | ) 14 | -------------------------------------------------------------------------------- /record/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.shortcuts import render 3 | from django.contrib.auth.decorators import login_required 4 | from django.core.paginator import Paginator, InvalidPage, EmptyPage 5 | 6 | from record.models import OperateRecord, ReturnRecord 7 | from saltstack.util import mysqlReturns, outFormat 8 | # Create your views here. 9 | 10 | @login_required 11 | def record(request): 12 | user = request.user.username 13 | page_size = 10 14 | 15 | all_record = OperateRecord.objects.order_by('-id') 16 | paginator = Paginator(all_record, page_size) 17 | try: 18 | page = int(request.GET.get('page', 1)) 19 | except ValueError: 20 | page = 1 21 | 22 | # page_id = range(((page-1)*13+1),(page*13+1)) 23 | 24 | try: 25 | posts = paginator.page(page) 26 | except (EmptyPage, InvalidPage): 27 | posts = paginator.page(paginator.num_pages) 28 | 29 | return render( 30 | request, 31 | 'record_list.html', 32 | {'posts': posts, 33 | # 'page_id': page_id, 34 | } 35 | ) 36 | 37 | 38 | @login_required 39 | def recordDetail(request): 40 | user = request.user.username 41 | hostsft = {} 42 | 43 | if 'jid' in request.GET: 44 | job_id = request.GET.get('jid') 45 | jid_record = OperateRecord.objects.get(jid=job_id) 46 | 47 | try: 48 | jidStatus = ReturnRecord.objects.get(jid=job_id) 49 | hostsft['sum'] = jidStatus.tgt_total 50 | hostsft['rsum'] = jidStatus.tgt_ret 51 | hostsft['unre'] = jidStatus.tgt_unret 52 | hostsft['unrestr'] = jidStatus.tgt_unret_list 53 | hostsft['fa'] = jidStatus.tgt_fail 54 | hostsft['tr'] = jidStatus.tgt_succ 55 | except: 56 | hostsft['sum'] = 'Null' 57 | hostsft['rsum'] = 'Null' 58 | hostsft['unre'] = 'Null' 59 | hostsft['unrestr'] = 'Null' 60 | hostsft['fa'] = 'Null' 61 | hostsft['tr'] = 'Null' 62 | 63 | # db = db_operate() 64 | # sql = 'select id,`return` from salt_returns where jid=%s' 65 | # jid_result = db.select_table(settings.RETURNS_MYSQL, sql, str(job_id)) 66 | jid_result = mysqlReturns(job_id) 67 | ret, hostfa, hosttr = outFormat(jid_result) 68 | 69 | else: 70 | jid_record = '' 71 | ret = {} 72 | 73 | return render( 74 | request, 75 | 'record_detail.html', 76 | {'jid_record': jid_record, 77 | 'hostsft': hostsft, 78 | 'ret': ret 79 | } 80 | ) 81 | -------------------------------------------------------------------------------- /replacedata/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hasal/dzhops/fcd16adc61a941dccdaebee156b545784a5e96a8/replacedata/__init__.py -------------------------------------------------------------------------------- /replacedata/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | from replacedata.models import StockExchage,StockIndex 5 | 6 | # Register your models here. 7 | 8 | admin.site.register(StockExchage) 9 | admin.site.register(StockIndex) 10 | -------------------------------------------------------------------------------- /replacedata/forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django import forms 3 | import re 4 | 5 | class RedataAllForms(forms.Form): 6 | ''' 7 | forms for data_replace_all.html 8 | ''' 9 | 10 | data_source = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control'})) 11 | data_path = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control'})) 12 | target_server = forms.CharField(required=False, widget=forms.TextInput(attrs={'class': 'form-control'})) 13 | 14 | def clean_data_source(self): 15 | ''' 16 | checkout ip address lawful 17 | :return: 18 | ''' 19 | pattern = re.compile( 20 | r'(\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b)') 21 | data_source = self.cleaned_data['data_source'] 22 | mth = pattern.match(data_source) 23 | if not mth: 24 | raise forms.ValidationError(u"IP地址格式不合法!") 25 | return data_source 26 | -------------------------------------------------------------------------------- /replacedata/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.db import models 3 | 4 | # Create your models here. 5 | 6 | class StockExchage(models.Model): 7 | ''' 8 | All Stock Exchanges; 9 | ''' 10 | 11 | stkexchen = models.CharField(max_length=6, blank = True, verbose_name = u'股票市场英文简称') 12 | stkexchcn = models.CharField(max_length=20, blank = True, verbose_name = u'股票市场中文名称') 13 | 14 | def __unicode__(self): 15 | return u'%s %s' % (self.stkexchen, self.stkexchcn) 16 | 17 | class StockIndex(models.Model): 18 | ''' 19 | stock index to stock exchange 20 | ''' 21 | stkindex = models.CharField(max_length=30, blank = True, verbose_name = u'指数名称') 22 | exchange = models.CharField(max_length=10, blank = True, verbose_name = u'所在市场') 23 | 24 | def __unicode__(self): 25 | return u'%s %s' % (self.stkindex, self.exchange) -------------------------------------------------------------------------------- /replacedata/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /replacedata/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.conf.urls import patterns, include, url 3 | from django.contrib import admin 4 | admin.autodiscover() 5 | 6 | urlpatterns = patterns( 7 | 'replacedata.views', 8 | # url(r'^$', 'oms.views.home', name='home'), 9 | # url(r'^blog/', include('blog.urls')), 10 | url(r'^repair/history/$', 'repairHistoryData', name='repair_data'), 11 | url(r'^api/history/$', 'repairHistoryDataAPI', name='repair_data_api'), 12 | ) 13 | -------------------------------------------------------------------------------- /replacedata/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.shortcuts import render 3 | from django.http import HttpResponse 4 | from django.contrib.auth.decorators import login_required 5 | 6 | from replacedata.models import StockExchage 7 | from hostlist.models import DataCenter 8 | from record.models import OperateRecord, ReturnRecord 9 | from saltstack.saltapi import SaltAPI 10 | from saltstack.util import outFormat, datacenterToMinionID, findJob, mysqlReturns, manageResult, moduleDetection, moduleLock, moduleUnlock 11 | from dzhops import settings 12 | 13 | import os, time, logging, json 14 | 15 | # Create your views here. 16 | log = logging.getLogger('dzhops') 17 | 18 | @login_required 19 | def repairHistoryData(request): 20 | ''' 21 | 本功能最初设计目的是补错误的历史数据。 22 | :param request: 23 | :return: 24 | ''' 25 | user = request.user.username 26 | dc_list = [] 27 | data_centers = {} 28 | stock_exchanges = [] 29 | 30 | result_dc = DataCenter.objects.all() 31 | for dc in result_dc: 32 | dc_list.append(dc.dcen) 33 | data_centers[dc.dcen] = dc.dccn 34 | dc_list.sort() 35 | 36 | result_stk = StockExchage.objects.all() 37 | for stkcode in result_stk: 38 | stock_exchanges.append(stkcode.stkexchen) 39 | 40 | return render( 41 | request, 42 | 'repair_history_data.html', 43 | { 44 | 'dc_list': dc_list, 45 | 'data_centers': data_centers, 46 | 'stock_exchanges': stock_exchanges 47 | } 48 | ) 49 | 50 | @login_required 51 | def repairHistoryDataAPI(request): 52 | ''' 53 | 54 | :param request: 55 | :return: 56 | ''' 57 | user = request.user.username 58 | data_path = '/srv/salt/dzh_store/mobileserver/DATA/' 59 | state_module = 'state.sls' 60 | get_errors = [] 61 | errors = [] 62 | result_dict = {} 63 | 64 | if request.method == 'GET': 65 | module_detection = moduleDetection(state_module, user) 66 | if module_detection: 67 | get_errors.append(module_detection) 68 | if not request.GET.get('datacenter', ''): 69 | get_errors.append(u'需要指定目标机房,才能允许后续操作!') 70 | if not request.GET.get('stockexchange', ''): 71 | get_errors.append(u'亲,需要指定本次将要补数据的市场!') 72 | if not request.GET.get('sls', ''): 73 | get_errors.append(u'行情程序需要重启吗?请选择之一!') 74 | if not os.path.exists(data_path): 75 | get_errors.append(u'目录:{0} 不存在!'.format(data_path)) 76 | log.error("The data path:{0} not exist.".format(data_path)) 77 | 78 | if get_errors: 79 | for error in get_errors: 80 | errors.append(error.encode('utf8')) 81 | result_dict['errors'] = errors 82 | else: 83 | get_dc_str = request.GET.get('datacenter') 84 | get_exch_str = request.GET.get('stockexchange') 85 | get_sls = request.GET.get('sls') 86 | 87 | dc_clean = get_dc_str.strip(',') 88 | getdclist = dc_clean.split(',') 89 | exch_clean = get_exch_str.strip(',') 90 | getexchlist = exch_clean.split(',') 91 | getstatesls = get_sls 92 | 93 | if get_dc_str: 94 | result_host_set = datacenterToMinionID(getdclist) 95 | else: 96 | result_host_set = set([]) 97 | 98 | stkexch_set = set(os.listdir(data_path)) 99 | clear_dir = stkexch_set.difference(set(getexchlist)) 100 | for exchcode in clear_dir: 101 | day_path = os.path.join(data_path, exchcode, 'history/day') 102 | day_files = os.listdir(day_path) 103 | if day_files: 104 | for dyfile in day_files: 105 | dyfile_path = os.path.join(day_path,dyfile) 106 | os.remove(dyfile_path) 107 | log.info('Delete Other Market Success') 108 | 109 | sapi = SaltAPI( 110 | url=settings.SALT_API['url'], 111 | username=settings.SALT_API['user'], 112 | password=settings.SALT_API['password']) 113 | 114 | module_lock = moduleLock(state_module, user) 115 | if '*' in getdclist: 116 | jid = sapi.asyncMasterToMinion(getstatesls) 117 | else: 118 | tgt_list_to_str = ','.join(list(result_host_set)) 119 | jid = sapi.asyncMasterToMinion(tgt_list_to_str,getstatesls) 120 | module_unlock = moduleUnlock(state_module, user) 121 | 122 | if getexchlist: 123 | operate_tgt = getexchlist[0] 124 | else: 125 | operate_tgt = 'unknown' 126 | 127 | op_time = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(time.time())) 128 | op_user = getstatesls 129 | op_tgt = '%s...' % operate_tgt 130 | p1 = OperateRecord.objects.create( 131 | nowtime=op_time, 132 | username=user, 133 | user_operate=op_user, 134 | simple_tgt=op_tgt, 135 | jid=jid) 136 | 137 | find_job = findJob(result_host_set,jid) 138 | result = mysqlReturns(jid) 139 | 140 | ret, hostfa, hosttr = outFormat(result) 141 | 142 | recv_ips_list = ret.keys() 143 | send_recv_info = manageResult(result_host_set, recv_ips_list) 144 | send_recv_info['succeed'] = hosttr 145 | send_recv_info['failed'] = hostfa 146 | saveRecord = ReturnRecord.objects.create( 147 | jid=jid, 148 | tgt_total=send_recv_info['send_count'], 149 | tgt_ret=send_recv_info['recv_count'], 150 | tgt_succ=send_recv_info['succeed'], 151 | tgt_fail=send_recv_info['failed'], 152 | tgt_unret=send_recv_info['unrecv_count'], 153 | tgt_unret_list=send_recv_info['unrecv_strings'] 154 | ) 155 | result_dict['result'] = ret 156 | result_dict['info'] = send_recv_info 157 | ret_json = json.dumps(result_dict) 158 | 159 | return HttpResponse(ret_json, content_type='application/json') 160 | -------------------------------------------------------------------------------- /saltstack/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hasal/dzhops/fcd16adc61a941dccdaebee156b545784a5e96a8/saltstack/__init__.py -------------------------------------------------------------------------------- /saltstack/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.contrib import admin 3 | 4 | # Register your models here. 5 | from saltstack.models import DangerCommand, ModulesLock, DeployModules, ConfigUpdate, CommonOperate 6 | 7 | admin.site.register(DangerCommand) 8 | admin.site.register(ModulesLock) 9 | admin.site.register(DeployModules) 10 | admin.site.register(ConfigUpdate) 11 | admin.site.register(CommonOperate) 12 | -------------------------------------------------------------------------------- /saltstack/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.db import models 3 | 4 | # Create your models here. 5 | 6 | 7 | class DangerCommand(models.Model): 8 | ''' 9 | 将危险的命令存进该库,远程执行命令功能会来该表取数据进行对比; 10 | ''' 11 | command = models.CharField(max_length=50, unique=True, verbose_name=u'命令') 12 | status = models.CharField(max_length=20, verbose_name=u'状态') 13 | 14 | def __unicode__(self): 15 | return u'{0} {1}'.format(self.command, self.status) 16 | 17 | 18 | class ModulesLock(models.Model): 19 | ''' 20 | 模块是否有人正在使用相关信息保存在该表; 21 | ''' 22 | module = models.CharField(max_length=30, blank=True, verbose_name=u'模块名称') 23 | status = models.CharField(max_length=30, blank=True, verbose_name=u'使用状态') 24 | user = models.CharField(max_length=30, blank=True, verbose_name=u'用户') 25 | 26 | def __unicode__(self): 27 | return u'%s %s %s' % (self.module, self.status, self.user) 28 | 29 | 30 | class DeployModules(models.Model): 31 | ''' 32 | 程序部署sls文件表; 33 | ''' 34 | slsfile = models.CharField( 35 | max_length=255, blank=True, verbose_name=u'sls文件') 36 | module = models.CharField(max_length=30, blank=True, verbose_name=u'模块名称') 37 | 38 | def __unicode__(self): 39 | return u'%s %s' % (self.slsfile, self.module) 40 | 41 | 42 | class ConfigUpdate(models.Model): 43 | ''' 44 | 配置更新sls文件表; 45 | ''' 46 | slsfile = models.CharField( 47 | max_length=255, blank=True, verbose_name=u'sls文件') 48 | module = models.CharField(max_length=30, blank=True, verbose_name=u'模块名称') 49 | 50 | def __unicode__(self): 51 | return u'%s %s' % (self.slsfile, self.module) 52 | 53 | 54 | class CommonOperate(models.Model): 55 | ''' 56 | 日常维护sls文件表; 57 | ''' 58 | slsfile = models.CharField( 59 | max_length=255, blank=True, verbose_name=u'sls文件') 60 | module = models.CharField(max_length=30, blank=True, verbose_name=u'模块名称') 61 | 62 | def __unicode__(self): 63 | return u'%s %s' % (self.slsfile, self.module) 64 | 65 | 66 | class Jids(models.Model): 67 | jid = models.CharField(primary_key=True, max_length=255) 68 | load = models.TextField() 69 | 70 | class Meta: 71 | managed = False 72 | db_table = 'jids' 73 | 74 | def __unicode__(self): 75 | return u'%s %s' % (self.jid, self.load) 76 | 77 | 78 | class SaltReturns(models.Model): 79 | useless = models.AutoField(primary_key=True) 80 | fun = models.CharField(max_length=50) 81 | jid = models.CharField(max_length=255) 82 | # Field renamed because it was a Python reserved word. 83 | return_field = models.TextField(db_column='return') 84 | id = models.CharField(max_length=255) 85 | success = models.CharField(max_length=10) 86 | full_ret = models.TextField() 87 | alter_time = models.DateTimeField() 88 | 89 | class Meta: 90 | managed = False 91 | db_table = 'salt_returns' 92 | 93 | def __unicode__(self): 94 | return u'%s %s %s' % (self.jid, self.id, self.return_field) 95 | -------------------------------------------------------------------------------- /saltstack/saltapi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import urllib2, urllib, json 4 | 5 | 6 | class SaltAPI(object): 7 | def __init__(self, url, username, password): 8 | self.__url = url.rstrip('/') 9 | self.__user = username 10 | self.__password = password 11 | self.__token_id = self.saltLogin() 12 | 13 | def saltLogin(self): 14 | params = {'eauth': 'pam', 'username': self.__user, 'password': self.__password} 15 | encode = urllib.urlencode(params) 16 | obj = urllib.unquote(encode) 17 | headers = {'X-Auth-Token': ''} 18 | url = self.__url + '/login' 19 | req = urllib2.Request(url, obj, headers) 20 | opener = urllib2.urlopen(req) 21 | content = json.loads(opener.read()) 22 | try: 23 | token = content['return'][0]['token'] 24 | return token 25 | except KeyError: 26 | raise KeyError 27 | 28 | def postRequest(self, obj, prefix='/'): 29 | url = self.__url + prefix 30 | headers = {'X-Auth-Token': self.__token_id} 31 | req = urllib2.Request(url, obj, headers) 32 | opener = urllib2.urlopen(req) 33 | content = json.loads(opener.read()) 34 | return content 35 | 36 | def asyncMasterToMinion(self, tgt, fun, arg): 37 | ''' 38 | 异步执行,当target为部分minion时,Master操作Minion; 39 | :param target: 目标服务器ID组成的字符串; 40 | :param fun: 使用的salt模块,如state.sls, cmd.run 41 | :param arg: 传入的命令或sls文件 42 | :return: jid字符串 43 | ''' 44 | if tgt == '*': 45 | params = {'client': 'local_async', 'tgt': tgt, 'fun': fun, 'arg': arg} 46 | else: 47 | params = {'client': 'local_async', 'tgt': tgt, 'fun': fun, 'arg': arg, 'expr_form': 'list'} 48 | obj = urllib.urlencode(params) 49 | content = self.postRequest(obj) 50 | jid = content['return'][0]['jid'] 51 | return jid 52 | 53 | def masterToMinionContent(self, tgt, fun, arg): 54 | ''' 55 | Master控制Minion,返回的结果是内容,不是jid; 56 | 目标参数tgt是一个如下格式的字符串:'*' 或 'zhaogb-201, zhaogb-202, zhaogb-203, ...' 57 | ''' 58 | if tgt == '*': 59 | params = {'client': 'local', 'tgt': tgt, 'fun': fun, 'arg': arg} 60 | else: 61 | params = {'client': 'local', 'tgt': tgt, 'fun': fun, 'arg': arg, 'expr_form': 'list'} 62 | obj = urllib.urlencode(params) 63 | content = self.postRequest(obj) 64 | result = content['return'][0] 65 | return result 66 | 67 | def allMinionKeys(self): 68 | ''' 69 | 返回所有Minion keys; 70 | 分别为 已接受、待接受、已拒绝; 71 | :return: [u'local', u'minions_rejected', u'minions_denied', u'minions_pre', u'minions'] 72 | ''' 73 | params = {'client': 'wheel', 'fun': 'key.list_all'} 74 | obj = urllib.urlencode(params) 75 | content = self.postRequest(obj) 76 | minions = content['return'][0]['data']['return']['minions'] 77 | minions_pre = content['return'][0]['data']['return']['minions_pre'] 78 | minions_rej = content['return'][0]['data']['return']['minions_rejected'] 79 | return minions, minions_pre, minions_rej 80 | 81 | def actionKyes(self, keystrings, action): 82 | ''' 83 | 对Minion keys 进行指定处理; 84 | :param keystrings: 将要处理的minion id字符串; 85 | :param action: 将要进行的处理,如接受、拒绝、删除; 86 | :return: 87 | {"return": [{"tag": "salt/wheel/20160322171740805129", "data": {"jid": "20160322171740805129", "return": {}, "success": true, "_stamp": "2016-03-22T09:17:40.899757", "tag": "salt/wheel/20160322171740805129", "user": "zhaogb", "fun": "wheel.key.delete"}}]} 88 | ''' 89 | func = 'key.' + action 90 | params = {'client': 'wheel', 'fun': func, 'match': keystrings} 91 | obj = urllib.urlencode(params) 92 | content = self.postRequest(obj) 93 | ret = content['return'][0]['data']['success'] 94 | return ret 95 | 96 | def acceptKeys(self, keystrings): 97 | ''' 98 | 接受Minion发过来的key; 99 | :return: 100 | ''' 101 | params = {'client': 'wheel', 'fun': 'key.accept', 'match': keystrings} 102 | obj = urllib.urlencode(params) 103 | content = self.postRequest(obj) 104 | ret = content['return'][0]['data']['success'] 105 | return ret 106 | 107 | def deleteKeys(self, keystrings): 108 | ''' 109 | 删除Minion keys; 110 | :param node_name: 111 | :return: 112 | ''' 113 | params = {'client': 'wheel', 'fun': 'key.delete', 'match': keystrings} 114 | obj = urllib.urlencode(params) 115 | content = self.postRequest(obj) 116 | ret = content['return'][0]['data']['success'] 117 | return ret 118 | -------------------------------------------------------------------------------- /saltstack/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /saltstack/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.conf.urls import patterns, include, url 3 | from django.contrib import admin 4 | admin.autodiscover() 5 | 6 | urlpatterns = patterns( 7 | 'saltstack.views', 8 | # Examples: 9 | # url(r'^$', 'dzhops.views.home', name='home'), 10 | # url(r'^blog/', include('blog.urls')), 11 | url(r'^execute/$', 'remoteExecute', name='execute'), 12 | url(r'^deploy/$', 'deployProgram', name='deploy'), 13 | url(r'^update/$', 'updateConfig', name='update'), 14 | url(r'^routine/$', 'routineMaintenance', name='routine'), 15 | url(r'^api/execute/$', 'remoteExecuteApi', name='execute_api'), 16 | url(r'^api/deploy/$', 'deployProgramApi', name='deploy_api'), 17 | ) 18 | -------------------------------------------------------------------------------- /saltstack/util.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from hostlist.models import HostList, DataCenter 4 | from saltstack.models import SaltReturns 5 | from saltstack.models import ModulesLock 6 | from saltstack.saltapi import SaltAPI 7 | from dzhops import settings 8 | 9 | import logging, time, json, re 10 | 11 | log = logging.getLogger('dzhops') 12 | 13 | def moduleDetection(module, user): 14 | ''' 15 | 检测如state.sls/cmd.run等模块是否被占用; 16 | :param module: 'cmd.run' 或 'state.sls' 17 | :param user: 'zhaogb' 或其他用户名; 18 | :return: 如果模块被占用,则返回如 "zhaogb is using cmd.run";如果模块没有被占用,则返回空字符串; 19 | ''' 20 | log.debug('%s detection module %s occupied' % (user, module)) 21 | try: 22 | module_exist = ModulesLock.objects.get(module=module) 23 | module_status = module_exist.status 24 | module_user = module_exist.user 25 | if module_status == 'True': 26 | status = '%s is using module %s' % (module_user, module) 27 | log.info(status) 28 | elif module_status == 'False': 29 | status = '' 30 | log.info("Nobody uses module %s" % module) 31 | else: 32 | pass 33 | except ModulesLock.DoesNotExist: 34 | status = '' 35 | log.info("The %s module has never been used" % module) 36 | 37 | return status 38 | 39 | 40 | def moduleLock(module, user): 41 | ''' 42 | 将模块锁定; 43 | :param module: 'cmd.run' 或 'state.sls' 44 | :param user: 'zhaogb' 或其他用户名; 45 | :return: None 46 | ''' 47 | log.debug('%s Lock Module : %s' % (user, module)) 48 | try: 49 | module_exist = ModulesLock.objects.get(module=module) 50 | module_status = module_exist.status 51 | if module_status == 'False': 52 | module_exist.status = 'True' 53 | module_exist.user = user 54 | module_exist.save() 55 | log.info("%s Lock Module %s Successed!" % (user, module)) 56 | else: 57 | log.info("Someone could use this module %s" % module) 58 | except ModulesLock.DoesNotExist: 59 | log.info("The %s module has never been used" % module) 60 | module_lock = ModulesLock.objects.create(module=module, status='True', user=user) 61 | log.info("%s Lock Module %s Successed!" % (user, module)) 62 | 63 | 64 | def moduleUnlock(module, user): 65 | ''' 66 | 将锁定的模块解锁; 67 | :param module: 'cmd.run' 或 'state.sls' 68 | :param user: 'zhaogb' 或其他用户名; 69 | :return: None 70 | ''' 71 | log.debug('%s Unlock Module %s' % (user, module)) 72 | module_unlock = ModulesLock.objects.get(module=module) 73 | module_unlock.status = 'False' 74 | module_unlock.user = '' 75 | module_unlock.save() 76 | log.info('%s unlock module %s successed!' % (user, module)) 77 | 78 | def targetToMinionID(tgt): 79 | ''' 80 | 将服务器IP或MinionID字符串转换成由MinionID组成的集合; 81 | 如果传过来‘*’,会返回全部MinionKeys组成的列表,将用于计算目标主机数量; 82 | :param tgt: 'zhaogb-201, zhaogb-203,...' or 'zhaogb-*' or 'zh*' or '10.15.*' or 'z*,10*' or '*'; 83 | :return:1.如果‘*’在tgt中,则返回全部MinionKeys组成的列表; 84 | 2.如果'zhaogb-*' or '10.10.* '在tgt中, 返回转换、正则匹配到的相关minionid集合; 85 | ''' 86 | 87 | target_list = tgt.split(',') 88 | minion_id_list = [] 89 | all_minion_ip_list = [] 90 | all_minion_id_list = [] 91 | minion_ip_to_id_list = [] 92 | minion_id_to_id_list = [] 93 | 94 | if '*' in target_list: 95 | result_data = HostList.objects.all().values_list("minionid") 96 | for row_data in result_data: 97 | minion_id = row_data[0] 98 | minion_id_list.append(minion_id) 99 | minion_id_set = set(minion_id_list) 100 | else: 101 | all_minion_info_data = HostList.objects.all() 102 | for minion_info in all_minion_info_data: 103 | all_minion_ip_list.append(minion_info.ip) 104 | all_minion_id_list.append(minion_info.minionid) 105 | 106 | for target in target_list: 107 | if '*' in target: 108 | target_replace_point = target.replace('.', '\.') 109 | target_replace_star = target_replace_point.replace('*', '.*') 110 | target_string = r'%s' % target_replace_star 111 | pattern = re.compile(target_string) 112 | for minion_ip in all_minion_ip_list: 113 | match_ip = pattern.match(minion_ip) 114 | if match_ip: 115 | mtach_minion_ip_data = HostList.objects.get(ip=minion_ip) 116 | match_minion_ip_to_id = mtach_minion_ip_data.minionid 117 | minion_ip_to_id_list.append(match_minion_ip_to_id) 118 | for minion_id in all_minion_id_list: 119 | match_id = pattern.match(minion_id) 120 | if match_id: 121 | minion_id_to_id_list.append(minion_id) 122 | else: 123 | target_replace_none = target.replace('.','') 124 | if target_replace_none.isdigit(): 125 | try: 126 | mtach_minion_ip_data = HostList.objects.get(ip=target) 127 | match_minion_ip_to_id = mtach_minion_ip_data.minionid 128 | minion_ip_to_id_list.append(match_minion_ip_to_id) 129 | except HostList.DoesNotExist: 130 | log.error('Without this IP on host list. IP:{0}'.format(target)) 131 | else: 132 | try: 133 | mtach_minion_id_data = HostList.objects.get(minionid=target) 134 | minion_ip_to_id_list.append(target) 135 | except HostList.DoesNotExist: 136 | log.error("MinionID don't exsit. Minion id:{0}".format(target)) 137 | 138 | minion_ip_to_id_set = set(minion_ip_to_id_list) 139 | minion_id_to_id_set = set(minion_id_to_id_list) 140 | minion_id_set = minion_ip_to_id_set.union(minion_id_to_id_set) 141 | 142 | return minion_id_set 143 | 144 | def datacenterToMinionID(datacenter_list): 145 | ''' 146 | 由机房名称组成的列表转换成由MinionID组成的集合; 147 | 如果传过来‘*’,会返回全部MinionKeys组成的列表,将用于计算目标主机数量; 148 | **注意**: **这里没有判断传过来的列表是否为空,请调用该函数之前自己判断** 149 | :param dc_list: ['dctest1', 'dctest2', 'dctest3', ...] or ['*'] 150 | :return: a set, set(['zhaogb-201', 'zhaogb-202', 'zhaogb-203', ..., 'zhaogb-nnn']) 151 | ''' 152 | all_mininon_id_list = [] 153 | if '*' in datacenter_list: 154 | result_data = HostList.objects.all().values_list("minionid") 155 | for row_data in result_data: 156 | minion_id = row_data[0] 157 | all_mininon_id_list.append(minion_id) 158 | else: 159 | for dc in datacenter_list: 160 | result_data_dccn = DataCenter.objects.get(dcen=dc) 161 | result_data_hosts = HostList.objects.filter(dccn=result_data_dccn.dccn) 162 | for row_data in result_data_hosts: 163 | minion_id = row_data.minionid 164 | all_mininon_id_list.append(minion_id) 165 | 166 | minion_id_set = set(all_mininon_id_list) 167 | 168 | return minion_id_set 169 | 170 | def findJob(minionids_set, jid): 171 | ''' 172 | 173 | :return: 174 | ''' 175 | target_list_to_str = ','.join(list(minionids_set)) 176 | log.debug('target_list: %s' % str(target_list_to_str)) 177 | fun = 'saltutil.find_job' 178 | diff_send_receive = [] 179 | loop = True 180 | 181 | sapi = SaltAPI( 182 | url=settings.SALT_API['url'], 183 | username=settings.SALT_API['user'], 184 | password=settings.SALT_API['password']) 185 | 186 | while loop: 187 | counter = 0 188 | # log.debug('The loop variable') 189 | log.debug('into loop start [common.views.findJob]') 190 | time.sleep(10) 191 | find_job_result = sapi.masterToMinionContent(target_list_to_str, fun, jid) 192 | log.debug('find_job_result: %s' % str(find_job_result)) 193 | find_job_result_set = set(find_job_result.keys()) 194 | diff_send_receive.extend(list(minionids_set.difference(find_job_result_set))) 195 | find_job_result_value = find_job_result.values() 196 | for eachDict in find_job_result_value: 197 | if eachDict: 198 | log.debug('The find job result is Dict, It is values list is Not null.') 199 | break 200 | else: 201 | counter += 1 202 | if counter == len(find_job_result_set): 203 | loop = False 204 | 205 | diff_send_receive_set = set(diff_send_receive) 206 | 207 | return diff_send_receive_set 208 | 209 | def mysqlReturns(jid): 210 | ''' 211 | 212 | :param jid: u'20160217142922771111' 213 | :return:{'host1':{'dict_content'},'host2':{'dict_content'},...} 214 | ''' 215 | jid = jid.encode('utf-8') 216 | dict_result = {} 217 | try: 218 | log.debug('Query the database salt_returns for %s' % jid) 219 | return_data = SaltReturns.objects.filter(jid=jid) 220 | for row in return_data: 221 | data_return = json.loads(row.return_field) 222 | dict_result[row.id] = data_return 223 | except BaseException, e: 224 | log.error(str(e)) 225 | 226 | return dict_result 227 | 228 | def outFormat(result): 229 | ''' 230 | 将从数据中获取到的结果,进行格式化输出; 231 | :param result: 232 | #result = { 233 | # 'zhaogb-201': 234 | # {'file_|-info_so_1_|-/usr/lib64/libodbc.so.1_|-managed': 235 | # {'comment': 'zhaogb-201', 'name': '/usr/lib64/libodbc.so.1', 236 | # 'start_time': 'zhaogb-201', 237 | # 'result': True, 238 | # 'duration': 'zhaogb-201', 239 | # '__run_num__': 2, 240 | # 'changes': {} 241 | # } 242 | # }, 243 | # 'zhaogb-202': 244 | # {'file_|-info_so_1_|-/usr/lib64/libodbc.so.1_|-managed': 245 | # {'comment': 'zhaogb-202', 246 | # 'name': 'zhaogb-202', 247 | # 'start_time': 'zhaogb-202', 248 | # 'result': True, 249 | # 'duration': 'zhaogb-202', 250 | # '__run_num__': 2, 251 | # 'changes': 252 | # {'diff': 'New file', 253 | # 'mode': '0644' 254 | # } 255 | # } 256 | # } 257 | # } 258 | :return: 多维列表,形如:[ 259 | [ip1', {'status': value1, 'cont': longstrings1}], 260 | [ip2', {'status': value2, 'cont': longstrings2}], 261 | ... ... 262 | ] 263 | ''' 264 | hostfa = 0 265 | hosttr = 0 266 | unret = {} 267 | 268 | for ka, va in result.iteritems(): 269 | # result {'zhaogb-201':{},'zhaogb-202':{}} 270 | # ka zhaogb-201,zhaogb-202 271 | # va {'mo_watch':{'comment':'','result':'',...}} 272 | valcon = {} 273 | longstrva = '' 274 | falseStatus = 0 275 | trueStatus = 0 276 | liv = [] 277 | 278 | minion_data = HostList.objects.get(minionid=ka) 279 | minion_ip = minion_data.ip 280 | 281 | if isinstance(va, dict): 282 | for kva in va.keys(): 283 | # kva mo_watch,... 284 | liva = kva.split('_|-') 285 | liv.append(va[kva]['result']) 286 | 287 | if va[kva]['changes']: 288 | changesStr = '' 289 | if liva[0] == 'file': 290 | if va[kva]['changes'].keys(): 291 | if ('diff' in va[kva]['changes'].keys() 292 | and va[kva]['changes']['diff'] != ''): 293 | changesStr += u'\n\t对比 : \n\t\t{0}'.format( 294 | va[kva]['changes']['diff']) 295 | if ('mode' in va[kva]['changes'].keys() 296 | and va[kva]['changes']['mode'] != ''): 297 | changesStr += u'\n\t权限 : \n\t\t{0}'.format( 298 | va[kva]['changes']['mode']) 299 | if ('diff' not in va[kva]['changes'].keys() 300 | and 'mode' not in va[kva]['changes'].keys()): 301 | for ck, cv in va[kva]['changes'].iteritems(): 302 | changesStr += u'\n\t{0}'.format(ck) 303 | if ('diff' in va[kva]['changes'][ck].keys() 304 | and va[kva]['changes'][ck]['diff'] != ''): 305 | changesStr += u'\n\t\t对比 : \n\t\t\t{0}'.format(cv['diff']) 306 | if ('mode' in va[kva]['changes'][ck].keys() 307 | and va[kva]['changes'][ck]['mode'] != ''): 308 | changesStr += u'\n\t\t权限 : \n\t\t\t{0}'.format(cv['mode']) 309 | 310 | elif liva[0] == 'cmd': 311 | if ('pid' in va[kva]['changes'].keys() 312 | and va[kva]['changes']['pid'] != ''): 313 | changesStr += u'\n\tPID : {0}'.format(va[kva]['changes']['pid']) 314 | if ('retcode' in va[kva]['changes'].keys() 315 | and va[kva]['changes']['retcode'] != ''): 316 | changesStr += u'\n\t返回代码 : {0}'.format(va[kva]['changes']['retcode']) 317 | if ('stderr' in va[kva]['changes'].keys() 318 | and va[kva]['changes']['stderr'] != ''): 319 | changesStr += u'\n\t错误 : {0}'.format(va[kva]['changes']['stderr']) 320 | if ('stdout' in va[kva]['changes'].keys() 321 | and va[kva]['changes']['stdout'] != ''): 322 | changesStr += u'\n\t输出 : {0}'.format(va[kva]['changes']['stdout']) 323 | else: 324 | pass 325 | va[kva]['changes'] = changesStr.encode('utf8') 326 | else: 327 | pass 328 | 329 | strva = '结果 : {0}\n标签 : {1}\n操作 : {2}\n开始 : {3}\n耗时 : {4} ms\n变动 : {5}\n{6}\n'.format( 330 | va[kva]['result'], 331 | liva[1], 332 | va[kva]['comment'], 333 | va[kva]['start_time'], 334 | va[kva]['duration'], 335 | va[kva]['changes'], 336 | '-' * 60) 337 | longstrva += strva 338 | 339 | if False in liv: 340 | colour = 'False' 341 | hostfa += 1 342 | elif True in liv: 343 | colour = 'True' 344 | hosttr += 1 345 | else: 346 | pass 347 | # error write to logging 348 | 349 | totalStatus = len(liv) 350 | for livStatus in liv: 351 | if livStatus == False: 352 | falseStatus += 1 353 | elif livStatus == True: 354 | trueStatus += 1 355 | else: 356 | pass 357 | # error write to logging 358 | 359 | longstrva += '失败 : {0}\n成功 : {1}\n总计 : {2}'.format(falseStatus, trueStatus, totalStatus) 360 | 361 | valcon['status'] = colour 362 | valcon['cont'] = longstrva 363 | unret[minion_ip] = valcon 364 | else: 365 | valcon['status'] = 'False' 366 | valcon['cont'] = str(va) 367 | unret[minion_ip] = valcon 368 | 369 | # 结果排序工作改为由前端处理,此处注释 370 | # result_keys_list = unret.keys() 371 | # result_keys_list.sort() 372 | # log.debug('Result keys list sorted: {0}'.format(str(result_keys_list))) 373 | # for ip in result_keys_list: 374 | # content = unret.get(ip) 375 | # sub_list = [ip, content] 376 | # result.append(sub_list) 377 | 378 | return unret, hostfa, hosttr 379 | 380 | def manageResult(send_ids_set, recv_ips_list): 381 | ''' 382 | 计算目标客户端数量,回收结果数量,未返回结果的数量,未返回结果的IP列表; 383 | :param send_ids_set: 384 | :param recv_ips_list: 385 | :return: {digit, digit, digit, strings} 386 | ''' 387 | diff_ip_list = [] 388 | info_keys = ('send_count', 'recv_count', 'unrecv_count', 'unrecv_strings') 389 | send_ids_count = len(send_ids_set) 390 | recv_ips_count = len(recv_ips_list) 391 | if send_ids_count == recv_ips_count: 392 | info_values = [send_ids_count, recv_ips_count, 0, ''] 393 | send_recv_info = dict(zip(info_keys, info_values)) 394 | elif recv_ips_count == 0: 395 | unrecv_ip_list = [] 396 | for i in send_ids_set: 397 | data = HostList.objects.get(minionid=i) 398 | unrecv_ip_list.append(data.ip) 399 | unrecv_strings = ', '.join(unrecv_ip_list) 400 | info_values = [send_ids_count, 0, send_ids_count, unrecv_strings] 401 | send_recv_info = dict(zip(info_keys, info_values)) 402 | else: 403 | recv_ids_list = [] 404 | for i in recv_ips_list: 405 | minion_data = HostList.objects.get(ip=i) 406 | minion_id = minion_data.minionid 407 | recv_ids_list.append(minion_id) 408 | recv_ids_set = set(recv_ids_list) 409 | diff_set = send_ids_set.difference(recv_ids_set) 410 | unrecv_count = len(diff_set) 411 | for i in diff_set: 412 | data = HostList.objects.get(minionid=i) 413 | diff_ip_list.append(data.ip) 414 | unrecv_strings = ', '.join(diff_ip_list) 415 | info_values = [send_ids_count, recv_ips_count, unrecv_count, unrecv_strings] 416 | send_recv_info = dict(zip(info_keys, info_values)) 417 | return send_recv_info -------------------------------------------------------------------------------- /saltstack/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.shortcuts import render 3 | from django.http import HttpResponse 4 | from django.contrib.auth.decorators import login_required 5 | 6 | from hostlist.models import DataCenter 7 | from record.models import OperateRecord, ReturnRecord 8 | from saltstack.models import DangerCommand, DeployModules, ConfigUpdate, CommonOperate 9 | from saltstack.saltapi import SaltAPI 10 | from saltstack.util import targetToMinionID, datacenterToMinionID, findJob, mysqlReturns, outFormat, manageResult, \ 11 | moduleDetection, moduleLock, moduleUnlock 12 | from dzhops import settings 13 | 14 | import logging, json, time, copy 15 | 16 | # Create your views here. 17 | 18 | log = logging.getLogger('dzhops') 19 | 20 | 21 | @login_required 22 | def deployProgram(request): 23 | ''' 24 | 部署程序,如部署行情程序、监控程序、账号代理程序等; 25 | :param request: 26 | 目标Minions或机房、组; 27 | 需要执行的sls文件 28 | :return: 29 | {'minion ip':{'cont':'format result','status': colour },...} , 30 | hostsft:{'sum':'','rsum':'','unre':'','unrestr':'','fa':'','tr':''} 31 | ''' 32 | user = request.user.username 33 | dcen_list = [] 34 | sls_list = [] 35 | data_centers = {} 36 | sls_mod_dict = {} 37 | 38 | result_dc = DataCenter.objects.all() 39 | for dc in result_dc: 40 | dcen_list.append(dc.dcen) 41 | data_centers[dc.dcen] = dc.dccn 42 | dcen_list.sort() 43 | 44 | result_sls = DeployModules.objects.all() 45 | for row_data in result_sls: 46 | sls_mod_dict[row_data.slsfile] = row_data.module 47 | sls_list.append(row_data.slsfile) 48 | sls_list.sort() 49 | 50 | return render( 51 | request, 52 | 'salt_deploy.html', 53 | { 54 | 'dcen_list': dcen_list, 55 | 'data_centers': data_centers, 56 | 'sls_list': sls_list, 57 | 'sls_mod_dict': sls_mod_dict 58 | } 59 | ) 60 | 61 | @login_required 62 | def updateConfig(request): 63 | ''' 64 | 配置更新,如行情配置更新、class文件更新、ssh配置更新等; 65 | :param request: 66 | :return: 67 | ''' 68 | user = request.user.username 69 | dcen_list = [] 70 | data_centers = {} 71 | sls_list = [] 72 | sls_mod_dict = {} 73 | 74 | result_dc = DataCenter.objects.all() 75 | for dc in result_dc: 76 | dcen_list.append(dc.dcen) 77 | data_centers[dc.dcen] = dc.dccn 78 | dcen_list.sort() 79 | 80 | result_sls = ConfigUpdate.objects.all() 81 | for row_data in result_sls: 82 | sls_mod_dict[row_data.slsfile] = row_data.module 83 | sls_list.append(row_data.slsfile) 84 | sls_list.sort() 85 | 86 | return render( 87 | request, 88 | 'salt_update.html', 89 | { 90 | 'dcen_list': dcen_list, 91 | 'data_centers': data_centers, 92 | 'sls_list': sls_list, 93 | 'sls_mod_dict': sls_mod_dict 94 | } 95 | ) 96 | 97 | @login_required 98 | def routineMaintenance(request): 99 | ''' 100 | 日常维护操作,比如日志清理、批量重启程序等; 101 | :param request: 102 | :return: 103 | ''' 104 | user = request.user.username 105 | dcen_list = [] 106 | data_centers = {} 107 | sls_list = [] 108 | sls_mod_dict = {} 109 | 110 | result_dc = DataCenter.objects.all() 111 | for dc in result_dc: 112 | dcen_list.append(dc.dcen) 113 | data_centers[dc.dcen] = dc.dccn 114 | dcen_list.sort() 115 | 116 | result_sls = CommonOperate.objects.all() 117 | for row_data in result_sls: 118 | sls_mod_dict[row_data.slsfile] = row_data.module 119 | sls_list.append(row_data.slsfile) 120 | 121 | return render( 122 | request, 123 | 'salt_routine.html', 124 | { 125 | 'dcen_list': dcen_list, 126 | 'data_centers': data_centers, 127 | 'sls_list': sls_list, 128 | 'sls_mod_dict': sls_mod_dict 129 | } 130 | ) 131 | 132 | @login_required 133 | def deployProgramApi(request): 134 | ''' 135 | 模块部署功能,前端页面提交的数据由该函数处理,执行完毕后返回json格式数据到api; 136 | :param request: 137 | :return: 138 | ''' 139 | user = request.user.username 140 | salt_module = 'state.sls' 141 | get_errors = [] 142 | errors = [] 143 | result_dict = {} 144 | 145 | if request.method == 'GET': 146 | check_tgt = request.GET.get('tgt', '') 147 | check_dc_list = request.GET.get('datacenter', '') 148 | check_arg = request.GET.get('sls', '') 149 | 150 | module_detection = moduleDetection(salt_module, user) 151 | 152 | if module_detection: 153 | get_errors.append(module_detection) 154 | log.debug('{0}'.format(str(module_detection))) 155 | if not (check_tgt or check_dc_list): 156 | get_errors.append(u'需要输入服务器IP或选择机房!') 157 | log.error('Did not enter servers ip or choose data center.') 158 | if not check_arg: 159 | get_errors.append(u'请选择将要进行的操作!') 160 | log.error('Not select the file of salt state.') 161 | 162 | if get_errors: 163 | for error in get_errors: 164 | errors.append(error.encode('utf-8')) 165 | result_dict['errors'] = errors 166 | else: 167 | tgt = request.GET.get('tgt', '') 168 | dc = request.GET.get('datacenter', '') 169 | arg = request.GET.get('sls', '') 170 | 171 | dc_clean = dc.strip(',') 172 | log.debug(str(dc_clean)) 173 | dc_list = dc_clean.split(',') 174 | target_list = tgt.split(',') 175 | tgt_mixture_list = copy.deepcopy(dc_list) 176 | tgt_mixture_list.extend(target_list) 177 | 178 | if tgt: 179 | minion_id_from_tgt_set = targetToMinionID(tgt) 180 | else: 181 | minion_id_from_tgt_set = set([]) 182 | if dc: 183 | log.debug(str(dc_list)) 184 | minion_id_from_dc_set = datacenterToMinionID(dc_list) 185 | else: 186 | minion_id_from_dc_set = set([]) 187 | all_minion_id_set = minion_id_from_tgt_set.union(minion_id_from_dc_set) 188 | # log.debug('The all target minion id set: {0}'.format(str(all_minion_id_set))) 189 | 190 | if all_minion_id_set: 191 | sapi = SaltAPI( 192 | url=settings.SALT_API['url'], 193 | username=settings.SALT_API['user'], 194 | password=settings.SALT_API['password']) 195 | 196 | module_lock = moduleLock(salt_module, user) 197 | 198 | if '*' in tgt_mixture_list: 199 | jid = sapi.asyncMasterToMinion('*', salt_module, arg) 200 | else: 201 | tgt_list_to_str = ','.join(list(all_minion_id_set)) 202 | jid = sapi.asyncMasterToMinion(tgt_list_to_str, salt_module, arg) 203 | 204 | if dc_list: 205 | operate_tgt = dc_list[0] 206 | elif tgt: 207 | operate_tgt = target_list[0] 208 | else: 209 | operate_tgt = 'unknown' 210 | 211 | op_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) 212 | op_user = arg 213 | op_tgt = '%s...' % operate_tgt 214 | p1 = OperateRecord.objects.create( 215 | nowtime=op_time, 216 | username=user, 217 | user_operate=op_user, 218 | simple_tgt=op_tgt, 219 | jid=jid) 220 | 221 | find_job = findJob(all_minion_id_set, jid) 222 | result = mysqlReturns(jid) 223 | module_unlock = moduleUnlock(salt_module, user) 224 | ret, hostfa, hosttr = outFormat(result) 225 | 226 | # log.debug(str(ret)) 227 | recv_ips_list = ret.keys() 228 | send_recv_info = manageResult(all_minion_id_set, recv_ips_list) 229 | send_recv_info['succeed'] = hosttr 230 | send_recv_info['failed'] = hostfa 231 | saveRecord = ReturnRecord.objects.create( 232 | jid=jid, 233 | tgt_total=send_recv_info['send_count'], 234 | tgt_ret=send_recv_info['recv_count'], 235 | tgt_succ=send_recv_info['succeed'], 236 | tgt_fail=send_recv_info['failed'], 237 | tgt_unret=send_recv_info['unrecv_count'], 238 | tgt_unret_list=send_recv_info['unrecv_strings'] 239 | ) 240 | result_dict['result'] = ret 241 | result_dict['info'] = send_recv_info 242 | else: 243 | log.info('The all target minion id set is Null.') 244 | set_null = u'数据库中没有找到输入的主机,请确认输入是否正确!' 245 | result_dict['errors'] = set_null.encode('utf-8') 246 | ret_json = json.dumps(result_dict) 247 | 248 | return HttpResponse(ret_json, content_type='application/json') 249 | 250 | @login_required 251 | def remoteExecute(request): 252 | ''' 253 | 通过SaltStack cmd.run模块,对Salt minion远程执行命令; 254 | :param request: None 255 | :return: 256 | ''' 257 | user = request.user.username 258 | dcen_list = [] 259 | data_centers = {} 260 | 261 | result_dc = DataCenter.objects.all() 262 | for dc in result_dc: 263 | dcen_list.append(dc.dcen) 264 | data_centers[dc.dcen] = dc.dccn 265 | dcen_list.sort() 266 | 267 | return render( 268 | request, 269 | 'salt_execute.html', 270 | {'dcen_list': dcen_list, 'data_centers': data_centers} 271 | ) 272 | 273 | 274 | @login_required 275 | def remoteExecuteApi(request): 276 | ''' 277 | 远程执行的命令通过JQuery(ajax)提交到这里,处理后返回结果json; 278 | :param request: 279 | :return: 280 | ''' 281 | user = request.user.username 282 | get_errors = [] 283 | errors = [] 284 | result_dict = {} 285 | danger_cmd_list = [] 286 | 287 | danger_cmd_data = DangerCommand.objects.filter(status='True') 288 | if danger_cmd_data: 289 | for i in danger_cmd_data: 290 | danger_cmd_list.append(i.command) 291 | else: 292 | log.debug('The table of DangerCommand is Null.') 293 | 294 | if request.method == 'GET': 295 | check_tgt = request.GET.get('tgt', '') 296 | check_dc_list = request.GET.get('datacenter', '') 297 | check_arg = request.GET.get('arg', '') 298 | 299 | module_detection = moduleDetection('cmd.run', user) 300 | 301 | if module_detection: 302 | get_errors.append(module_detection) 303 | if not (check_tgt or check_dc_list): 304 | get_errors.append(u'需要指定目标主机或目标机房!') 305 | if not check_arg: 306 | get_errors.append(u'请输入将要执行的命令!') 307 | else: 308 | if danger_cmd_list: 309 | arg_list = check_arg.split(';') 310 | for i in arg_list: 311 | try: 312 | command = i.split()[0] 313 | except IndexError, e: 314 | log.debug('Command ends with a semicolon, Error info: {0}.'.format(str(e))) 315 | continue 316 | for j in danger_cmd_list: 317 | if j in command: 318 | get_errors.append(u'%s 命令危险,不允许使用!' % command) 319 | else: 320 | log.debug('Databases has not danger command') 321 | 322 | if get_errors: 323 | for error in get_errors: 324 | errors.append(error.encode('utf-8')) 325 | result_dict['errors'] = errors 326 | else: 327 | tgt = request.GET.get('tgt', '') 328 | dc = request.GET.get('datacenter', '') 329 | arg = request.GET.get('arg', '') 330 | 331 | dc_clean = dc.strip(',') 332 | log.debug(str(dc_clean)) 333 | dc_list = dc_clean.split(',') 334 | target_list = tgt.split(',') 335 | tgt_mixture_list = copy.deepcopy(dc_list) 336 | tgt_mixture_list.extend(target_list) 337 | 338 | if tgt: 339 | minion_id_from_tgt_set = targetToMinionID(tgt) 340 | else: 341 | minion_id_from_tgt_set = set([]) 342 | if dc: 343 | log.debug(str(dc_list)) 344 | minion_id_from_dc_set = datacenterToMinionID(dc_list) 345 | else: 346 | minion_id_from_dc_set = set([]) 347 | all_minion_id_set = minion_id_from_tgt_set.union(minion_id_from_dc_set) 348 | log.debug('The all target minion id set: {0}'.format(str(all_minion_id_set))) 349 | 350 | if all_minion_id_set: 351 | sapi = SaltAPI( 352 | url=settings.SALT_API['url'], 353 | username=settings.SALT_API['user'], 354 | password=settings.SALT_API['password']) 355 | 356 | module_lock = moduleLock('cmd.run', user) 357 | 358 | if '*' in tgt_mixture_list: 359 | jid = sapi.asyncMasterToMinion('*', 'cmd.run', arg) 360 | else: 361 | tgt_list_to_str = ','.join(list(all_minion_id_set)) 362 | jid = sapi.asyncMasterToMinion(tgt_list_to_str, 'cmd.run', arg) 363 | 364 | if dc_list: 365 | operate_tgt = dc_list[0] 366 | elif tgt: 367 | operate_tgt = target_list[0] 368 | else: 369 | operate_tgt = 'unknown' 370 | 371 | op_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) 372 | op_user = arg 373 | op_tgt = '%s...' % operate_tgt 374 | p1 = OperateRecord.objects.create( 375 | nowtime=op_time, 376 | username=user, 377 | user_operate=op_user, 378 | simple_tgt=op_tgt, 379 | jid=jid) 380 | 381 | find_job = findJob(all_minion_id_set, jid) 382 | result = mysqlReturns(jid) 383 | module_unlock = moduleUnlock('cmd.run', user) 384 | ret, hostfa, hosttr = outFormat(result) 385 | 386 | # log.debug(str(ret)) 387 | recv_ips_list = ret.keys() 388 | send_recv_info = manageResult(all_minion_id_set, recv_ips_list) 389 | saveRecord = ReturnRecord.objects.create( 390 | jid=jid, 391 | tgt_total=send_recv_info['send_count'], 392 | tgt_ret=send_recv_info['recv_count'], 393 | tgt_unret=send_recv_info['unrecv_count'], 394 | tgt_unret_list=send_recv_info['unrecv_strings'] 395 | ) 396 | result_dict['result'] = ret 397 | result_dict['info'] = send_recv_info 398 | else: 399 | log.info('The all target minion id set is Null.') 400 | set_null = u'数据库中没有找到输入的主机,请确认输入是否正确!' 401 | result_dict['errors'] = set_null.encode('utf-8') 402 | ret_json = json.dumps(result_dict) 403 | 404 | return HttpResponse(ret_json, content_type='application/json') 405 | -------------------------------------------------------------------------------- /scripts/data_acquisition.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # Guibin created on 2015/10/21 4 | # version: 0.01 5 | 6 | from __future__ import division 7 | import MySQLdb 8 | import subprocess 9 | import math 10 | from decimal import Decimal 11 | 12 | # mysql 13 | host = '数据库地址' 14 | db = '库名' 15 | user = '用户' 16 | pw = '密码' 17 | port = 端口 18 | 19 | # snmp command 20 | sysone = "snmpget -v 2c -c public localhost .1.3.6.1.4.1.2021.10.1.3.1 |cut -d ':' -f 4|tr -d '[:blank:]'" 21 | sysfive = "snmpget -v 2c -c public localhost .1.3.6.1.4.1.2021.10.1.3.2 |cut -d ':' -f 4|tr -d '[:blank:]'" 22 | sysfifteen = "snmpget -v 2c -c public localhost .1.3.6.1.4.1.2021.10.1.3.3 |cut -d ':' -f 4|tr -d '[:blank:]'" 23 | 24 | cpuidle = "snmpget -v 2c -c public localhost .1.3.6.1.4.1.2021.11.11.0 |cut -d ':' -f 4|tr -d '[:blank:]'" 25 | 26 | memtotal = "snmpget -v 2c -c public localhost .1.3.6.1.4.1.2021.4.5.0 |cut -d ':' -f 4|tr -d '[:blank:]'" 27 | memfree = "snmpget -v 2c -c public localhost .1.3.6.1.4.1.2021.4.6.0 |cut -d ':' -f 4|tr -d '[:blank:]'" 28 | 29 | disktotal = "snmpget -v 2c -c public localhost .1.3.6.1.4.1.2021.9.1.6.1 |cut -d ':' -f 4|tr -d '[:blank:]'" 30 | diskused = "snmpget -v 2c -c public localhost .1.3.6.1.4.1.2021.9.1.8.1 |cut -d ':' -f 4|tr -d '[:blank:]'" 31 | diskperc = "snmpget -v 2c -c public localhost .1.3.6.1.4.1.2021.9.1.9.1 |cut -d ':' -f 4|tr -d '[:blank:]'" 32 | 33 | # exec function 34 | def ExecSnmp(getsnmp): 35 | child = subprocess.Popen(getsnmp, shell=True, stdout=subprocess.PIPE) 36 | child.wait() 37 | middle_res = child.stdout.read() 38 | result = middle_res.strip('\n') 39 | return result 40 | 41 | def SaveMy(sql): 42 | conn = MySQLdb.connect( 43 | host = host, 44 | user = user, 45 | passwd = pw, 46 | db = db, 47 | port = port, 48 | charset='utf8') 49 | cursor = conn.cursor() 50 | try: 51 | cursor.execute(sql) 52 | conn.commit() 53 | 54 | except MySQLdb.Error,e: 55 | # Rollback in case there is any error 56 | #mysqlErro = "Mysql Error %d: %s" % (e.args[0], e.args[1]) 57 | conn.rollback() 58 | 59 | #get monitor data 60 | sysload1 = ExecSnmp(sysone) 61 | sysload5 = ExecSnmp(sysfive) 62 | sysload15 = ExecSnmp(sysfifteen) 63 | 64 | cpuidl = ExecSnmp(cpuidle) 65 | cpuused = str(100 - int(cpuidl)) 66 | 67 | memtol = ExecSnmp(memtotal) 68 | memfre = ExecSnmp(memfree) 69 | memperc = str(100 - int(round(int(memfre[:-2])/int(memtol[:-2])*100))) 70 | memtolg = str(int(math.ceil(int(memtol[:-2])/1024/1024))) 71 | memusdg = Decimal(str(round((int(memtol[:-2]) - int(memfre[:-2]))/1024/1024,2))) 72 | 73 | disktol = ExecSnmp(disktotal) 74 | diskusd = ExecSnmp(diskused) 75 | diskpr = ExecSnmp(diskperc) 76 | 77 | dktotal = str(int(math.ceil(int(disktol)/1024/1024))) 78 | dkused = Decimal(str(round(int(diskusd)/1024/1024,1))) 79 | 80 | #save to mysql 81 | serv_sql = '''INSERT INTO `index_servstatus` 82 | (`nowtime`,`sysone`,`sysfive`,`sysfifteen`,`cpuperc`,`memtotal`,`memused`,`memperc`,`disktotal`,`diskused`,`diskperc`) 83 | VALUES 84 | (now(), %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)''' 85 | conn = MySQLdb.connect( 86 | host = host, 87 | user = user, 88 | passwd = pw, 89 | db = db, 90 | port = port, 91 | charset='utf8') 92 | cursor = conn.cursor() 93 | try: 94 | cursor.execute(serv_sql, (sysload1,sysload5,sysload15,cpuused,memtolg,memusdg,memperc,dktotal,dkused,diskpr)) 95 | conn.commit() 96 | 97 | except MySQLdb.Error,e: 98 | # Rollback in case there is any error 99 | #mysqlErro = "Mysql Error %d: %s" % (e.args[0], e.args[1]) 100 | conn.rollback() 101 | 102 | #print 'system load : %s, %s, %s' % (sysload1, sysload5, sysload15) 103 | #print 'CPU used perc: %d%%' % cpuused 104 | #print 'Mem used perc: %d%%' % memperc 105 | #print 'Mem : %s/%d' % (memusdg, memtolg) 106 | #print 'Disk : %s/%d %s%%' % (dktotal, dkused, diskpr) 107 | 108 | -------------------------------------------------------------------------------- /scripts/prc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding: utf-8 3 | 4 | import subprocess 5 | import MySQLdb 6 | 7 | def ExecCmd(cmd): 8 | child = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE) 9 | retno = child.wait() 10 | return retno 11 | 12 | # mysql 13 | host = '数据库地址' 14 | db = '库名' 15 | user = '用户' 16 | pw = '密码' 17 | port = 端口 18 | 19 | saltcmd = 'ps -ef|grep salt-master|grep -v grep' 20 | apicmd = 'ps -ef|grep salt-api|grep -v grep' 21 | mycmd = 'ps -ef|grep mysql|grep -v grep' 22 | snmpcmd = 'ps -ef|grep snmpd|grep -v grep' 23 | 24 | saltproc = ExecCmd(saltcmd) 25 | apiproc = ExecCmd(apicmd) 26 | myproc = ExecCmd(mycmd) 27 | snmproc = ExecCmd(snmpcmd) 28 | 29 | #print saltproc,apiproc,myproc,snmproc 30 | 31 | #save to mysql 32 | proc_sql = '''INSERT INTO `index_procstatus` 33 | (`nowtime`,`saltproc`,`apiproc`,`myproc`,`snmproc`) 34 | VALUES 35 | (now(), %s, %s, %s, %s)''' 36 | conn = MySQLdb.connect( 37 | host = host, 38 | user = user, 39 | passwd = pw, 40 | db = db, 41 | port = port, 42 | charset='utf8') 43 | cursor = conn.cursor() 44 | try: 45 | cursor.execute(proc_sql, (saltproc, apiproc, myproc, snmproc)) 46 | conn.commit() 47 | 48 | except MySQLdb.Error,e: 49 | # Rollback in case there is any error 50 | #mysqlErro = "Mysql Error %d: %s" % (e.args[0], e.args[1]) 51 | conn.rollback() 52 | -------------------------------------------------------------------------------- /scripts/slapi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # Guibin created on 2015/10/26 4 | # version: 0.01 5 | 6 | import sys,MySQLdb,urllib 7 | sys.path.append('/opt/dzhops') 8 | from dzhops import settings 9 | from common.saltapi import SaltAPI 10 | 11 | class scriptApi(SaltAPI): 12 | def mini_status(self): 13 | ''' 14 | {u'return': [{u'down': [u'zhaogb-202'], u'up': [u'zhaogb-203', u'zhaogb-212', u'zhaogb-220']}]} 15 | ''' 16 | params = {"client":"runner","fun":"manage.status"} 17 | obj = urllib.urlencode(params) 18 | content = self.postRequest(obj) 19 | mini_up = content['return'][0]['up'] 20 | mini_down = content['return'][0]['down'] 21 | return mini_up,mini_down 22 | ret = {} 23 | try: 24 | sapi = scriptApi(url=settings.SALT_API['url'],username=settings.SALT_API['user'],password=settings.SALT_API['password']) 25 | minions,minions_pre,minions_rej = sapi.list_all_key() 26 | mini_up, mini_down = sapi.mini_status() 27 | 28 | ret['num_mini'] = len(minions) 29 | ret['num_minipre'] = len(minions_pre) 30 | ret['num_minirej'] = len(minions_rej) 31 | 32 | ret['num_miniup'] = len(mini_up) 33 | ret['num_minidown'] = len(mini_down) 34 | ret['num_miniall'] = ret['num_miniup'] + ret['num_minidown'] 35 | 36 | except Exception,e: 37 | print str(e) 38 | ret['num_mini'] = '0' 39 | ret['num_minipre'] = '0' 40 | ret['num_minirej'] = '0' 41 | 42 | ret['num_miniup'] = '0' 43 | ret['num_minidown'] = '0' 44 | ret['num_miniall'] = '0' 45 | 46 | conf = settings.DATABASES['default'] 47 | mikey_sql = '''INSERT INTO `index_minikeys` 48 | (`nowtime`,`miniall`,`minion`,`miniout`,`keyall`,`keypre`,`keyrej`) 49 | VALUES 50 | (now(), %s, %s, %s, %s, %s, %s)''' 51 | 52 | conn = MySQLdb.connect( 53 | host = conf['HOST'], 54 | user = conf['USER'], 55 | passwd = conf['PASSWORD'], 56 | db = conf['NAME'], 57 | port = conf['PORT'], 58 | charset='utf8') 59 | cursor = conn.cursor() 60 | try: 61 | cursor.execute(mikey_sql, (ret['num_miniall'],ret['num_miniup'],ret['num_minidown'],ret['num_mini'],ret['num_minipre'],ret['num_minirej'])) 62 | conn.commit() 63 | 64 | except MySQLdb.Error,e: 65 | # Rollback in case there is any error 66 | #mysqlErro = "Mysql Error %d: %s" % (e.args[0], e.args[1]) 67 | conn.rollback() 68 | -------------------------------------------------------------------------------- /static/bootstrap/3.3.4/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hasal/dzhops/fcd16adc61a941dccdaebee156b545784a5e96a8/static/bootstrap/3.3.4/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /static/bootstrap/3.3.4/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hasal/dzhops/fcd16adc61a941dccdaebee156b545784a5e96a8/static/bootstrap/3.3.4/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /static/bootstrap/3.3.4/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hasal/dzhops/fcd16adc61a941dccdaebee156b545784a5e96a8/static/bootstrap/3.3.4/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /static/bootstrap/3.3.4/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hasal/dzhops/fcd16adc61a941dccdaebee156b545784a5e96a8/static/bootstrap/3.3.4/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /static/bootstrap/3.3.4/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /static/img/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hasal/dzhops/fcd16adc61a941dccdaebee156b545784a5e96a8/static/img/8.png -------------------------------------------------------------------------------- /static/img/button.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hasal/dzhops/fcd16adc61a941dccdaebee156b545784a5e96a8/static/img/button.gif -------------------------------------------------------------------------------- /static/img/dataloading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hasal/dzhops/fcd16adc61a941dccdaebee156b545784a5e96a8/static/img/dataloading.gif -------------------------------------------------------------------------------- /static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hasal/dzhops/fcd16adc61a941dccdaebee156b545784a5e96a8/static/img/favicon.ico -------------------------------------------------------------------------------- /static/img/zhaogb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hasal/dzhops/fcd16adc61a941dccdaebee156b545784a5e96a8/static/img/zhaogb.jpg -------------------------------------------------------------------------------- /static/otherjs/dzhops.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Guibin on 2016/3/19. 3 | */ 4 | 5 | //实现复选框全选与反选; 6 | function checkAll(chkall) { 7 | if(chkall.checked){ 8 | $("input[name='minion_id']").each(function(){this.checked=true;}); 9 | }else{ 10 | $("input[name='minion_id']").each(function(){this.checked=false;}); 11 | } 12 | } 13 | 14 | //获取复选框的值 15 | function getCheckValue(action) { 16 | var minion_id = ""; 17 | $('input[name="minion_id"]:checked').each(function(){ 18 | minion_id += $(this).val() + ','; 19 | }); 20 | if (minion_id === "") { 21 | alert("您尚未选择任何选项,请重新选择后提交!"); 22 | return false; 23 | } else { 24 | var url = '/keys/' + action + '/' 25 | $.getJSON(url, {"minion_id": minion_id, "action": action}, function(result) { 26 | if (result === true) { 27 | var ret = deleteTbodyTr(minion_id); 28 | if (ret === true) { 29 | alert('操作成功!') 30 | } else { 31 | alert('错误1:操作失败!') 32 | } 33 | 34 | } else { 35 | alert('错误2:操作失败!') 36 | } 37 | }); 38 | } 39 | } 40 | 41 | //将'aaa,bbb,ccc,'转换为数组,并以此删除以此为id的元素; 42 | function deleteTbodyTr(minion_id) { 43 | var minion_id_str = minion_id.slice(0,-1); 44 | var minion_id_array = minion_id_str.split(','); 45 | $.each(minion_id_array, function(index,value) { 46 | var tbody_tr_id = '#' + value; 47 | $(tbody_tr_id).remove(); 48 | }); 49 | return true; 50 | } 51 | 52 | function saltExecute() { 53 | // $.ajaxSettings.async = false; 54 | $(document).ready(function(){ 55 | $('#result').html(""); 56 | $('#info').html("") 57 | var tgt = $("input[name='tgt']").val(); 58 | var arg = $("input[name='arg']").val(); 59 | var datacenter = ""; 60 | $('input[name="datacenter"]:checked').each(function(){ 61 | datacenter += $(this).val() + ','; 62 | }); 63 | if ((tgt === '' || tgt === null) && (datacenter === '' || datacenter === null)) { 64 | alert('请输入服务器IP或选择对应机房!'); 65 | return false; 66 | } 67 | if (arg === '' || arg === null) { 68 | alert('请输入将要执行的命令!'); 69 | return false; 70 | } 71 | $("#execapi").attr("disabled","disabled"); 72 | $("#execapi").html(''); 73 | $.getJSON("/salt/api/execute/",{'tgt':tgt,'datacenter':datacenter, 'arg': arg}, function(ret){ 74 | if (ret.hasOwnProperty('errors')) { 75 | alert(ret.errors); 76 | $("#execapi").removeAttr("disabled"); 77 | $("#execapi").html("提交"); 78 | return false; 79 | } else { 80 | if (ret.info.unrecv_count===0) { 81 | $('#info').html("本次执行对象共"+ret.info.send_count+"台,其中"+ret.info.recv_count+"台返回结果;" 82 | ); 83 | } else { 84 | $('#info').html("本次执行对象共" + ret.info.send_count + "台,其中" + ret.info.recv_count + "台返回结果;未返回结果的有以下" + ret.info.unrecv_count + "台:
" + ret.info.unrecv_strings 85 | ); 86 | } 87 | var sortArray = []; 88 | $.each(ret.result, function(key, val) { sortArray[sortArray.length] = key;}); 89 | sortArray.sort(); 90 | $.each(sortArray, function(i, key) { 91 | $("#result").append( 92 | "

" + key + "

"+ret['result'][key]['cont']+"
" 93 | ); 94 | }); 95 | }; 96 | $("#execapi").removeAttr("disabled"); 97 | $("#execapi").html("提交"); 98 | }); 99 | }); 100 | // $.ajaxSettings.async = true; 101 | } 102 | 103 | function saltFunc() { 104 | //$.ajaxSettings.async = false; 105 | $(document).ready(function(){ 106 | $("#info").html(""); 107 | $("#result").html(""); 108 | var tgt = $("input[name='tgt']").val(); 109 | var datacenter = ""; 110 | var arg = ""; 111 | $("input[name='datacenter']:checked").each(function(){ 112 | datacenter += $(this).val() + ','; 113 | }); 114 | $("input[name='sls']:checked").each(function(){ 115 | arg = $(this).val(); 116 | }); 117 | if ((tgt === '' || tgt === null) && (datacenter === '' || datacenter === null)) { 118 | alert('请输入服务器IP或选择对应机房!'); 119 | return false; 120 | }; 121 | if (arg === '' || arg === null) { 122 | alert('请选择将要进行的操作!'); 123 | return false; 124 | }; 125 | $("#salt_submit").attr("disabled","disabled"); 126 | $("#salt_submit").html(''); 127 | $.getJSON("/salt/api/deploy/", {"tgt":tgt, "datacenter":datacenter, "sls":arg}, function(ret){ 128 | if (ret.hasOwnProperty("errors")) { 129 | alert(ret.errors); 130 | $("#salt_submit").removeAttr("disabled"); 131 | $("#salt_submit").html("提交"); 132 | return false; 133 | } else { 134 | if (ret.info.unrecv_count === 0) { 135 | $("#info").html('\ 136 |
本次执行对象'+ret.info.send_count+'台,\ 137 | 共'+ret.info.recv_count+'台返回结果,\ 138 | 其中成功'+ret.info.succeed+'台,\ 139 | 失败'+ret.info.failed+'台;
' 140 | ); 141 | } else { 142 | $("#info").html('\ 143 |
本次执行对象'+ret.info.send_count+'台,\ 144 | 共'+ret.info.recv_count+'台返回结果,\ 145 | 其中成功'+ret.info.succeed+'台,\ 146 | 失败'+ret.info.failed+'台;\ 147 | 未返回结果的有以下'+ret.info.unrecv_count+'台:\ 148 |
'+ ret.info.unrecv_strings+';
' 149 | ); 150 | } 151 | } 152 | var sortArray = []; 153 | $.each(ret.result, function(key, value) { 154 | sortArray[sortArray.length] = key; 155 | }); 156 | sortArray.sort(); 157 | $.each(sortArray, function(i, key) { 158 | var ip_nodot = key.replace(/\./g,''); 159 | if (ret['result'][key]['status'] === 'True') { 160 | $("#result").append('\ 161 |
\ 162 | \ 163 |
\ 164 | ' 180 | ); 181 | } else { 182 | $("#result").append('\ 183 |
\ 184 | \ 185 |
\ 186 | '); 202 | }; 203 | }); 204 | $("#salt_submit").removeAttr("disabled"); 205 | $("#salt_submit").html("提交"); 206 | }); 207 | }); 208 | //$.ajaxSettings.async = true; 209 | } 210 | 211 | //修复历史数据 212 | function repairHistoryData() { 213 | $("#info").html(""); 214 | $("#result").html(""); 215 | var datacenter = ""; 216 | var stockexchange = ""; 217 | var sls = ""; 218 | $("input[name='datacenter']:checked").each(function(){ 219 | datacenter += $(this).val() + ','; 220 | }); 221 | $("input[name='stockexchange']:checked").each(function(){ 222 | stockexchange += $(this).val() + ','; 223 | }); 224 | $("input[name='sls']:checked").each(function(){ 225 | sls = $(this).val(); 226 | }); 227 | if (datacenter === '' || datacenter === null) { 228 | alert('请选择将要操作的机房!'); 229 | return false; 230 | } 231 | if (stockexchange === '' || stockexchange === null) { 232 | alert('请选择将要补数据的市场!'); 233 | return false; 234 | } 235 | if (sls === '' || sls === null) { 236 | alert('请选择补过数据以后是否需要重启行情程序!'); 237 | return false; 238 | } 239 | $("#submit_history").attr("disabled","disabled"); 240 | $("#submit_history").html(''); 241 | $.getJSON("/data/api/history/", {"datacenter":datacenter, "stockexchange":stockexchange, "sls": sls}, function(ret){ 242 | if (ret.hasOwnProperty("errors")) { 243 | alert(ret.errors); 244 | $("#submit_history").removeAttr("disabled"); 245 | $("#submit_history").html("提交"); 246 | return false; 247 | } else { 248 | if (ret.info.unrecv_count === 0) { 249 | $("#info").html('\ 250 |
本次执行对象'+ret.info.send_count+'台,\ 251 | 共'+ret.info.recv_count+'台返回结果,\ 252 | 其中成功'+ret.info.succeed+'台,\ 253 | 失败'+ret.info.failed+'台;
' 254 | ); 255 | } else { 256 | $("#info").html('\ 257 |
本次执行对象'+ret.info.send_count+'台,\ 258 | 共'+ret.info.recv_count+'台返回结果,\ 259 | 其中成功'+ret.info.succeed+'台,\ 260 | 失败'+ret.info.failed+'台;\ 261 | 未返回结果的有以下'+ret.info.unrecv_count+'台:\ 262 |
'+ ret.info.unrecv_strings+'
' 263 | ); 264 | } 265 | } 266 | var sortArray = []; 267 | $.each(ret.result, function(key, value) { 268 | sortArray[sortArray.length] = key; 269 | }); 270 | sortArray.sort(); 271 | $.each(sortArray, function(i, key) { 272 | var ip_nodot = key.replace(/\./g,''); 273 | if (ret['result'][key]['status'] === 'True') { 274 | $("#result").append('\ 275 |
\ 276 | \ 277 |
\ 278 | ' 294 | ); 295 | } else { 296 | $("#result").append('\ 297 |
\ 298 | \ 299 |
\ 300 | '); 316 | }; 317 | }); 318 | $("#submit_history").removeAttr("disabled"); 319 | $("#submit_history").html("提交"); 320 | }); 321 | } -------------------------------------------------------------------------------- /static/otherjs/ie-emulation-modes-warning.js: -------------------------------------------------------------------------------- 1 | // NOTICE!! DO NOT USE ANY OF THIS JAVASCRIPT 2 | // IT'S JUST JUNK FOR OUR DOCS! 3 | // ++++++++++++++++++++++++++++++++++++++++++ 4 | /*! 5 | * Copyright 2014 Twitter, Inc. 6 | * 7 | * Licensed under the Creative Commons Attribution 3.0 Unported License. For 8 | * details, see http://creativecommons.org/licenses/by/3.0/. 9 | */ 10 | // Intended to prevent false-positive bug reports about Bootstrap not working properly in old versions of IE due to folks testing using IE's unreliable emulation modes. 11 | (function () { 12 | 'use strict'; 13 | 14 | function emulatedIEMajorVersion() { 15 | var groups = /MSIE ([0-9.]+)/.exec(window.navigator.userAgent) 16 | if (groups === null) { 17 | return null 18 | } 19 | var ieVersionNum = parseInt(groups[1], 10) 20 | var ieMajorVersion = Math.floor(ieVersionNum) 21 | return ieMajorVersion 22 | } 23 | 24 | function actualNonEmulatedIEMajorVersion() { 25 | // Detects the actual version of IE in use, even if it's in an older-IE emulation mode. 26 | // IE JavaScript conditional compilation docs: http://msdn.microsoft.com/en-us/library/ie/121hztk3(v=vs.94).aspx 27 | // @cc_on docs: http://msdn.microsoft.com/en-us/library/ie/8ka90k2e(v=vs.94).aspx 28 | var jscriptVersion = new Function('/*@cc_on return @_jscript_version; @*/')() // jshint ignore:line 29 | if (jscriptVersion === undefined) { 30 | return 11 // IE11+ not in emulation mode 31 | } 32 | if (jscriptVersion < 9) { 33 | return 8 // IE8 (or lower; haven't tested on IE<8) 34 | } 35 | return jscriptVersion // IE9 or IE10 in any mode, or IE11 in non-IE11 mode 36 | } 37 | 38 | var ua = window.navigator.userAgent 39 | if (ua.indexOf('Opera') > -1 || ua.indexOf('Presto') > -1) { 40 | return // Opera, which might pretend to be IE 41 | } 42 | var emulated = emulatedIEMajorVersion() 43 | if (emulated === null) { 44 | return // Not IE 45 | } 46 | var nonEmulated = actualNonEmulatedIEMajorVersion() 47 | 48 | if (emulated !== nonEmulated) { 49 | window.alert('WARNING: You appear to be using IE' + nonEmulated + ' in IE' + emulated + ' emulation mode.\nIE emulation modes can behave significantly differently from ACTUAL older versions of IE.\nPLEASE DON\'T FILE BOOTSTRAP BUGS based on testing in IE emulation modes!') 50 | } 51 | })(); 52 | -------------------------------------------------------------------------------- /static/otherjs/ie10-viewport-bug-workaround.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * IE10 viewport hack for Surface/desktop Windows 8 bug 3 | * Copyright 2014 Twitter, Inc. 4 | * Licensed under the Creative Commons Attribution 3.0 Unported License. For 5 | * details, see http://creativecommons.org/licenses/by/3.0/. 6 | */ 7 | 8 | // See the Getting Started docs for more information: 9 | // http://getbootstrap.com/getting-started/#support-ie10-width 10 | 11 | (function () { 12 | 'use strict'; 13 | if (navigator.userAgent.match(/IEMobile\/10\.0/)) { 14 | var msViewportStyle = document.createElement('style') 15 | msViewportStyle.appendChild( 16 | document.createTextNode( 17 | '@-ms-viewport{width:auto!important}' 18 | ) 19 | ) 20 | document.querySelector('head').appendChild(msViewportStyle) 21 | } 22 | })(); 23 | -------------------------------------------------------------------------------- /static/theme/cover.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Globals 3 | */ 4 | 5 | /* Links */ 6 | a, 7 | a:focus, 8 | a:hover { 9 | color: #fff; 10 | } 11 | 12 | /* Custom default button */ 13 | .btn-default, 14 | .btn-default:hover, 15 | .btn-default:focus { 16 | color: #333; 17 | text-shadow: none; /* Prevent inheritence from `body` */ 18 | background-color: #fff; 19 | border: 1px solid #fff; 20 | } 21 | 22 | 23 | /* 24 | * Base structure 25 | */ 26 | 27 | html, 28 | body { 29 | height: 100%; 30 | background-color: #333; 31 | } 32 | body { 33 | color: #fff; 34 | text-align: center; 35 | text-shadow: 0 1px 3px rgba(0,0,0,.5); 36 | } 37 | 38 | /* Extra markup and styles for table-esque vertical and horizontal centering */ 39 | .site-wrapper { 40 | display: table; 41 | width: 100%; 42 | height: 100%; /* For at least Firefox */ 43 | min-height: 100%; 44 | -webkit-box-shadow: inset 0 0 100px rgba(0,0,0,.5); 45 | box-shadow: inset 0 0 100px rgba(0,0,0,.5); 46 | } 47 | .site-wrapper-inner { 48 | display: table-cell; 49 | vertical-align: top; 50 | } 51 | .cover-container { 52 | margin-right: auto; 53 | margin-left: auto; 54 | } 55 | 56 | /* Padding for spacing */ 57 | .inner { 58 | padding: 30px; 59 | } 60 | 61 | 62 | /* 63 | * Header 64 | */ 65 | .masthead-brand { 66 | margin-top: 10px; 67 | margin-bottom: 10px; 68 | } 69 | 70 | .masthead-nav > li { 71 | display: inline-block; 72 | } 73 | .masthead-nav > li + li { 74 | margin-left: 20px; 75 | } 76 | .masthead-nav > li > a { 77 | padding-right: 0; 78 | padding-left: 0; 79 | font-size: 16px; 80 | font-weight: bold; 81 | color: #fff; /* IE8 proofing */ 82 | color: rgba(255,255,255,.75); 83 | border-bottom: 2px solid transparent; 84 | } 85 | .masthead-nav > li > a:hover, 86 | .masthead-nav > li > a:focus { 87 | background-color: transparent; 88 | border-bottom-color: #a9a9a9; 89 | border-bottom-color: rgba(255,255,255,.25); 90 | } 91 | .masthead-nav > .active > a, 92 | .masthead-nav > .active > a:hover, 93 | .masthead-nav > .active > a:focus { 94 | color: #fff; 95 | border-bottom-color: #fff; 96 | } 97 | 98 | @media (min-width: 768px) { 99 | .masthead-brand { 100 | float: left; 101 | } 102 | .masthead-nav { 103 | float: right; 104 | } 105 | } 106 | 107 | 108 | /* 109 | * Cover 110 | */ 111 | 112 | .cover { 113 | padding: 0 20px; 114 | } 115 | .cover .btn-lg { 116 | padding: 10px 20px; 117 | font-weight: bold; 118 | } 119 | 120 | 121 | /* 122 | * Footer 123 | */ 124 | 125 | .mastfoot { 126 | color: #999; /* IE8 proofing */ 127 | color: rgba(255,255,255,.5); 128 | } 129 | 130 | 131 | /* 132 | * Affix and center 133 | */ 134 | 135 | @media (min-width: 768px) { 136 | /* Pull out the header and footer */ 137 | .masthead { 138 | position: fixed; 139 | top: 0; 140 | } 141 | .mastfoot { 142 | position: fixed; 143 | bottom: 0; 144 | } 145 | /* Start the vertical centering */ 146 | .site-wrapper-inner { 147 | vertical-align: middle; 148 | } 149 | /* Handle the widths */ 150 | .masthead, 151 | .mastfoot, 152 | .cover-container { 153 | width: 100%; /* Must be percentage or pixels for horizontal alignment */ 154 | } 155 | } 156 | 157 | @media (min-width: 992px) { 158 | .masthead, 159 | .mastfoot, 160 | .cover-container { 161 | width: 700px; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /static/theme/dashboard.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Base structure 3 | */ 4 | 5 | /* Move down content because we have a fixed navbar that is 50px tall */ 6 | body { 7 | padding-top: 50px; 8 | } 9 | 10 | 11 | /* 12 | * Global add-ons 13 | */ 14 | 15 | .sub-header { 16 | padding-bottom: 10px; 17 | border-bottom: 1px solid #eee; 18 | } 19 | 20 | /* 21 | * Top navigation 22 | * Hide default border to remove 1px line. 23 | */ 24 | .navbar-fixed-top { 25 | border: 0; 26 | } 27 | 28 | /* 29 | * Sidebar 30 | */ 31 | 32 | /* Hide for mobile, show later */ 33 | .sidebar { 34 | display: none; 35 | } 36 | @media (min-width: 768px) { 37 | .sidebar { 38 | position: fixed; 39 | top: 51px; 40 | bottom: 0; 41 | left: 0; 42 | z-index: 1000; 43 | display: block; 44 | padding: 20px; 45 | overflow-x: hidden; 46 | overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ 47 | background-color: #f5f5f5; 48 | border-right: 1px solid #eee; 49 | } 50 | } 51 | 52 | /* Sidebar navigation */ 53 | .nav-sidebar { 54 | margin-right: -21px; /* 20px padding + 1px border */ 55 | margin-bottom: 20px; 56 | margin-left: -20px; 57 | } 58 | .nav-sidebar > li > a { 59 | padding-right: 20px; 60 | padding-left: 20px; 61 | } 62 | .nav-sidebar > .active > a, 63 | .nav-sidebar > .active > a:hover, 64 | .nav-sidebar > .active > a:focus { 65 | color: #fff; 66 | background-color: #428bca; 67 | } 68 | 69 | 70 | /* 71 | * Main content 72 | */ 73 | 74 | .main { 75 | padding: 20px; 76 | } 77 | @media (min-width: 768px) { 78 | .main { 79 | padding-right: 40px; 80 | padding-left: 40px; 81 | } 82 | } 83 | .main .page-header { 84 | margin-top: 0; 85 | } 86 | 87 | 88 | /* 89 | * Placeholder dashboard ideas 90 | */ 91 | 92 | .placeholders { 93 | margin-bottom: 30px; 94 | text-align: center; 95 | } 96 | .placeholders h4 { 97 | margin-bottom: 0; 98 | } 99 | .placeholder { 100 | margin-bottom: 20px; 101 | } 102 | .placeholder img { 103 | display: inline-block; 104 | border-radius: 50%; 105 | } 106 | -------------------------------------------------------------------------------- /static/theme/dzhops.css: -------------------------------------------------------------------------------- 1 | #autoNewLine { 2 | word-wrap: break-word; 3 | word-break: normal; 4 | } -------------------------------------------------------------------------------- /static/theme/theme.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 70px; 3 | padding-bottom: 30px; 4 | } 5 | 6 | .theme-dropdown .dropdown-menu { 7 | position: static; 8 | display: block; 9 | margin-bottom: 20px; 10 | } 11 | 12 | .theme-showcase > p > .btn { 13 | margin: 5px 0; 14 | } 15 | 16 | .theme-showcase .navbar .container { 17 | width: auto; 18 | } 19 | -------------------------------------------------------------------------------- /templates/asset_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}主机列表-移动终端自动化运维平台{% endblock %} 3 | {% block nav %} 4 |
  • 仪表盘
  • 5 |
  • 主机列表(current)
  • 6 |
  • SaltStack
  • 7 |
  • 数据修复
  • 8 |
  • MinionKeys
  • 9 |
  • 操作记录
  • 10 | {% endblock %} 11 | {% block content %} 12 |
    13 |
    14 |
    15 |
    16 | 22 |
    23 |
    24 | 30 |
    31 |
    32 |
    33 |
    34 |
    35 | {% if serv_list %} 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | {# #} 49 | {# #} 50 | {# #} 51 | {# #} 52 | {# #} 53 | {# #} 54 | 55 | 56 | 57 | {% for serv in serv_list %} 58 | 59 | 60 | {% for i in serv %} 61 | 62 | {% endfor %} 63 | {# #} 64 | {# #} 65 | {# #} 66 | {# #} 67 | {# #} 68 | {# #} 69 | {# #} 70 | {# #} 71 | {# #} 72 | {# #} 73 | {# #} 74 | {# #} 75 | {# #} 76 | 77 | {% endfor %} 78 | 79 |
    IDIP地址主机名MinionID运营商类别地区机房维护人员MAC地址主行情源备行情源授权日期授权状态备注
    {{ forloop.counter }}{{ i }}{{ all_host.hostname }}{{ all_host.minionid }}{{ all_host.nocn }}{{ all_host.catagorycn }}{{ all_host.pacn }}{{ all_host.dccn }}{{ all_host.engineer }}{{ all_host.macaddr }}{{ all_host.zsourceip }}{{ all_host.bsourceip }}{{ all_host.licdate }}{{ all_host.licstatus }}{{ all_host.remark }}
    80 | {% endif %} 81 |
    82 | 104 | {% endblock %} 105 | -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {% block title %}{% endblock %} 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 38 | 39 | 40 | 41 | 42 | 68 | 69 |
    70 |
    71 | 94 |
    95 | {% block content %}{% endblock %} 96 |
    97 |
    98 |
    99 | 100 | 101 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}移动终端自动化运维平台{% endblock %} 3 | {% block nav %} 4 |
  • 仪表盘(current)
  • 5 |
  • 主机列表
  • 6 |
  • SaltStack
  • 7 |
  • 数据修复
  • 8 |
  • MinionKeys
  • 9 |
  • 操作记录
  • 10 | {% endblock %} 11 | {% block content %} 12 | 17 |
    18 | 25 |
    26 |
    27 | 34 |
    35 |
    36 | 43 |
    44 |
    45 | 52 |
    53 |
    54 |
    55 |
    56 |

    操作记录

    57 |
    58 |
    59 |
    {{ stret }}
    60 |
    61 |
    62 |
    63 | {% endblock %} 64 | 65 | -------------------------------------------------------------------------------- /templates/index_upload.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}移动终端自动化运维平台{% endblock %} 3 | {% block nav %} 4 |
  • 仪表盘(current)
  • 5 |
  • 主机列表
  • 6 |
  • SaltStack
  • 7 |
  • 数据修复
  • 8 |
  • MinionKeys
  • 9 |
  • 操作记录
  • 10 | {% endblock %} 11 | {% block content %} 12 | 18 |
    19 |

    上传头像

    20 |
    21 | {% csrf_token %} 22 |
    23 | 24 |
    25 | {{ form.file }} 26 | {{ form.file.errors }} 27 |
    28 |
    29 |
    30 |
    31 | 32 |
    33 |
    34 |
    35 |
    36 | {% endblock %} 37 | -------------------------------------------------------------------------------- /templates/manage_keys.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}认证信息-移动终端自动化运维平台{% endblock %} 3 | {% block nav %} 4 |
  • 仪表盘
  • 5 |
  • 主机列表
  • 6 |
  • SaltStack
  • 7 |
  • 数据修复
  • 8 |
  • MinionKeys(current)
  • 9 |
  • 操作记录
  • 10 | {% endblock %} 11 | {% block content %} 12 |
    13 |
    14 |
    15 |
    16 |
    17 | 22 |
    23 |
    24 | 30 |
    31 |
    32 | 38 |
    39 |
    40 | 接受 41 |
    42 |
    43 | 拒绝 44 |
    45 |
    46 | 删除 47 |
    48 |
    49 |
    50 |
    51 |
    52 |
    53 |
    54 | 55 | 56 | 57 | 62 | 63 | 64 | 65 | 66 | 67 | {% if serv_list %} 68 | {% for serv in serv_list %} 69 | {% for ip,id in serv.iteritems %} 70 | 71 | 76 | 77 | 78 | 79 | {% endfor %} 80 | {% endfor %} 81 | {% endif %} 82 | 83 |
    58 | 61 | Minion IDMinion IP
    72 | 75 | {{ id }}{{ ip }}
    84 |
    85 |
    86 | 118 | {% endblock %} 119 | -------------------------------------------------------------------------------- /templates/profile.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}移动终端自动化运维平台{% endblock %} 3 | {% block nav %} 4 |
  • 仪表盘(current)
  • 5 |
  • 主机列表
  • 6 |
  • SaltStack
  • 7 |
  • 数据修复
  • 8 |
  • MinionKeys
  • 9 |
  • 操作记录
  • 10 | {% endblock %} 11 | {% block content %} 12 | 18 |
    19 |

    修改密码

    20 | {% if tips %} 21 |
    22 |
    {{ tips }}
    23 |
    24 | {% endif %} 25 |
    26 | {% csrf_token %} 27 |
    28 | 29 |
    30 | {{ form.password_old }} 31 | {{ form.password_old.errors }} 32 |
    33 |
    34 |
    35 | 36 |
    37 | {{ form.password_new }} 38 | {{ form.password_new.errors }} 39 |
    40 |
    41 |
    42 | 43 |
    44 | {{ form.password_new_again }} 45 | {{ form.password_new_again.errors }} 46 |
    47 |
    48 |
    49 |
    50 | 51 |
    52 |
    53 |
    54 |
    55 | {% endblock %} 56 | -------------------------------------------------------------------------------- /templates/record_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}主机列表-移动终端自动化运维平台{% endblock %} 3 | {% block nav %} 4 |
  • 仪表盘
  • 5 |
  • 主机列表
  • 6 |
  • SaltStack
  • 7 |
  • 数据修复
  • 8 |
  • MinionKeys
  • 9 |
  • 操作记录(current)
  • 10 | {% endblock %} 11 | {% block content %} 12 | 18 |
    19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {% if jid_record %} 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | {% endif %} 40 |
    时间用户操作目标jid
    {{ jid_record.nowtime|date:"Y-m-d H:i:s" }}{{ jid_record.username }}{{ jid_record.user_operate }}{{ jid_record.simple_tgt }}{{ jid_record.jid }}
    41 |
    42 | {% if hostsft %} 43 | 55 | {% endif %} 56 |
    57 |
    58 | {% for each_ret,each_val in ret.items %} 59 | 60 |
    61 | {% if not hostsft.tr or each_val.status == 'True' %} 62 | 63 | {% else %} 64 | 65 | {% endif %} 66 |
    67 | 68 | 84 | {% endfor %} 85 |
    86 |
    87 | {% endblock %} 88 | -------------------------------------------------------------------------------- /templates/record_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}主机列表-移动终端自动化运维平台{% endblock %} 3 | {% block nav %} 4 |
  • 仪表盘
  • 5 |
  • 主机列表
  • 6 |
  • SaltStack
  • 7 |
  • 数据修复
  • 8 |
  • MinionKeys
  • 9 |
  • 操作记录(current)
  • 10 | {% endblock %} 11 | {% block content %} 12 | 18 |
    19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% if posts %} 31 | 32 | {% for row_record in posts.object_list %} 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | {% endfor %} 42 | 43 | {% endif %} 44 |
    序号时间用户操作目标jid
    {{ posts.start_index|add:forloop.counter0 }}{{ row_record.nowtime|date:"Y-m-d H:i:s" }}{{ row_record.username }}{{ row_record.user_operate }}{{ row_record.simple_tgt }}{{ row_record.jid }}
    45 | 60 |
    61 | {% endblock %} 62 | -------------------------------------------------------------------------------- /templates/registration/logged_out.html: -------------------------------------------------------------------------------- 1 | {% if user.is_authenticated %} 2 | {{ user.username }} 3 | 登录 4 | {% else %} 5 | 您已经退出 6 | {% endif %} 7 | -------------------------------------------------------------------------------- /templates/registration/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 登陆-移动终端自动化运维平台 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | 31 | 32 |
    33 | 34 |
    35 | 36 |
    37 |
    38 |
    39 |

    移动终端自动化运维平台

    40 |
    41 |
    42 | {% if form.errors %} 43 |
    用户名或密码错误!
    44 | {% endif %} 45 |
    46 |
    47 | 56 |
    57 |
    58 | 59 |
    60 |
    61 |

    移动终端 © 2015. All rights reserved.

    62 |
    63 |
    64 | 65 |
    66 | 67 |
    68 | 69 |
    70 | 71 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /templates/repair_history_data.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}模块部署-移动终端自动化运维平台{% endblock %} 3 | {% block nav %} 4 |
  • 仪表盘
  • 5 |
  • 主机列表
  • 6 |
  • SaltStack
  • 7 |
  • 数据修复(current)
  • 8 |
  • MinionKeys
  • 9 |
  • 操作记录
  • 10 | {% endblock %} 11 | {% block content %} 12 | 20 |
    21 |
    22 |
    23 |
    24 |
    25 |
    26 | 27 |
    28 |
    29 | 32 |
    33 | {% for dcen_li in dc_list %} 34 | {% for dcen,dccn in data_centers.iteritems %} 35 | {% if dcen == dcen_li %} 36 |
    37 | 40 |
    41 | {% endif %} 42 | {% endfor %} 43 | {% endfor %} 44 |
    45 |
    46 |
    47 | 48 |
    49 | {% for st_exc in stock_exchanges %} 50 |
    51 | 54 |
    55 | {% endfor %} 56 |
    57 |
    58 |
    59 | 60 |
    61 |
    62 | 65 |
    66 |
    67 | 70 |
    71 |
    72 |
    73 |
    74 |
    75 |
    76 |
    77 |
    78 | 79 |
    80 |
    81 |
    82 |
    83 |

    84 |
    85 |
    86 |

    87 |
    88 |
    89 |
    90 |
    91 | {% endblock %} 92 | -------------------------------------------------------------------------------- /templates/salt_deploy.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}模块部署-移动终端自动化运维平台{% endblock %} 3 | {% block nav %} 4 |
  • 仪表盘
  • 5 |
  • 主机列表
  • 6 |
  • SaltStack(current)
  • 7 |
  • 数据修复
  • 8 |
  • MinionKeys
  • 9 |
  • 操作记录
  • 10 | {% endblock %} 11 | {% block content %} 12 | 20 |
    21 |
    22 |
    23 |
    24 |
    25 |
    26 | 27 |
    28 | 29 |
    30 |
    31 |
    32 | 33 |
    34 |
    35 | 38 |
    39 | {% for dcen_li in dcen_list %} 40 | {% for dcen,dccn in data_centers.iteritems %} 41 | {% if dcen == dcen_li %} 42 |
    43 | 46 |
    47 | {% endif %} 48 | {% endfor %} 49 | {% endfor %} 50 |
    51 |
    52 |
    53 | 54 |
    55 | {% for sls in sls_list %} 56 | {% for sls_key,mod in sls_mod_dict.iteritems %} 57 | {% if sls == sls_key %} 58 |
    59 | 62 |
    63 | {% endif %} 64 | {% endfor %} 65 | {% endfor %} 66 |
    67 |
    68 |
    69 |
    70 |
    71 |
    72 |
    73 | 74 |
    75 |
    76 |
    77 |
    78 |

    79 |
    80 |
    81 |

    82 |
    83 |
    84 |
    85 |
    86 | {% endblock %} 87 | -------------------------------------------------------------------------------- /templates/salt_execute.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}远程操作-移动终端自动化运维平台{% endblock %} 3 | {% block nav %} 4 |
  • 仪表盘
  • 5 |
  • 主机列表
  • 6 |
  • SaltStack(current)
  • 7 |
  • 数据修复
  • 8 |
  • MinionKeys
  • 9 |
  • 操作记录
  • 10 | {% endblock %} 11 | {% block content %} 12 | 20 |
    21 |
    22 |
    23 |
    24 |
    25 |
    26 | 27 |
    28 | 29 |
    30 |
    31 |
    32 | 33 |
    34 | 37 | {% for dcen_li in dcen_list %} 38 | {% for dcen,dccn in data_centers.iteritems %} 39 | {% if dcen == dcen_li %} 40 | 42 | {% endif %} 43 | {% endfor %} 44 | {% endfor %} 45 |
    46 |
    47 |
    48 | 49 |
    50 | 51 |
    52 |
    53 |
    54 |
    55 |
    56 |
    57 |
    58 | 59 |
    60 |
    61 |
    62 |
    63 |

    64 |
    65 |
    66 |

    67 |
    68 |
    69 |
    70 |
    71 | 72 | 73 | {% endblock %} 74 | -------------------------------------------------------------------------------- /templates/salt_routine.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}模块更新-移动终端自动化运维平台{% endblock %} 3 | {% block nav %} 4 |
  • 仪表盘
  • 5 |
  • 主机列表
  • 6 |
  • SaltStack(current)
  • 7 |
  • 数据修复
  • 8 |
  • MinionKeys
  • 9 |
  • 操作记录
  • 10 | {% endblock %} 11 | {% block content %} 12 | 20 |
    21 |
    22 |
    23 |
    24 |
    25 |
    26 | 27 |
    28 | 29 |
    30 |
    31 |
    32 | 33 |
    34 |
    35 | 38 |
    39 | {% for dcen_li in dcen_list %} 40 | {% for dcen,dccn in data_centers.iteritems %} 41 | {% if dcen == dcen_li %} 42 |
    43 | 46 |
    47 | {% endif %} 48 | {% endfor %} 49 | {% endfor %} 50 |
    51 |
    52 |
    53 | 54 |
    55 | {% for sls in sls_list %} 56 | {% for sls_key,mod in sls_mod_dict.iteritems %} 57 | {% if sls == sls_key %} 58 |
    59 | 62 |
    63 | {% endif %} 64 | {% endfor %} 65 | {% endfor %} 66 |
    67 |
    68 |
    69 |
    70 |
    71 |
    72 |
    73 | 74 |
    75 |
    76 |
    77 |
    78 |

    79 |
    80 |
    81 |

    82 |
    83 |
    84 |
    85 |
    86 | {% endblock %} 87 | -------------------------------------------------------------------------------- /templates/salt_update.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}模块更新-移动终端自动化运维平台{% endblock %} 3 | {% block nav %} 4 |
  • 仪表盘
  • 5 |
  • 主机列表
  • 6 |
  • SaltStack(current)
  • 7 |
  • 数据修复
  • 8 |
  • MinionKeys
  • 9 |
  • 操作记录
  • 10 | {% endblock %} 11 | {% block content %} 12 | 20 |
    21 |
    22 |
    23 |
    24 |
    25 |
    26 | 27 |
    28 | 29 |
    30 |
    31 |
    32 | 33 |
    34 |
    35 | 38 |
    39 | {% for dcen_li in dcen_list %} 40 | {% for dcen,dccn in data_centers.iteritems %} 41 | {% if dcen == dcen_li %} 42 |
    43 | 46 |
    47 | {% endif %} 48 | {% endfor %} 49 | {% endfor %} 50 |
    51 |
    52 |
    53 | 54 |
    55 | {% for sls in sls_list %} 56 | {% for sls_key,mod in sls_mod_dict.iteritems %} 57 | {% if sls == sls_key %} 58 |
    59 | 62 |
    63 | {% endif %} 64 | {% endfor %} 65 | {% endfor %} 66 |
    67 |
    68 |
    69 |
    70 |
    71 |
    72 |
    73 | 74 |
    75 |
    76 |
    77 |
    78 |

    79 |
    80 |
    81 |

    82 |
    83 |
    84 |
    85 |
    86 | {% endblock %} 87 | -------------------------------------------------------------------------------- /templates/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {% block title %}{% endblock %} 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 38 | 39 | 40 |
    41 | 80 |
    81 |
    82 | {# {% include 'anew/nav_bar_header.html' %}#} 83 |
    84 | {% block content %}{% endblock %} 85 | {# {% include 'anew/footer.html' %}#} 86 |
    87 |
    88 | 89 | 90 | --------------------------------------------------------------------------------