From 13f34a8bdd42b8e7568859b0544052e227cc5f9f Mon Sep 17 00:00:00 2001 From: Administrator Date: Tue, 25 Oct 2016 18:15:02 +0800 Subject: [PATCH 01/42] =?UTF-8?q?ops:=20=20[future]=E5=AE=8C=E6=88=90ansib?= =?UTF-8?q?le2.0=20API=20=E7=9A=84=E5=9F=BA=E6=9C=AC=E5=B0=81=E8=A3=85.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/ansible_api.py | 229 +++++++++++++++++++++++++--------------- 1 file changed, 143 insertions(+), 86 deletions(-) diff --git a/apps/ops/ansible_api.py b/apps/ops/ansible_api.py index f33089ba9..115a06dbe 100644 --- a/apps/ops/ansible_api.py +++ b/apps/ops/ansible_api.py @@ -80,56 +80,34 @@ class Config(object): default_config.HOST_KEY_CHECKING = False -class MyInventory(object): - """Ansible Inventory对象的封装, Inventory是Ansbile中的核心概念(资产清单), - 这个概念和CMDB很像,都是对资产的抽象. 为了简化Inventory的使用, 通过传入资产列表即可初始化Inventory. +class InventoryMixin(object): + """提供生成Ansible inventory对象的方法 """ - def __init__(self, *assets, **group): - """初始化Inventory对象, args为一个资产列表, kwargs是资产组变量列表, 比如 - args: - [{ - "name": "asset_name", - "ip": "asset_ip", - "port": "asset_port", - "username": "asset_user", - "password": "asset_pass", - "key": "asset_private_key", - "group": "asset_group_name", - ... - }] - kwargs: - "groupName1": {"group_variable1": "value1",...} - "groupName2": {"group_variable1": "value1",...} - """ - self.assets = assets - self.assets_group = group - self.loader = DataLoader() - self.variable_manager = VariableManager() - self.groups = [] - self.inventory = self.gen_inventory() + def gen_inventory(self): + """用于生成动态构建Ansible Inventory. - def __gen_group(self): - """初始化Ansible Group, 将资产添加到Inventory里面 - :return: None + :return: 返回一个Ansible的inventory对象 """ + + # TODO: 验证输入 + # 创建Ansible Group. - for asset in self.assets: + for asset in self.hosts: g_name = asset.get('group', 'default') if g_name not in [g.name for g in self.groups]: group = Group(name=asset.get('group', 'default')) - self.groups.append(group) # 初始化组变量 - for group_name, variables in self.assets_group.iteritems(): + for group_name, variables in self.group_vars.iteritems(): for g in self.groups: if g.name == group_name: for v_name, v_value in variables: g.set_variable(v_name, v_value) # 往组里面添加Host - for asset in self.assets: + for asset in self.hosts: host = Host(name=asset['name'], port=asset['port']) host.set_variable('ansible_ssh_host', asset['ip']) host.set_variable('ansible_ssh_port', asset['port']) @@ -147,36 +125,103 @@ class MyInventory(object): if g.name == asset.get('group', 'default'): g.add_host(host) - def validate(self): - pass - - def gen_inventory(self): - self.validate() - i = Inventory(loader=self.loader, variable_manager=self.variable_manager, host_list=[]) - self.__gen_group() + # 生成Ansible inventory对象 + inventory = Inventory(loader=self.loader, variable_manager=self.variable_manager, host_list=[]) for g in self.groups: - i.add_group(g) - self.variable_manager.set_inventory(i) - return i + inventory.add_group(g) + self.variable_manager.set_inventory(inventory) + return inventory -class PlayBookRunner(object): - """用于执行AnsiblePlaybook的接口.简化Playbook对象的使用 +class CallbackModule(CallbackBase): + """处理和分析Ansible运行结果,并保存数据. + """ + CALLBACK_VERSION = 2.0 + CALLBACK_TYPE = 'stdout' + CALLBACK_NAME = 'json' + + def __init__(self, display=None): + super(CallbackModule, self).__init__(display) + self.results = [] + + def _new_play(self, play): + return { + 'play': { + 'name': play.name, + 'id': str(play._uuid) + }, + 'tasks': [] + } + + def _new_task(self, task): + return { + 'task': { + 'name': task.name, + 'id': str(task._uuid) + }, + 'hosts': {} + } + + def v2_playbook_on_play_start(self, play): + self.results.append(self._new_play(play)) + + def v2_playbook_on_task_start(self, task, is_conditional): + self.results[-1]['tasks'].append(self._new_task(task)) + + def v2_runner_on_ok(self, result, **kwargs): + host = result._host + self.results[-1]['tasks'][-1]['hosts'][host.name] = result._result + + def v2_playbook_on_stats(self, stats): + """Display info about playbook statistics""" + + hosts = sorted(stats.processed.keys()) + + summary = {} + for h in hosts: + s = stats.summarize(h) + summary[h] = s + + output = { + 'plays': self.results, + 'stats': summary + } + + print(json.dumps(output, indent=4, sort_keys=True)) + + v2_runner_on_failed = v2_runner_on_ok + v2_runner_on_unreachable = v2_runner_on_ok + v2_runner_on_skipped = v2_runner_on_ok + + +class PlayBookRunner(InventoryMixin): + """用于执行AnsiblePlaybook的接口.简化Playbook对象的使用. """ - def __init__(self, inventory, config, palybook_path, playbook_var, become_pass, verbosity=0): + def __init__(self, config, palybook_path, playbook_var, become_pass, *hosts, **group_vars): """ - :param inventory: myinventory实例 + :param config: Config实例 :param palybook_path: playbook的路径 :param playbook_var: 执行Playbook时的变量 :param become_pass: sudo passsword - :param verbosity: --verbosity + :param hosts: 可变位置参数, 为一个资产列表, 每一个资产用dict表示, 以下是这个dict必须包含的key + [{ + "name": "asset_name", + "ip": "asset_ip", + "port": "asset_port", + "username": "asset_user", + "password": "asset_pass", + "key": "asset_private_key", + "group": "asset_group_name", + ... + }] + :param group_vars: 可变关键字参数, 是资产组变量, 记录对应的资产组变量 + "groupName1": {"group_variable1": "value1",...} + "groupName2": {"group_variable1": "value1",...} """ self.options = config - self.options.verbosity = verbosity - self.options.connection = 'smart' # 设置verbosity级别, 及命令行的--verbose选项 self.display = Display() @@ -190,16 +235,24 @@ class PlayBookRunner(object): passwords = {'become_pass': become_pass} # 传入playbook的路径,以及执行需要的变量 - inventory.variable_manager.extra_vars = playbook_var pb_dir = os.path.dirname(__file__) playbook = "%s/%s" % (pb_dir, palybook_path) + # 生成Ansible inventory, 这些变量Mixin都会用到 + self.hosts = hosts + self.group_vars = group_vars + self.loader = DataLoader() + self.variable_manager = VariableManager() + self.groups = [] + self.variable_manager.extra_vars = playbook_var + self.inventory = self.gen_inventory() + # 初始化playbook的executor self.pbex = playbook_executor.PlaybookExecutor( playbooks=[playbook], - inventory=inventory, - variable_manager=inventory.variable_manager, - loader=inventory.loader, + inventory=self.inventory, + variable_manager=self.variable_manager, + loader=self.loader, options=self.options, passwords=passwords) @@ -223,13 +276,15 @@ class PlayBookRunner(object): return stats -class ADHocRunner(object): +class ADHocRunner(InventoryMixin): """ADHoc接口 """ - def __init__(self, inventory, config, become_pass=None, verbosity=0): + def __init__(self, config, play_data, become_pass=None, *hosts, **group_vars): """ - :param inventory: myinventory实例 + :param hosts: 见PlaybookRunner参数 + :param group_vars: 见PlaybookRunner参数 :param config: Config实例 + :param play_data: play_data = dict( name="Ansible Ad-Hoc", @@ -240,8 +295,6 @@ class ADHocRunner(object): """ self.options = config - self.options.verbosity = verbosity - self.options.connection = 'smart' # 设置verbosity级别, 及命令行的--verbose选项 self.display = Display() @@ -254,22 +307,18 @@ class ADHocRunner(object): self.options.become_user = 'root' self.passwords = {'become_pass': become_pass} + # 生成Ansible inventory, 这些变量Mixin都会用到 + self.hosts = hosts + self.group_vars = group_vars + self.loader = DataLoader() + self.variable_manager = VariableManager() + self.groups = [] + self.inventory = self.gen_inventory() + # 初始化callback插件 - # self.results_callback = ResultCallback() + self.results_callback = CallbackModule() - # 初始化Play - play_source = { - "name": "Ansible Play", - "hosts": "*", - "gather_facts": "no", - "tasks": [ - dict(action=dict(module='shell', args='id'), register='shell_out'), - dict(action=dict(module='debug', args=dict(msg='{{shell_out.stdout}}'))) - ] - } - - self.play = Play().load(play_source, variable_manager=inventory.variable_manager, loader=inventory.loader) - self.inventory = inventory + self.play = Play().load(play_data, variable_manager=self.variable_manager, loader=self.loader) def run(self): """执行ADHoc 记录日志, 处理结果 @@ -278,16 +327,16 @@ class ADHocRunner(object): # TODO:日志和结果分析 try: tqm = TaskQueueManager( - inventory=self.inventory.inventory, - variable_manager=self.inventory.variable_manager, - loader=self.inventory.loader, - stdout_callback=default_config.DEFAULT_STDOUT_CALLBACK, + inventory=self.inventory, + variable_manager=self.variable_manager, + loader=self.loader, + stdout_callback=self.results_callback, options=self.options, passwords=self.passwords ) - - result = tqm.run(self.play) - return result + ext_code = tqm.run(self.play) + result = json.dumps(self.results_callback.results) + return ext_code, result finally: if tqm: tqm.cleanup() @@ -300,11 +349,19 @@ if __name__ == "__main__": "ip": "localhost", "port": "22", "username": "yumaojun", - "password": "xxx", + "password": "yusky0902", "key": "asset_private_key", }] - inv = MyInventory(*assets) - print inv.inventory.get_group('default').get_hosts() - hoc = ADHocRunner(inv, conf, 'xxx') - hoc.run() - + # 初始化Play + play_source = { + "name": "Ansible Play", + "hosts": "*", + "gather_facts": "no", + "tasks": [ + dict(action=dict(module='setup')), + ] + } + hoc = ADHocRunner(conf, play_source,'yusky0902', *assets) + ext_code, result = hoc.run() + print ext_code + print result \ No newline at end of file From eb5f0fcf684b23f30fd422a9ab393a7fa40be54a Mon Sep 17 00:00:00 2001 From: Administrator Date: Thu, 27 Oct 2016 15:20:16 +0800 Subject: [PATCH 02/42] =?UTF-8?q?[future]=20=E5=9C=A8=E4=B8=BB=E6=9C=BA?= =?UTF-8?q?=E5=B1=82=E9=9D=A2=E6=94=AF=E6=8C=81=E5=A4=9A=E7=A7=8Dsudo,=20?= =?UTF-8?q?=E4=BB=A5=E5=8F=8A=E5=9B=9E=E8=B0=83=E7=BB=93=E6=9E=9C=E7=9A=84?= =?UTF-8?q?=E5=88=86=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/ansible_api.py | 138 +++++++++++++++++++++++++++------------- apps/ops/models.py | 33 +++++++++- requirements.txt | 2 +- 3 files changed, 126 insertions(+), 47 deletions(-) diff --git a/apps/ops/ansible_api.py b/apps/ops/ansible_api.py index 115a06dbe..7b45b1acd 100644 --- a/apps/ops/ansible_api.py +++ b/apps/ops/ansible_api.py @@ -22,11 +22,11 @@ class Config(object): """Ansible运行时配置类, 用于初始化Ansible. """ def __init__(self, verbosity=None, inventory=None, listhosts=None, subset=None, module_paths=None, extra_vars=None, - forks=None, ask_vault_pass=None, vault_password_files=None, new_vault_password_file=None, - output_file=None, tags=None, skip_tags=None, one_line=None, tree=None, ask_sudo_pass=None, ask_su_pass=None, - sudo=None, sudo_user=None, become=None, become_method=None, become_user=None, become_ask_pass=None, - ask_pass=None, private_key_file=None, remote_user=None, connection=None, timeout=None, ssh_common_args=None, - sftp_extra_args=None, scp_extra_args=None, ssh_extra_args=None, poll_interval=None, seconds=None, check=None, + forks=None, ask_vault_pass=False, vault_password_files=None, new_vault_password_file=None, + output_file=None, tags=None, skip_tags=None, one_line=None, tree=None, ask_sudo_pass=False, ask_su_pass=False, + sudo=None, sudo_user=None, become=None, become_method=None, become_user=None, become_ask_pass=False, + ask_pass=False, private_key_file=None, remote_user=None, connection="smart", timeout=None, ssh_common_args=None, + sftp_extra_args=None, scp_extra_args=None, ssh_extra_args=None, poll_interval=None, seconds=None, check=False, syntax=None, diff=None, force_handlers=None, flush_cache=None, listtasks=None, listtags=None, module_path=None): self.verbosity = verbosity self.inventory = inventory @@ -86,46 +86,75 @@ class InventoryMixin(object): def gen_inventory(self): """用于生成动态构建Ansible Inventory. + self.hosts: [ + {"host": , + "port": , + "user": , + "pass": , + "key": , + "group": + "other_host_var": }, + {...}, + ] + self.group_vars: { + "groupName1": {"var1": , "var2": , ...}, + "groupName2": {"var1": , "var2": , ...}, + } :return: 返回一个Ansible的inventory对象 """ # TODO: 验证输入 - # 创建Ansible Group. + # 创建Ansible Group,如果没有则创建default组 for asset in self.hosts: g_name = asset.get('group', 'default') if g_name not in [g.name for g in self.groups]: - group = Group(name=asset.get('group', 'default')) + group = Group(name=g_name) self.groups.append(group) - # 初始化组变量 + # 添加组变量到相应的组上 for group_name, variables in self.group_vars.iteritems(): for g in self.groups: if g.name == group_name: - for v_name, v_value in variables: + for v_name, v_value in variables.iteritems(): g.set_variable(v_name, v_value) # 往组里面添加Host for asset in self.hosts: + # 添加Host链接的常用变量(host,port,user,pass,key) host = Host(name=asset['name'], port=asset['port']) - host.set_variable('ansible_ssh_host', asset['ip']) - host.set_variable('ansible_ssh_port', asset['port']) - host.set_variable('ansible_ssh_user', asset['username']) + host.set_variable('ansible_host', asset['ip']) + host.set_variable('ansible_port', asset['port']) + host.set_variable('ansible_user', asset['username']) + # 添加密码和秘钥 if asset.get('password'): host.set_variable('ansible_ssh_pass', asset['password']) if asset.get('key'): host.set_variable('ansible_ssh_private_key_file', asset['key']) + # 添加become支持 + become = asset.get("become", None) + if become is not None: + host.set_variable("ansible_become", True) + host.set_variable("ansible_become_method", become.get('method')) + host.set_variable("ansible_become_user", become.get('user')) + host.set_variable("ansible_become_pass", become.get('pass')) + else: + host.set_variable("ansible_become", False) + + # 添加其他Host的额外变量 for key, value in asset.iteritems(): if key not in ["name", "port", "ip", "username", "password", "key"]: host.set_variable(key, value) + + # 将host添加到组里面 for g in self.groups: if g.name == asset.get('group', 'default'): g.add_host(host) - # 生成Ansible inventory对象 + # 将组添加到Inventory里面,生成真正的inventory对象 inventory = Inventory(loader=self.loader, variable_manager=self.variable_manager, host_list=[]) for g in self.groups: inventory.add_group(g) @@ -143,6 +172,7 @@ class CallbackModule(CallbackBase): def __init__(self, display=None): super(CallbackModule, self).__init__(display) self.results = [] + self.output = {} def _new_play(self, play): return { @@ -151,6 +181,7 @@ class CallbackModule(CallbackBase): 'id': str(play._uuid) }, 'tasks': [] + } def _new_task(self, task): @@ -159,22 +190,41 @@ class CallbackModule(CallbackBase): 'name': task.name, 'id': str(task._uuid) }, - 'hosts': {} + 'failed': {}, + 'unreachable': {}, + 'skipped': {}, + 'no_hosts': {}, + 'success': {} } + def v2_runner_on_failed(self, result, ignore_errors=False): + host = result._host + self.results[-1]['tasks'][-1]['failed'][host.name] = result._result + + def v2_runner_on_unreachable(self, result): + host = result._host + self.results[-1]['tasks'][-1]['unreachable'][host.name] = result._result + + def v2_runner_on_skipped(self, result): + host = result._host + self.results[-1]['tasks'][-1]['skipped'][host.name] = result._result + + def v2_runner_on_no_hosts(self, task): + self.results[-1]['tasks'][-1]['no_hosts']['name'] = task.name + self.results[-1]['tasks'][-1]['no_hosts']['uuid'] = task.uuid + + def v2_runner_on_ok(self, result): + host = result._host + self.results[-1]['tasks'][-1]['success'][host.name] = result._result + def v2_playbook_on_play_start(self, play): self.results.append(self._new_play(play)) def v2_playbook_on_task_start(self, task, is_conditional): self.results[-1]['tasks'].append(self._new_task(task)) - def v2_runner_on_ok(self, result, **kwargs): - host = result._host - self.results[-1]['tasks'][-1]['hosts'][host.name] = result._result - def v2_playbook_on_stats(self, stats): """Display info about playbook statistics""" - hosts = sorted(stats.processed.keys()) summary = {} @@ -182,16 +232,8 @@ class CallbackModule(CallbackBase): s = stats.summarize(h) summary[h] = s - output = { - 'plays': self.results, - 'stats': summary - } - - print(json.dumps(output, indent=4, sort_keys=True)) - - v2_runner_on_failed = v2_runner_on_ok - v2_runner_on_unreachable = v2_runner_on_ok - v2_runner_on_skipped = v2_runner_on_ok + self.output['plays'] = self.results + self.output['stats'] = summary class PlayBookRunner(InventoryMixin): @@ -279,7 +321,7 @@ class PlayBookRunner(InventoryMixin): class ADHocRunner(InventoryMixin): """ADHoc接口 """ - def __init__(self, config, play_data, become_pass=None, *hosts, **group_vars): + def __init__(self, config, play_data, *hosts, **group_vars): """ :param hosts: 见PlaybookRunner参数 :param group_vars: 见PlaybookRunner参数 @@ -299,13 +341,9 @@ class ADHocRunner(InventoryMixin): # 设置verbosity级别, 及命令行的--verbose选项 self.display = Display() self.display.verbosity = self.options.verbosity - playbook_executor.verbosity = self.options.verbosity - # sudo成其他用户的配置 - self.options.become = True - self.options.become_method = 'sudo' - self.options.become_user = 'root' - self.passwords = {'become_pass': become_pass} + # sudo的配置移到了Host级别去了,因此这里不再需要处理 + self.passwords = None # 生成Ansible inventory, 这些变量Mixin都会用到 self.hosts = hosts @@ -344,24 +382,36 @@ class ADHocRunner(InventoryMixin): if __name__ == "__main__": conf = Config() - assets = [{ - "name": "localhost", - "ip": "localhost", + assets = [ + { + "name": "192.168.1.119", + "ip": "192.168.1.119", + "port": "22", + "username": "root", + "password": "xxx", + "key": "asset_private_key", + }, + { + "name": "192.168.232.135", + "ip": "192.168.232.135", "port": "22", "username": "yumaojun", - "password": "yusky0902", + "password": "xxx", "key": "asset_private_key", - }] + "become": {"method": "sudo", "user": "root", "pass": "yusky0902"} + }, + ] # 初始化Play play_source = { "name": "Ansible Play", - "hosts": "*", + "hosts": "default", "gather_facts": "no", "tasks": [ dict(action=dict(module='setup')), + dict(action=dict(module='command', args='lsdfd')) ] } - hoc = ADHocRunner(conf, play_source,'yusky0902', *assets) + hoc = ADHocRunner(conf, play_source, *assets) ext_code, result = hoc.run() print ext_code - print result \ No newline at end of file + print result diff --git a/apps/ops/models.py b/apps/ops/models.py index bd4b2abe9..13563de9b 100644 --- a/apps/ops/models.py +++ b/apps/ops/models.py @@ -1,5 +1,34 @@ -from __future__ import unicode_literals +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals, absolute_import + +import logging from django.db import models +from django.utils.translation import ugettext_lazy as _ + + +logger = logging.getLogger(__name__) + + +class Play(models.Model): + name = models.CharField(max_length=128, verbose_name=_('Name')) + uuid = models.CharField(max_length=128, verbose_name=_('UUID')) + completed = models.BooleanField(default=False, verbose_name=_('IsCompleted')) + status_code = models.IntegerField(default=0, verbose_name=_('StatusCode')) + + def __unicode__(self): + return self.name + + +class Task(models.Model): + play = models.ForeignKey(Play, related_name='tasks', blank=True) + name = models.CharField(max_length=128, blank=True, blverbose_name=_('Name')) + uuid = models.CharField(max_length=128, verbose_name=_('UUID')) + + def __unicode__(self): + return self.clean() + + +class HostResult(models.Model): + task = models.ForeignKey(Task, related_name='host_results', blank=True) -# Create your models here. diff --git a/requirements.txt b/requirements.txt index ddf45a598..017720c87 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,7 +16,7 @@ ForgeryPy==0.1 openpyxl==2.4.0 paramiko==2.0.2 celery==3.1.23 -ansible==2.1.1.0 +ansible==2.1.2.0 django-simple-captcha==0.5.2 django-formtools==1.0 sshpubkeys==2.2.0 From 96bc1cd8f148e8ae9262e04df993b1b5361024a3 Mon Sep 17 00:00:00 2001 From: Administrator Date: Fri, 28 Oct 2016 13:41:11 +0800 Subject: [PATCH 03/42] =?UTF-8?q?[future]ansible=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E5=AD=98=E5=85=A5=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E4=B8=AD...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/ansible_api.py | 112 +++++++++++++++++++++++++++++++++------- apps/ops/models.py | 61 ++++++++++++++++++---- 2 files changed, 144 insertions(+), 29 deletions(-) diff --git a/apps/ops/ansible_api.py b/apps/ops/ansible_api.py index 7b45b1acd..8b55aa50f 100644 --- a/apps/ops/ansible_api.py +++ b/apps/ops/ansible_api.py @@ -3,6 +3,10 @@ from __future__ import unicode_literals import os import json +import logging +import ansible.constants as default_config + + from ansible.executor.task_queue_manager import TaskQueueManager from ansible.inventory import Inventory, Host, Group from ansible.vars import VariableManager @@ -10,9 +14,13 @@ from ansible.parsing.dataloader import DataLoader from ansible.executor import playbook_executor from ansible.utils.display import Display from ansible.playbook.play import Play -import ansible.constants as default_config from ansible.plugins.callback import CallbackBase +from models import AnsiblePlay, AnsibleTask, AnsibleHostResult + + +logger = logging.getLogger(__name__) + class AnsibleError(StandardError): pass @@ -175,21 +183,28 @@ class CallbackModule(CallbackBase): self.output = {} def _new_play(self, play): - return { - 'play': { - 'name': play.name, - 'id': str(play._uuid) - }, + """将Play保持到数据里面 + """ + ret = { + 'name': play.name, + 'uuid': str(play._uuid), 'tasks': [] - } + try: + play = AnsiblePlay(name=ret['name'], uuid=ret['uuid'], completed=False) + play.save() + except Exception as e: + logger.error("Save ansible play uuid to database error!, %s" % e.message) + + return ret + def _new_task(self, task): - return { - 'task': { - 'name': task.name, - 'id': str(task._uuid) - }, + """将Task保持到数据库里,需要和Play进行关联 + """ + ret = { + 'name': task.name, + 'uuid': str(task._uuid), 'failed': {}, 'unreachable': {}, 'skipped': {}, @@ -197,23 +212,61 @@ class CallbackModule(CallbackBase): 'success': {} } + try: + play = AnsiblePlay.objects.get(uuid=self.__play_uuid) + task = AnsibleTask(play=play, uuid=ret['uuid'], name=ret['name']) + task.save() + except Exception as e: + logger.error("Save ansible task uuid to database error!, %s" % e.message) + + return ret + + @property + def __task_uuid(self): + return self.results[-1]['tasks'][-1]['uuid'] + + @property + def __play_uuid(self): + return self.results[-1]['uuid'] + + def save_task_result(self, result): + try: + task = AnsibleTask.objects.get(uuid=self.__task_uuid) + host_result = AnsibleHostResult(task=task, name=result._host) + host_result.save() + except Exception as e: + logger.error("Save Ansible host result to database error!, %s" % e.message) + + @staticmethod + def save_no_host_result(task): + try: + task = AnsibleTask.objects.get(uuid=task._uuid) + host_result = AnsibleHostResult(task=task, no_host="no host to run this task") + host_result.save() + except Exception as e: + logger.error("Save Ansible host result to database error!, %s" % e.message) + def v2_runner_on_failed(self, result, ignore_errors=False): + self.save_task_result(result) host = result._host self.results[-1]['tasks'][-1]['failed'][host.name] = result._result def v2_runner_on_unreachable(self, result): + self.save_task_result(result) host = result._host self.results[-1]['tasks'][-1]['unreachable'][host.name] = result._result def v2_runner_on_skipped(self, result): + self.save_task_result(result) host = result._host self.results[-1]['tasks'][-1]['skipped'][host.name] = result._result def v2_runner_on_no_hosts(self, task): - self.results[-1]['tasks'][-1]['no_hosts']['name'] = task.name - self.results[-1]['tasks'][-1]['no_hosts']['uuid'] = task.uuid + self.save_no_host_result(task) + self.results[-1]['tasks'][-1]['no_hosts']['msg'] = "no host to run this task" def v2_runner_on_ok(self, result): + self.save_task_result(result) host = result._host self.results[-1]['tasks'][-1]['success'][host.name] = result._result @@ -224,7 +277,8 @@ class CallbackModule(CallbackBase): self.results[-1]['tasks'].append(self._new_task(task)) def v2_playbook_on_stats(self, stats): - """Display info about playbook statistics""" + """AdHoc模式下这个钩子不会执行 + """ hosts = sorted(stats.processed.keys()) summary = {} @@ -234,6 +288,7 @@ class CallbackModule(CallbackBase): self.output['plays'] = self.results self.output['stats'] = summary + print "summary: %s" % summary class PlayBookRunner(InventoryMixin): @@ -358,8 +413,19 @@ class ADHocRunner(InventoryMixin): self.play = Play().load(play_data, variable_manager=self.variable_manager, loader=self.loader) + @staticmethod + def update_db_play(result, ext_code): + try: + play = AnsiblePlay.objects.get(uuid=result[0]['uuid']) + play.completed = True + play.status_code = ext_code + play.save() + except Exception as e: + print e.message + logger.error("Update Ansible Play Status into database error!, %s" % e.message) + def run(self): - """执行ADHoc 记录日志, 处理结果 + """执行ADHoc, 执行完后, 修改AnsiblePlay的状态 """ tqm = None # TODO:日志和结果分析 @@ -374,13 +440,17 @@ class ADHocRunner(InventoryMixin): ) ext_code = tqm.run(self.play) result = json.dumps(self.results_callback.results) + + self.update_db_play(result, ext_code) + return ext_code, result + finally: if tqm: tqm.cleanup() -if __name__ == "__main__": +def test_run(): conf = Config() assets = [ { @@ -388,7 +458,7 @@ if __name__ == "__main__": "ip": "192.168.1.119", "port": "22", "username": "root", - "password": "xxx", + "password": "tongfang_test", "key": "asset_private_key", }, { @@ -408,10 +478,14 @@ if __name__ == "__main__": "gather_facts": "no", "tasks": [ dict(action=dict(module='setup')), - dict(action=dict(module='command', args='lsdfd')) + dict(action=dict(module='command', args='ls')) ] } hoc = ADHocRunner(conf, play_source, *assets) ext_code, result = hoc.run() print ext_code print result + + +if __name__ == "__main__": + test_run() diff --git a/apps/ops/models.py b/apps/ops/models.py index 13563de9b..9821dabee 100644 --- a/apps/ops/models.py +++ b/apps/ops/models.py @@ -10,25 +10,66 @@ from django.utils.translation import ugettext_lazy as _ logger = logging.getLogger(__name__) -class Play(models.Model): +class AnsiblePlay(models.Model): + uuid = models.CharField(max_length=128, verbose_name=_('UUID'), primary_key=True) name = models.CharField(max_length=128, verbose_name=_('Name')) - uuid = models.CharField(max_length=128, verbose_name=_('UUID')) completed = models.BooleanField(default=False, verbose_name=_('IsCompleted')) status_code = models.IntegerField(default=0, verbose_name=_('StatusCode')) def __unicode__(self): - return self.name + return "AnsiblePlay: %s<%s>" % (self.name, self.uuid) -class Task(models.Model): - play = models.ForeignKey(Play, related_name='tasks', blank=True) - name = models.CharField(max_length=128, blank=True, blverbose_name=_('Name')) - uuid = models.CharField(max_length=128, verbose_name=_('UUID')) +class AnsibleTask(models.Model): + uuid = models.CharField(max_length=128, verbose_name=_('UUID'), primary_key=True) + play = models.ForeignKey(AnsiblePlay, related_name='tasks', blank=True) + name = models.CharField(max_length=128, blank=True, verbose_name=_('Name')) def __unicode__(self): - return self.clean() + return "AnsibleTask: %s<%s>" % (self.name, self.uuid) + + def failed(self): + pass + + def success(self): + pass -class HostResult(models.Model): - task = models.ForeignKey(Task, related_name='host_results', blank=True) +class AnsibleHostResult(models.Model): + task = models.ForeignKey(AnsibleTask, related_name='host_results', blank=True) + name = models.CharField(max_length=128, blank=True, verbose_name=_('Name')) + status = models.BooleanField(blank=True, default=False, verbose_name=_('Status')) + success = models.TextField(blank=True, verbose_name=_('Success')) + skipped = models.TextField(blank=True, verbose_name=_('Skipped')) + failed = models.TextField(blank=True, verbose_name=_('Failed')) + unreachable = models.TextField(blank=True, verbose_name=_('Unreachable')) + no_host = models.TextField(blank=True, verbose_name=_('NoHost')) + def __unicode__(self): + return "AnsibleHostResult: %s<%s>" % (self.name, str(self.status)) + + def is_failed(self): + pass + + def result(self): + pass + + +class AnsibleSetup(models.Model): + task = models.ForeignKey(AnsibleTask, related_name='host_results', blank=True) + name = models.CharField(max_length=128, blank=True, verbose_name=_('Name')) + status = models.BooleanField(blank=True, default=False, verbose_name=_('Status')) + success = models.TextField(blank=True, verbose_name=_('Success')) + skipped = models.TextField(blank=True, verbose_name=_('Skipped')) + failed = models.TextField(blank=True, verbose_name=_('Failed')) + unreachable = models.TextField(blank=True, verbose_name=_('Unreachable')) + no_host = models.TextField(blank=True, verbose_name=_('NoHost')) + + def __unicode__(self): + return "AnsibleHostResult: %s<%s>" % (self.name, str(self.status)) + + def is_failed(self): + pass + + def result(self): + pass From 51c530c12351524eb24d5d13f41aca4b54e21707 Mon Sep 17 00:00:00 2001 From: Administrator Date: Fri, 28 Oct 2016 14:58:09 +0800 Subject: [PATCH 04/42] =?UTF-8?q?[future]=20ansible=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E5=AD=98=E5=85=A5=E6=95=B0=E6=8D=AE=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/settings.py | 4 ++++ apps/ops/ansible_api.py | 30 ++++++++++++++++++---------- apps/ops/models.py | 40 ++++++++++++++++++------------------- 3 files changed, 43 insertions(+), 31 deletions(-) diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index b57a1f07a..ec2c98865 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -210,6 +210,10 @@ LOGGING = { 'jumpserver.users.view': { 'handlers': ['console', 'file'], 'level': LOG_LEVEL, + }, + 'jumpserver.ops.ansible_api': { + 'handlers': ['console', 'file'], + 'level': LOG_LEVEL, } } } diff --git a/apps/ops/ansible_api.py b/apps/ops/ansible_api.py index 8b55aa50f..3c058b9da 100644 --- a/apps/ops/ansible_api.py +++ b/apps/ops/ansible_api.py @@ -229,10 +229,20 @@ class CallbackModule(CallbackBase): def __play_uuid(self): return self.results[-1]['uuid'] - def save_task_result(self, result): + def save_task_result(self, result, status): try: task = AnsibleTask.objects.get(uuid=self.__task_uuid) host_result = AnsibleHostResult(task=task, name=result._host) + if status == "failed": + host_result.failed = json.dumps(result._result) + elif status == "unreachable": + host_result.unreachable = json.dumps(result._result) + elif status == "skipped": + host_result.skipped = json.dumps(result._result) + elif status == "success": + host_result.success = json.dumps(result._result) + else: + logger.error("No such status(failed|unreachable|skipped|success), please check!") host_result.save() except Exception as e: logger.error("Save Ansible host result to database error!, %s" % e.message) @@ -247,17 +257,17 @@ class CallbackModule(CallbackBase): logger.error("Save Ansible host result to database error!, %s" % e.message) def v2_runner_on_failed(self, result, ignore_errors=False): - self.save_task_result(result) + self.save_task_result(result, "failed") host = result._host self.results[-1]['tasks'][-1]['failed'][host.name] = result._result def v2_runner_on_unreachable(self, result): - self.save_task_result(result) + self.save_task_result(result, "unreachable") host = result._host self.results[-1]['tasks'][-1]['unreachable'][host.name] = result._result def v2_runner_on_skipped(self, result): - self.save_task_result(result) + self.save_task_result(result, "skipped") host = result._host self.results[-1]['tasks'][-1]['skipped'][host.name] = result._result @@ -266,7 +276,7 @@ class CallbackModule(CallbackBase): self.results[-1]['tasks'][-1]['no_hosts']['msg'] = "no host to run this task" def v2_runner_on_ok(self, result): - self.save_task_result(result) + self.save_task_result(result, "success") host = result._host self.results[-1]['tasks'][-1]['success'][host.name] = result._result @@ -421,11 +431,10 @@ class ADHocRunner(InventoryMixin): play.status_code = ext_code play.save() except Exception as e: - print e.message logger.error("Update Ansible Play Status into database error!, %s" % e.message) def run(self): - """执行ADHoc, 执行完后, 修改AnsiblePlay的状态 + """执行ADHoc, 执行完后, 修改AnsiblePlay的状态为完成状态. """ tqm = None # TODO:日志和结果分析 @@ -439,11 +448,12 @@ class ADHocRunner(InventoryMixin): passwords=self.passwords ) ext_code = tqm.run(self.play) - result = json.dumps(self.results_callback.results) + result = self.results_callback.results self.update_db_play(result, ext_code) - return ext_code, result + ret = json.dumps(result) + return ext_code, ret finally: if tqm: @@ -478,7 +488,7 @@ def test_run(): "gather_facts": "no", "tasks": [ dict(action=dict(module='setup')), - dict(action=dict(module='command', args='ls')) + dict(action=dict(module='command', args='lsss')) ] } hoc = ADHocRunner(conf, play_source, *assets) diff --git a/apps/ops/models.py b/apps/ops/models.py index 9821dabee..365e1c3c0 100644 --- a/apps/ops/models.py +++ b/apps/ops/models.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals, absolute_import import logging +import json from django.db import models from django.utils.translation import ugettext_lazy as _ @@ -48,28 +49,25 @@ class AnsibleHostResult(models.Model): def __unicode__(self): return "AnsibleHostResult: %s<%s>" % (self.name, str(self.status)) + @property def is_failed(self): - pass + if self.failed or self.unreachable or self.no_host: + return True + return False - def result(self): - pass + @property + def success_data(self): + if self.success: + return json.loads(self.success) + elif self.skipped: + return json.loads(self.skipped) + @property + def failed_data(self): + if self.failed: + return json.loads(self.failed) + elif self.unreachable: + return json.loads(self.unreachable) + elif self.no_host: + return {"msg": self.no_host} -class AnsibleSetup(models.Model): - task = models.ForeignKey(AnsibleTask, related_name='host_results', blank=True) - name = models.CharField(max_length=128, blank=True, verbose_name=_('Name')) - status = models.BooleanField(blank=True, default=False, verbose_name=_('Status')) - success = models.TextField(blank=True, verbose_name=_('Success')) - skipped = models.TextField(blank=True, verbose_name=_('Skipped')) - failed = models.TextField(blank=True, verbose_name=_('Failed')) - unreachable = models.TextField(blank=True, verbose_name=_('Unreachable')) - no_host = models.TextField(blank=True, verbose_name=_('NoHost')) - - def __unicode__(self): - return "AnsibleHostResult: %s<%s>" % (self.name, str(self.status)) - - def is_failed(self): - pass - - def result(self): - pass From fd945513ac216e6f9ced42ebb3bb4a8962e26e22 Mon Sep 17 00:00:00 2001 From: Administrator Date: Fri, 28 Oct 2016 15:47:37 +0800 Subject: [PATCH 05/42] =?UTF-8?q?[fix]=20=E4=BF=AE=E6=94=B9=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E7=9A=84=E6=98=BE=E7=A4=BA,=20=E5=9B=A0=E4=B8=BA?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E9=BB=98=E8=AE=A4=E4=BC=9A=E5=8A=A0=E4=B8=8A?= =?UTF-8?q?Class=E7=9A=84Name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/ops/models.py b/apps/ops/models.py index 365e1c3c0..70f44720e 100644 --- a/apps/ops/models.py +++ b/apps/ops/models.py @@ -18,7 +18,7 @@ class AnsiblePlay(models.Model): status_code = models.IntegerField(default=0, verbose_name=_('StatusCode')) def __unicode__(self): - return "AnsiblePlay: %s<%s>" % (self.name, self.uuid) + return "%s<%s>" % (self.name, self.uuid) class AnsibleTask(models.Model): @@ -27,7 +27,7 @@ class AnsibleTask(models.Model): name = models.CharField(max_length=128, blank=True, verbose_name=_('Name')) def __unicode__(self): - return "AnsibleTask: %s<%s>" % (self.name, self.uuid) + return "%s<%s>" % (self.name, self.uuid) def failed(self): pass @@ -47,7 +47,7 @@ class AnsibleHostResult(models.Model): no_host = models.TextField(blank=True, verbose_name=_('NoHost')) def __unicode__(self): - return "AnsibleHostResult: %s<%s>" % (self.name, str(self.status)) + return "%s<%s>" % (self.name, str(self.status)) @property def is_failed(self): From 97d7e6cb9b5ecad64c9c048184be54297596a2aa Mon Sep 17 00:00:00 2001 From: Administrator Date: Fri, 28 Oct 2016 17:28:32 +0800 Subject: [PATCH 06/42] =?UTF-8?q?[future]=20=E4=BF=AE=E6=94=B9celery=20?= =?UTF-8?q?=E4=BD=BF=E7=94=A8eventlet=20=E4=BD=9C=E4=B8=BAconcurrent=20poo?= =?UTF-8?q?l,=20=E6=B7=BB=E5=8A=A0=E8=8E=B7=E5=8F=96=E8=B5=84=E4=BA=A7?= =?UTF-8?q?=E7=A1=AC=E4=BB=B6=E4=BF=A1=E6=81=AF=E7=9A=84=20=E5=88=9D?= =?UTF-8?q?=E6=AD=A5=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/api.py | 4 +++- apps/ops/run_tasks.py | 9 --------- apps/ops/taskers.py | 17 +++++++++++++++++ apps/ops/tasks.py | 36 +++++++++++++++++++++++++++--------- requirements.txt | 2 +- run_server.py | 2 +- 6 files changed, 49 insertions(+), 21 deletions(-) delete mode 100644 apps/ops/run_tasks.py create mode 100644 apps/ops/taskers.py diff --git a/apps/ops/api.py b/apps/ops/api.py index ecbf4289f..e36433172 100644 --- a/apps/ops/api.py +++ b/apps/ops/api.py @@ -1,3 +1,5 @@ # ~*~ coding: utf-8 ~*~ -# + +from __future__ import unicode_literals + diff --git a/apps/ops/run_tasks.py b/apps/ops/run_tasks.py deleted file mode 100644 index 4c3115fac..000000000 --- a/apps/ops/run_tasks.py +++ /dev/null @@ -1,9 +0,0 @@ -from .tasks import longtime_add -import time - -result = longtime_add.delay(1,2) -print 'Task finished? ', result.ready() -print 'Task result: ', result.result -time.sleep(10) -print 'Task finished? ', result.ready() -print 'Task result: ', result.result \ No newline at end of file diff --git a/apps/ops/taskers.py b/apps/ops/taskers.py new file mode 100644 index 000000000..7a4f2e801 --- /dev/null +++ b/apps/ops/taskers.py @@ -0,0 +1,17 @@ +from __future__ import unicode_literals + +from .tasks import get_asset_hardware_info +from celery.result import AsyncResult + + +def start_get_hardware_info(*assets): + result = get_asset_hardware_info.delay(*assets) + return result.id + + +def get_hardware_info(task_id): + result = AsyncResult(task_id) + if result.ready(): + return {"Completed": False, "data": result.get()} + else: + return {"Completed": True, "data": None} diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index ae4b23f01..8d6a32312 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -1,17 +1,35 @@ -from __future__ import absolute_import -import time +from __future__ import absolute_import, unicode_literals from celery import shared_task from common import celery_app +from ops.ansible_api import Config, ADHocRunner -@shared_task -def longtime_add(x, y): - print 'long time task begins' - # sleep 5 seconds - time.sleep(5) - print 'long time task finished' - return x + y + +@shared_task(name="get_asset_hardware_info") +def get_asset_hardware_info(*assets): + conf = Config() + play_source = { + "name": "Get host hardware information", + "hosts": "default", + "gather_facts": "no", + "tasks": [ + dict(action=dict(module='setup')) + ] + } + hoc = ADHocRunner(conf, play_source, *assets) + ext_code, result = hoc.run() + return ext_code, result + + +@shared_task(name="asset_test_ping_check") +def asset_test_ping_check(): + pass + + +@shared_task(name="add_user_to_assert") +def add_user_to_asset(): + pass @celery_app.task(name='hello-world') diff --git a/requirements.txt b/requirements.txt index 017720c87..12c2e48a4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,4 +23,4 @@ sshpubkeys==2.2.0 djangorestframework-bulk==0.2.1 python-gssapi==0.6.4 tornado==4.4.2 - +eventlet==0.19.0 diff --git a/run_server.py b/run_server.py index e1d3240c3..da9daeb7f 100644 --- a/run_server.py +++ b/run_server.py @@ -29,7 +29,7 @@ def start_django(): def start_celery(): os.chdir(apps_dir) print('start celery') - subprocess.call('celery -A common worker -l info', shell=True) + subprocess.call('celery -A common worker -P eventlet -l info', shell=True) def main(): From ccf3851d81bdec8f31d7269e05254709a507d0af Mon Sep 17 00:00:00 2001 From: Administrator Date: Fri, 28 Oct 2016 17:43:56 +0800 Subject: [PATCH 07/42] =?UTF-8?q?[future]=20=E5=88=9D=E6=AD=A5=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=B5=8B=E8=AF=95=E9=93=BE=E6=8E=A5=E7=9A=84=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/ansible_api.py | 4 ++-- apps/ops/taskers.py | 15 +++++++++++---- apps/ops/tasks.py | 15 +++++++++++++-- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/apps/ops/ansible_api.py b/apps/ops/ansible_api.py index 3c058b9da..a5e940310 100644 --- a/apps/ops/ansible_api.py +++ b/apps/ops/ansible_api.py @@ -468,7 +468,7 @@ def test_run(): "ip": "192.168.1.119", "port": "22", "username": "root", - "password": "tongfang_test", + "password": "xxx", "key": "asset_private_key", }, { @@ -478,7 +478,7 @@ def test_run(): "username": "yumaojun", "password": "xxx", "key": "asset_private_key", - "become": {"method": "sudo", "user": "root", "pass": "yusky0902"} + "become": {"method": "sudo", "user": "root", "pass": "xxx"} }, ] # 初始化Play diff --git a/apps/ops/taskers.py b/apps/ops/taskers.py index 7a4f2e801..12bdfaff9 100644 --- a/apps/ops/taskers.py +++ b/apps/ops/taskers.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals -from .tasks import get_asset_hardware_info +from .tasks import * from celery.result import AsyncResult @@ -9,9 +9,16 @@ def start_get_hardware_info(*assets): return result.id -def get_hardware_info(task_id): +def get_result(task_id): result = AsyncResult(task_id) if result.ready(): - return {"Completed": False, "data": result.get()} + return {"Completed": True, "data": result.get()} else: - return {"Completed": True, "data": None} + return {"Completed": False, "data": None} + + +def start_ping_test(*assets): + result = asset_test_ping_check.delay(*assets) + return result.id + + diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index 8d6a32312..9ea8de9b2 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -23,8 +23,19 @@ def get_asset_hardware_info(*assets): @shared_task(name="asset_test_ping_check") -def asset_test_ping_check(): - pass +def asset_test_ping_check(*assets): + conf = Config() + play_source = { + "name": "Test host connection use ping", + "hosts": "default", + "gather_facts": "no", + "tasks": [ + dict(action=dict(module='ping')) + ] + } + hoc = ADHocRunner(conf, play_source, *assets) + ext_code, result = hoc.run() + return ext_code, result @shared_task(name="add_user_to_assert") From 97b8bcd5ca65a077d008eca631b5cf38b5f011a6 Mon Sep 17 00:00:00 2001 From: Administrator Date: Mon, 31 Oct 2016 17:41:26 +0800 Subject: [PATCH 08/42] =?UTF-8?q?[future]=201.=20settings=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0ops=20=E7=9A=84logger,=20=E5=85=B3=E4=BA=8EAnsible?= =?UTF-8?q?=E7=9A=84log=E5=8D=95=E7=8B=AC=E8=AE=B0=E5=BD=95=202.=20models?= =?UTF-8?q?=20=E5=A2=9E=E5=8A=A0Tasker=E6=A8=A1=E5=9E=8B,=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0AnsibleHostReuslt=20=E5=AF=B9=E4=BA=8E=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=A4=84=E7=90=86=E7=9A=84=E6=96=B9=E6=B3=95=203.=20a?= =?UTF-8?q?nsible=5Fapi,=20callback=20=E7=B1=BB=EF=BC=8C=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E4=BF=9D=E5=AD=98Tasker=E7=9A=84=E9=80=BB=E8=BE=91,i=E5=85=B6?= =?UTF-8?q?=E4=BB=96=E5=85=BC=E5=AE=B9=204.=20taskers,=20=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E8=8E=B7=E5=8F=96=E7=A1=AC=E4=BB=B6=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E5=92=8Cping=E7=9A=84=20tasker=E6=8E=A5=E5=8F=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/settings.py | 10 ++- apps/ops/ansible_api.py | 59 +++++++++----- apps/ops/models.py | 155 ++++++++++++++++++++++++++++++++++-- apps/ops/taskers.py | 58 ++++++++++++-- apps/ops/tasks.py | 8 +- 5 files changed, 250 insertions(+), 40 deletions(-) diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index ec2c98865..c4fbec0a0 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -182,6 +182,12 @@ LOGGING = { 'formatter': 'main', 'filename': os.path.join(PROJECT_DIR, 'logs', 'jumpserver.log') }, + 'ansible_logs': { + 'level': 'DEBUG', + 'class': 'logging.FileHandler', + 'formatter': 'main', + 'filename': os.path.join(PROJECT_DIR, 'logs', 'ansible.log') + }, }, 'loggers': { 'django': { @@ -211,8 +217,8 @@ LOGGING = { 'handlers': ['console', 'file'], 'level': LOG_LEVEL, }, - 'jumpserver.ops.ansible_api': { - 'handlers': ['console', 'file'], + 'ops.ansible_api': { + 'handlers': ['console', 'ansible_logs'], 'level': LOG_LEVEL, } } diff --git a/apps/ops/ansible_api.py b/apps/ops/ansible_api.py index a5e940310..09fb06cd5 100644 --- a/apps/ops/ansible_api.py +++ b/apps/ops/ansible_api.py @@ -4,9 +4,11 @@ from __future__ import unicode_literals import os import json import logging +import traceback import ansible.constants as default_config - +from uuid import uuid4 +from django.utils import timezone from ansible.executor.task_queue_manager import TaskQueueManager from ansible.inventory import Inventory, Host, Group from ansible.vars import VariableManager @@ -16,7 +18,8 @@ from ansible.utils.display import Display from ansible.playbook.play import Play from ansible.plugins.callback import CallbackBase -from models import AnsiblePlay, AnsibleTask, AnsibleHostResult +from models import Tasker, AnsiblePlay, AnsibleTask, AnsibleHostResult + logger = logging.getLogger(__name__) @@ -177,24 +180,28 @@ class CallbackModule(CallbackBase): CALLBACK_TYPE = 'stdout' CALLBACK_NAME = 'json' - def __init__(self, display=None): + def __init__(self, tasker_id, display=None): super(CallbackModule, self).__init__(display) self.results = [] self.output = {} + self.tasker_id = tasker_id def _new_play(self, play): """将Play保持到数据里面 """ ret = { + 'tasker': self.tasker_id, 'name': play.name, 'uuid': str(play._uuid), 'tasks': [] } try: - play = AnsiblePlay(name=ret['name'], uuid=ret['uuid'], completed=False) + tasker = Tasker.objects.get(uuid=self.tasker_id) + play = AnsiblePlay(tasker, name=ret['name'], uuid=ret['uuid']) play.save() except Exception as e: + traceback.print_exc() logger.error("Save ansible play uuid to database error!, %s" % e.message) return ret @@ -418,24 +425,37 @@ class ADHocRunner(InventoryMixin): self.groups = [] self.inventory = self.gen_inventory() - # 初始化callback插件 - self.results_callback = CallbackModule() - self.play = Play().load(play_data, variable_manager=self.variable_manager, loader=self.loader) @staticmethod - def update_db_play(result, ext_code): + def update_db_tasker(tasker_id, ext_code): try: - play = AnsiblePlay.objects.get(uuid=result[0]['uuid']) - play.completed = True - play.status_code = ext_code - play.save() + tasker = Tasker.objects.get(uuid=tasker_id) + tasker.end = timezone.now() + tasker.completed = True + tasker.exit_code = ext_code + tasker.save() except Exception as e: - logger.error("Update Ansible Play Status into database error!, %s" % e.message) + logger.error("Update Tasker Status into database error!, %s" % e.message) - def run(self): + def create_db_tasker(self, name, uuid): + try: + hosts = [host.get('name') for host in self.hosts] + tasker = Tasker(name=name, uuid=uuid, hosts=','.join(hosts), start=timezone.now()) + tasker.save() + except Exception as e: + logger.error("Save Tasker to database error!, %s" % e.message) + + def run(self, tasker_name, tasker_uuid): """执行ADHoc, 执行完后, 修改AnsiblePlay的状态为完成状态. + + :param tasker_uuid 用于标示此次task """ + # 初始化callback插件,以及Tasker + + self.create_db_tasker(tasker_name, tasker_uuid) + self.results_callback = CallbackModule(tasker_uuid) + tqm = None # TODO:日志和结果分析 try: @@ -450,7 +470,8 @@ class ADHocRunner(InventoryMixin): ext_code = tqm.run(self.play) result = self.results_callback.results - self.update_db_play(result, ext_code) + # 任务运行结束, 标示任务完成 + self.update_db_tasker(tasker_uuid, ext_code) ret = json.dumps(result) return ext_code, ret @@ -468,7 +489,7 @@ def test_run(): "ip": "192.168.1.119", "port": "22", "username": "root", - "password": "xxx", + "password": "tongfang_test", "key": "asset_private_key", }, { @@ -487,12 +508,12 @@ def test_run(): "hosts": "default", "gather_facts": "no", "tasks": [ - dict(action=dict(module='setup')), - dict(action=dict(module='command', args='lsss')) + dict(action=dict(module='ping')), ] } hoc = ADHocRunner(conf, play_source, *assets) - ext_code, result = hoc.run() + uuid = "tasker-" + uuid4().hex + ext_code, result = hoc.run("test_task", uuid) print ext_code print result diff --git a/apps/ops/models.py b/apps/ops/models.py index 70f44720e..d3e52566e 100644 --- a/apps/ops/models.py +++ b/apps/ops/models.py @@ -11,24 +11,46 @@ from django.utils.translation import ugettext_lazy as _ logger = logging.getLogger(__name__) +class Tasker(models.Model): + uuid = models.CharField(max_length=128, verbose_name=_('UUID'), primary_key=True) + name = models.CharField(max_length=128, blank=True, verbose_name=_('Name')) + start = models.DateTimeField(auto_now_add=True, verbose_name=_('Start Time')) + end = models.DateTimeField(blank=True, null=True, verbose_name=_('End Time')) + exit_code = models.IntegerField(default=0, verbose_name=_('Exit Code')) + completed = models.BooleanField(default=False, verbose_name=_('Is Completed')) + hosts = models.TextField(blank=True, null=True, verbose_name=_('Hosts')) + + def __unicode__(self): + return "%s" % self.uuid + + @property + def total_hosts(self): + return self.hosts.split(',') + + class AnsiblePlay(models.Model): + tasker = models.ForeignKey(Tasker, related_name='plays', blank=True, null=True) uuid = models.CharField(max_length=128, verbose_name=_('UUID'), primary_key=True) name = models.CharField(max_length=128, verbose_name=_('Name')) - completed = models.BooleanField(default=False, verbose_name=_('IsCompleted')) - status_code = models.IntegerField(default=0, verbose_name=_('StatusCode')) def __unicode__(self): return "%s<%s>" % (self.name, self.uuid) + def to_dict(self): + return {"uuid": self.uuid, "name": self.name} + class AnsibleTask(models.Model): + play = models.ForeignKey(AnsiblePlay, related_name='tasks', blank=True, null=True) uuid = models.CharField(max_length=128, verbose_name=_('UUID'), primary_key=True) - play = models.ForeignKey(AnsiblePlay, related_name='tasks', blank=True) name = models.CharField(max_length=128, blank=True, verbose_name=_('Name')) def __unicode__(self): return "%s<%s>" % (self.name, self.uuid) + def to_dict(self): + return {"uuid": self.uuid, "name": self.name} + def failed(self): pass @@ -37,9 +59,8 @@ class AnsibleTask(models.Model): class AnsibleHostResult(models.Model): - task = models.ForeignKey(AnsibleTask, related_name='host_results', blank=True) + task = models.ForeignKey(AnsibleTask, related_name='host_results', blank=True, null=True) name = models.CharField(max_length=128, blank=True, verbose_name=_('Name')) - status = models.BooleanField(blank=True, default=False, verbose_name=_('Status')) success = models.TextField(blank=True, verbose_name=_('Success')) skipped = models.TextField(blank=True, verbose_name=_('Skipped')) failed = models.TextField(blank=True, verbose_name=_('Failed')) @@ -47,7 +68,7 @@ class AnsibleHostResult(models.Model): no_host = models.TextField(blank=True, verbose_name=_('NoHost')) def __unicode__(self): - return "%s<%s>" % (self.name, str(self.status)) + return "%s %s<%s>" % (self.name, str(self.is_success), self.task.uuid) @property def is_failed(self): @@ -56,14 +77,18 @@ class AnsibleHostResult(models.Model): return False @property - def success_data(self): + def is_success(self): + return not self.is_failed + + @property + def _success_data(self): if self.success: return json.loads(self.success) elif self.skipped: return json.loads(self.skipped) @property - def failed_data(self): + def _failed_data(self): if self.failed: return json.loads(self.failed) elif self.unreachable: @@ -71,3 +96,117 @@ class AnsibleHostResult(models.Model): elif self.no_host: return {"msg": self.no_host} + @property + def failed_msg(self): + return self._failed_data.get("msg") + + @staticmethod + def __filter_disk(ansible_devices, exclude_devices): + """ + 过滤磁盘设备,丢弃掉不需要的设备 + + :param ansible_devices: 对应的facts字段 + :param exclude_devices: 一个需要被丢弃的设备,匹配规则是startwith, 比如需要丢弃sr0子类的 ['sr'] + :return: 过滤获取的结果 + """ + for start_str in exclude_devices: + for key in ansible_devices.keys(): + if key.startswith(start_str): + ansible_devices.pop(key) + return ansible_devices + + @staticmethod + def __filter_interface(ansible_interfaces, exclude_interface): + """ + 过滤网卡设备,丢弃掉不需要的网卡, 比如lo + + :param ansible_interface: 对应的facts字段 + :param exclude_interface: 一个需要被丢弃的设备,匹配规则是startwith, 比如需要丢弃lo子类的 ['lo'] + :return: 过滤获取的结果 + """ + for interface in ansible_interfaces: + for start_str in exclude_interface: + if interface.startswith(start_str): + i = ansible_interfaces.index(interface) + ansible_interfaces.pop(i) + return ansible_interfaces + + @staticmethod + def __gather_interface(facts, interfaces): + """ + 收集所有interface的具体信息 + + :param facts: ansible faces + :param interfaces: 需要收集的intreface列表 + :return: interface的详情 + """ + result = {} + for key in interfaces: + if "ansible_" + key in facts.keys(): + result[key] = facts.get(key) + return result + + def __deal_setup(self): + """ + 处理ansible setup模块收集到的数据,提取资产需要的部分 + + :return: {"msg": , "data": }, 注意msg是异常信息, 有msg时 data为None + """ + result = self._success_data + module_name = result['invocation'].get('module_name') if result.get('invocation') else None + if module_name is not None: + if module_name != "setup": + return {"msg": "the property only for ansible setup module result!, can't support other module", "data":None} + else: + data = {} + facts =result.get('ansible_facts') + interfaces = self.__filter_interface(facts.get('ansible_interfaces'), ['lo']) + + cpu_describe = "%s %s" % (facts.get('ansible_processor')[0], facts.get('ansible_processor')[1]) if len(facts.get('ansible_processor')) >= 2 else "" + + data['sn'] = facts.get('ansible_product_serial') + data['env'] = facts.get('ansible_env') + data['os'] = "%s %s(%s)" % (facts.get('ansible_distribution'), + facts.get('ansible_distribution_version'), + facts.get('ansible_distribution_release')) + data['mem'] = facts.get('ansible_memtotal_mb') + data['cpu'] = "%s %d核" % (cpu_describe, facts.get('ansible_processor_count')) + data['disk'] = self.__filter_disk(facts.get('ansible_devices'), ['sr']) + data['interface'] = self.__gather_interface(facts, interfaces) + return {"msg": None, "data": data} + else: + return {"msg": "there isn't module_name field! can't process this data format", "data": None} + + @property + def deal_setup(self): + try: + return self.__deal_setup() + except Exception as e: + return {"msg": "deal with setup data failed, %s" % e.message, "data": None} + + def __deal_ping(self): + """ + 处理ansible ping模块收集到的数据 + + :return: {"msg": , "data": {"success": }}, 注意msg是异常信息, 有msg时 data为None + """ + result = self._success_data + module_name = result['invocation'].get('module_name') if result.get('invocation') else None + if module_name is not None: + if module_name != "ping": + return {"msg": "the property only for ansible setup module result!, can't support other module", "data":None} + else: + ping = True if result.get('ping') == "pong" else False + + return {"msg": None, "data": {"success": ping}} + else: + return {"msg": "there isn't module_name field! can't process this data format", "data": None} + + @property + def deal_ping(self): + try: + return self.__deal_ping() + except Exception as e: + return {"msg": "deal with ping data failed, %s" % e.message, "data": None} + + diff --git a/apps/ops/taskers.py b/apps/ops/taskers.py index 12bdfaff9..17dbd3a5b 100644 --- a/apps/ops/taskers.py +++ b/apps/ops/taskers.py @@ -1,14 +1,12 @@ from __future__ import unicode_literals from .tasks import * + +from .models import Tasker, AnsiblePlay, AnsibleTask, AnsibleHostResult +from uuid import uuid1 from celery.result import AsyncResult -def start_get_hardware_info(*assets): - result = get_asset_hardware_info.delay(*assets) - return result.id - - def get_result(task_id): result = AsyncResult(task_id) if result.ready(): @@ -17,8 +15,54 @@ def get_result(task_id): return {"Completed": False, "data": None} +def start_get_hardware_info(*assets): + name = "Get host hardware information" + uuid = "tasker-" + uuid1().hex + get_asset_hardware_info.delay(name, uuid, *assets) + return uuid + + +def __get_hardware_info(tasker_uuid): + tasker = Tasker.objects.get(uuid=tasker_uuid) + host_results = [] + + for play in tasker.plays.all(): + for t in play.tasks.all(): + for h in t.host_results.all(): + host_results.append(h) + + return host_results + + +def get_hardware_info(tasker_uuid): + try: + return {"msg": None, "data": __get_hardware_info(tasker_uuid)} + except Exception as e: + return {"msg": "query data failed!, %s" % e.message, "data": None} + + def start_ping_test(*assets): - result = asset_test_ping_check.delay(*assets) - return result.id + name = "Test host connection" + uuid = "tasker-" + uuid1().hex + asset_test_ping_check.delay(name, uuid, *assets) + return uuid +def __get_ping_test(tasker_uuid): + tasker = Tasker.objects.get(uuid=tasker_uuid) + host_results = [] + + for play in tasker.plays.all(): + for t in play.tasks.all(): + for h in t.host_results.all(): + host_results.append(h) + + return host_results + + +def get_ping_test(tasker_uuid): + try: + return {"msg": None, "data": __get_ping_test(tasker_uuid)} + except Exception as e: + return {"msg": "query data failed!, %s" % e.message, "data": None} + diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index 9ea8de9b2..21b1470ba 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -7,7 +7,7 @@ from ops.ansible_api import Config, ADHocRunner @shared_task(name="get_asset_hardware_info") -def get_asset_hardware_info(*assets): +def get_asset_hardware_info(task_name, task_uuid, *assets): conf = Config() play_source = { "name": "Get host hardware information", @@ -18,12 +18,12 @@ def get_asset_hardware_info(*assets): ] } hoc = ADHocRunner(conf, play_source, *assets) - ext_code, result = hoc.run() + ext_code, result = hoc.run(task_name, task_uuid) return ext_code, result @shared_task(name="asset_test_ping_check") -def asset_test_ping_check(*assets): +def asset_test_ping_check(task_name, task_uuid, *assets): conf = Config() play_source = { "name": "Test host connection use ping", @@ -34,7 +34,7 @@ def asset_test_ping_check(*assets): ] } hoc = ADHocRunner(conf, play_source, *assets) - ext_code, result = hoc.run() + ext_code, result = hoc.run(task_name, task_uuid) return ext_code, result From 92f396761cbd21291feed8d87df4b86a1bf1a7b9 Mon Sep 17 00:00:00 2001 From: Administrator Date: Tue, 1 Nov 2016 10:37:03 +0800 Subject: [PATCH 09/42] =?UTF-8?q?[future]=20=E5=AE=8C=E6=88=90=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E7=A1=AC=E4=BB=B6=E5=92=8C=E9=93=BE=E6=8E=A5=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E7=9A=84=E7=AC=AC=E4=B8=80=E7=89=88v1=201.=20?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=95=B0=E6=8D=AE=E7=BB=93=E6=9E=9C=E7=9A=84?= =?UTF-8?q?=E5=B0=81=E8=A3=85,=20=E6=B7=BB=E5=8A=A0=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E6=A0=B7=E5=88=97=202.=20=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E7=A1=AC=E4=BB=B6=E5=92=8C=E9=93=BE=E6=8E=A5=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3v1=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/ansible_api.py | 7 ++-- apps/ops/models.py | 7 ++-- apps/ops/taskers.py | 90 ++++++++++++++++++++++++++++++++--------- 3 files changed, 78 insertions(+), 26 deletions(-) diff --git a/apps/ops/ansible_api.py b/apps/ops/ansible_api.py index 09fb06cd5..48a313767 100644 --- a/apps/ops/ansible_api.py +++ b/apps/ops/ansible_api.py @@ -21,7 +21,6 @@ from ansible.plugins.callback import CallbackBase from models import Tasker, AnsiblePlay, AnsibleTask, AnsibleHostResult - logger = logging.getLogger(__name__) @@ -30,13 +29,13 @@ class AnsibleError(StandardError): class Config(object): - """Ansible运行时配置类, 用于初始化Ansible. + """Ansible运行时配置类, 用于初始化Ansible的一些默认配置. """ def __init__(self, verbosity=None, inventory=None, listhosts=None, subset=None, module_paths=None, extra_vars=None, - forks=None, ask_vault_pass=False, vault_password_files=None, new_vault_password_file=None, + forks=10, ask_vault_pass=False, vault_password_files=None, new_vault_password_file=None, output_file=None, tags=None, skip_tags=None, one_line=None, tree=None, ask_sudo_pass=False, ask_su_pass=False, sudo=None, sudo_user=None, become=None, become_method=None, become_user=None, become_ask_pass=False, - ask_pass=False, private_key_file=None, remote_user=None, connection="smart", timeout=None, ssh_common_args=None, + ask_pass=False, private_key_file=None, remote_user=None, connection="smart", timeout=10, ssh_common_args=None, sftp_extra_args=None, scp_extra_args=None, ssh_extra_args=None, poll_interval=None, seconds=None, check=False, syntax=None, diff=None, force_handlers=None, flush_cache=None, listtasks=None, listtags=None, module_path=None): self.verbosity = verbosity diff --git a/apps/ops/models.py b/apps/ops/models.py index d3e52566e..812445531 100644 --- a/apps/ops/models.py +++ b/apps/ops/models.py @@ -142,8 +142,9 @@ class AnsibleHostResult(models.Model): """ result = {} for key in interfaces: - if "ansible_" + key in facts.keys(): - result[key] = facts.get(key) + gather_key = "ansible_" + key + if gather_key in facts.keys(): + result[key] = facts.get(gather_key) return result def __deal_setup(self): @@ -175,7 +176,7 @@ class AnsibleHostResult(models.Model): data['interface'] = self.__gather_interface(facts, interfaces) return {"msg": None, "data": data} else: - return {"msg": "there isn't module_name field! can't process this data format", "data": None} + return {"msg": "there result isn't ansible setup module result! can't process this data format", "data": None} @property def deal_setup(self): diff --git a/apps/ops/taskers.py b/apps/ops/taskers.py index 17dbd3a5b..814a202ea 100644 --- a/apps/ops/taskers.py +++ b/apps/ops/taskers.py @@ -1,8 +1,9 @@ +# ~*~ coding: utf-8 ~*~ from __future__ import unicode_literals from .tasks import * -from .models import Tasker, AnsiblePlay, AnsibleTask, AnsibleHostResult +from .models import Tasker from uuid import uuid1 from celery.result import AsyncResult @@ -15,6 +16,39 @@ def get_result(task_id): return {"Completed": False, "data": None} +def __get_result_by_tasker_id(tasker_uuid, deal_method): + tasker = Tasker.objects.get(uuid=tasker_uuid) + total = tasker.total_hosts + total_len = len(total) + host_results = [] + + # 存储数据 + for play in tasker.plays.all(): + for t in play.tasks.all(): + task = {'name': t.name, 'uuid': t.uuid, 'percentage': 0, 'completed': {'success': {}, 'failed': {}}} + completed = [] + count = 0 + for h in t.host_results.all(): + completed.append(h.name) + count += 1 + if h.is_success: + result = getattr(h, deal_method) + if result.get('msg') is None: + task['completed']['success'][h.name] = result.get('data') + else: + task['completed']['failed'][h.name] = result.get('msg') + else: + task['completed']['failed'][h.name] = h.failed_msg + + # 计算进度 + task['percentage'] = float(count * 100 / total_len) + task['waited'] = list(set(total) - set(completed)) + + host_results.append(task) + + return host_results + + def start_get_hardware_info(*assets): name = "Get host hardware information" uuid = "tasker-" + uuid1().hex @@ -23,18 +57,30 @@ def start_get_hardware_info(*assets): def __get_hardware_info(tasker_uuid): - tasker = Tasker.objects.get(uuid=tasker_uuid) - host_results = [] - - for play in tasker.plays.all(): - for t in play.tasks.all(): - for h in t.host_results.all(): - host_results.append(h) - - return host_results + return __get_result_by_tasker_id(tasker_uuid, 'deal_setup') def get_hardware_info(tasker_uuid): + """ + + :param assets: 资产列表 + :return: 返回数据结构样列 + {u'data': [{u'completed': { + u'failed': {u'192.168.232.135': u'Authentication failure.'}, + u'success': {u'192.168.1.119': {u'cpu': u'GenuineIntel Intel Xeon E312xx (Sandy Bridge) 6\u6838', + u'disk': {: }, + u'env': {: }, + u'interface': {: }, + u'mem': 3951, + u'os': u'Ubuntu 16.04(xenial)', + u'sn': u'NA'}}}, + u'name': u'', + u'percentage': 100.0, + u'uuid': u'87cfedfe-ba55-44ff-bc43-e7e73b869ca1', + u'waited': []} + ], + u'msg': None} + """ try: return {"msg": None, "data": __get_hardware_info(tasker_uuid)} except Exception as e: @@ -49,18 +95,24 @@ def start_ping_test(*assets): def __get_ping_test(tasker_uuid): - tasker = Tasker.objects.get(uuid=tasker_uuid) - host_results = [] - - for play in tasker.plays.all(): - for t in play.tasks.all(): - for h in t.host_results.all(): - host_results.append(h) - - return host_results + return __get_result_by_tasker_id(tasker_uuid, 'deal_ping') def get_ping_test(tasker_uuid): + """ + + :param assets: 资产列表 + :return: 返回数据结构样列 + {u'data': [{u'completed': { + u'failed': {u'192.168.232.135': u'Authentication failure.'}, + u'success': {u'192.168.1.119': {u'success': True}}}, + u'name': u'', + u'percentage': 100.0, + u'uuid': u'3e6e0d3b-bee0-4383-b19e-bec6ba55d346', + u'waited': []} + ], + u'msg': None} + """ try: return {"msg": None, "data": __get_ping_test(tasker_uuid)} except Exception as e: From 1fd0f8fdde5f52fedc19ad13777b49f6fc47774b Mon Sep 17 00:00:00 2001 From: Administrator Date: Thu, 3 Nov 2016 18:26:10 +0800 Subject: [PATCH 10/42] =?UTF-8?q?[future]=20=E6=B7=BB=E5=8A=A0sudo?= =?UTF-8?q?=E7=9A=84=E9=85=8D=E7=BD=AE=E6=A8=A1=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/sudo_api.py | 148 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 apps/ops/sudo_api.py diff --git a/apps/ops/sudo_api.py b/apps/ops/sudo_api.py new file mode 100644 index 000000000..90d3e0644 --- /dev/null +++ b/apps/ops/sudo_api.py @@ -0,0 +1,148 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals + +""" +该模块主要用于提供一个统一的api来管理sudo的配置文件, +支持管理的系统包括: ubuntu(/etc/sudoers) + +因为sudoers配置文件很危险,所以采用生成临时文件, 验证ok后, 进行替换来变更 +""" + + +from jinja2 import Template + + + +__sudoers_tmp__ = """# management by JumpServer +# This file MUST be edited with the 'visudo' command as root. +# +# Please consider adding local content in /etc/sudoers.d/ instead of +# directly modifying this file. +# +# See the man page for details on how to write a sudoers file. +# +Defaults env_reset +Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + +# JumpServer Generate Other Configure is here +{% if Extra_Lines -%} +{% for line in Extra_Lines -%} +{{ line }} +{% endfor %} +{%- endif %} + +# Host alias specification +{% if Host_Alias -%} +{% for flag, items in Host_Alias.iteritems() -%} +Host_Alias {{ flag }} = {{ items|join(', ') }} +{% endfor %} +{%- endif %} + +# User alias specification +{% if User_Alias -%} +{% for flag, items in User_Alias.iteritems() -%} +User_Alias {{ flag }} = {{ items|join(', ') }} +{% endfor %} +{%- endif %} + + +# Cmnd alias specification +{% if Cmnd_Alias -%} +{% for flag, items in Cmnd_Alias.iteritems() -%} +Cmnd_Alias {{ flag }} = {{ items|join(', ') }} +{% endfor %} +{%- endif %} + +# Run as alias specification +{% if Runas_Alias -%} +{% for flag, items in Runas_Alias.iteritems() -%} +Runas_Alias {{ flag }} = {{ items|join(', ') }} +{% endfor %} +{%- endif %} + +# User privilege specification +root ALL=(ALL:ALL) ALL + +# JumpServer Generate User privilege is here. +# Note privileges is a tuple list like [(user, host, runas, command, nopassword),] +{% if privileges -%} +{% for User_Flag, Host_Flag, Runas_Flag, Command_Flag, NopassWord in privileges -%} +{% if NopassWord -%} +{{ User_Flag }} {{ Host_Flag }}=({{ Runas_Flag }}) NOPASSWD: {{ Command_Flag }} +{%- else -%} +{{ User_Flag }} {{ Host_Flag }}=({{ Runas_Flag }}) {{ Command_Flag }} +{%- endif %} +{% endfor %} +{%- endif %} + +# Members of the admin group may gain root privileges +%admin ALL=(ALL) ALL + +# Allow members of group sudo to execute any command +%sudo ALL=(ALL:ALL) ALL + +# See sudoers(5) for more information on "#include" directives: + +#includedir /etc/sudoers.d +""" + + +class Sudo(object): + """ + Sudo配置文件API, 用于配置sudo的配置文件 + + :param user_alias: {: } + :param cmnd_alias: {: } + :param host_alias: {: } + :param runas_alias: {: } + :param extra_lines: [, ,...] + :param privileges: [(user, host, runas, command, nopassword),] + """ + + def __init__(self, user_alias, cmnd_alias, privileges, host_alias=None, runas_alias=None, extra_lines=None): + self.extras = extra_lines + self.users = user_alias + self.commands = cmnd_alias + self.hosts = host_alias + self.runas = runas_alias + self.privileges = privileges + + def get_tmp(self): + template = Template(__sudoers_tmp__) + context = {"User_Alias": self.users, + "Cmnd_Alias": self.commands, + "Host_Alias": self.hosts, + "Runas_Alias": self.runas, + "Extra_Lines": self.extras, + "privileges": self.privileges} + return template.render(context) + + def gen_privileges(self): + pass + + def get_sudo_from_db(self): + pass + + def check_users(self): + pass + + def check_commands(self): + pass + + def check_hosts(self): + pass + + def check_runas(self): + pass + + def check_privileges(self): + pass + + +if __name__ == "__main__": + users = {"a": ['host1, host2'], "b": ["host3", "host4"]} + commands = {"dba": ["bin/bash"], "dev": ["bin/bash"]} + privileges = [("a", "ALL", "root", "dba", True), ("a", "ALL", "root", "dba", False)] + sudo = Sudo(users, commands, privileges, extra_lines=['aaaaaasf sdfasdf', 'bbbbb sfdsdf']) + print sudo.get_tmp() + From 8716d9c725dbf6749e69c4839c2bbc21e547f035 Mon Sep 17 00:00:00 2001 From: Administrator Date: Fri, 4 Nov 2016 18:12:10 +0800 Subject: [PATCH 11/42] =?UTF-8?q?[future]=20=E6=B7=BB=E5=8A=A0=E7=94=A8?= =?UTF-8?q?=E4=BA=8E=E8=AE=B0=E5=BD=95sudo=E7=9B=B8=E5=85=B3=E7=9A=84?= =?UTF-8?q?=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/models.py | 61 ++++++++++++++++++++++++++++++++++++++++++++ apps/ops/sudo_api.py | 1 - 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/apps/ops/models.py b/apps/ops/models.py index 812445531..8ea121d64 100644 --- a/apps/ops/models.py +++ b/apps/ops/models.py @@ -211,3 +211,64 @@ class AnsibleHostResult(models.Model): return {"msg": "deal with ping data failed, %s" % e.message, "data": None} +class HostAlia(models.Model): + name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Host_Alias')) + host_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) + + def __unicode__(self): + return self.name + + +class UserAlia(models.Model): + name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Host_Alias')) + host_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) + + def __unicode__(self): + return self.name + + +class CmdAlia(models.Model): + name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Host_Alias')) + host_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) + + def __unicode__(self): + return self.name + + +class RunasAlia(models.Model): + name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Host_Alias')) + host_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) + + def __unicode__(self): + return self.name + + +class Privilege(models.Model): + name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Host_Alias')) + host_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) + + def __unicode__(self): + return self.name + + +class Sudo(models.Model): + host_alias = models.ManyToManyField(HostAlia, related_name='sudos', blank=True, null=True) + user_alias = models.ManyToManyField(UserAlia, related_name='sudos', blank=True, null=True) + cmd_alias = models.ManyToManyField(CmdAlia, related_name='sudos', blank=True, null=True) + runas_alias = models.ManyToManyField(RunasAlia, related_name='sudos', blank=True, null=True) + privileges = models.ManyToManyField(Privilege, related_name='sudos', blank=True, null=True) + + @property + def content(self): + pass + + + + + + + + + + + diff --git a/apps/ops/sudo_api.py b/apps/ops/sudo_api.py index 90d3e0644..331371d10 100644 --- a/apps/ops/sudo_api.py +++ b/apps/ops/sudo_api.py @@ -12,7 +12,6 @@ from __future__ import unicode_literals from jinja2 import Template - __sudoers_tmp__ = """# management by JumpServer # This file MUST be edited with the 'visudo' command as root. # From d9278c2c2460523da6f11b92e7bbcc278431414b Mon Sep 17 00:00:00 2001 From: Administrator Date: Sat, 5 Nov 2016 13:10:44 +0800 Subject: [PATCH 12/42] =?UTF-8?q?[future]=20=E6=B7=BB=E5=8A=A0sudo?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E7=9A=84=E8=A1=A8=EF=BC=8C=E4=BB=A5=E5=8F=8A?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E7=94=9F=E6=88=90sudo=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E7=9A=84=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/models.py | 180 +++++++++++++++++++++++++++++++++++++++---- apps/ops/sudo_api.py | 147 ----------------------------------- 2 files changed, 165 insertions(+), 162 deletions(-) delete mode 100644 apps/ops/sudo_api.py diff --git a/apps/ops/models.py b/apps/ops/models.py index 8ea121d64..3f0f0205d 100644 --- a/apps/ops/models.py +++ b/apps/ops/models.py @@ -4,7 +4,9 @@ from __future__ import unicode_literals, absolute_import import logging import json +from jinja2 import Template from django.db import models +from assets.models import Asset from django.utils.translation import ugettext_lazy as _ @@ -220,47 +222,195 @@ class HostAlia(models.Model): class UserAlia(models.Model): - name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Host_Alias')) - host_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) + name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('User_Alias')) + user_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) def __unicode__(self): return self.name class CmdAlia(models.Model): - name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Host_Alias')) - host_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) + name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Command_Alias')) + cmd_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) def __unicode__(self): return self.name class RunasAlia(models.Model): - name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Host_Alias')) - host_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) + name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Runas_Alias')) + runas_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) def __unicode__(self): return self.name class Privilege(models.Model): - name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Host_Alias')) - host_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) + user = models.ForeignKey(UserAlia, blank=True, null=True, related_name='privileges') + host = models.ForeignKey(HostAlia, blank=True, null=True, related_name='privileges') + runas = models.ForeignKey(RunasAlia, blank=True, null=True, related_name='privileges') + command = models.ForeignKey(CmdAlia, blank=True, null=True, related_name='privileges') + nopassword = models.BooleanField(default=True, verbose_name=_('Is_NoPassword')) def __unicode__(self): - return self.name + return "[%s %s %s %s %s]" % (self.user.name, + self.host.name, + self.runas.name, + self.command.name, + self.nopassword) + + def to_tuple(self): + return self.user.name, self.host.name, self.runas.name, self.command.name, self.nopassword + + +class Extra_conf(models.Model): + line = models.TextField(blank=True, null=True, verbose_name=_('Extra_Item')) + + def __unicode__(self): + return self.line class Sudo(models.Model): - host_alias = models.ManyToManyField(HostAlia, related_name='sudos', blank=True, null=True) - user_alias = models.ManyToManyField(UserAlia, related_name='sudos', blank=True, null=True) - cmd_alias = models.ManyToManyField(CmdAlia, related_name='sudos', blank=True, null=True) - runas_alias = models.ManyToManyField(RunasAlia, related_name='sudos', blank=True, null=True) - privileges = models.ManyToManyField(Privilege, related_name='sudos', blank=True, null=True) + """ + Sudo配置文件对象, 用于配置sudo的配置文件 + + :param user_alias: {: } + :param cmnd_alias: {: } + :param host_alias: {: } + :param runas_alias: {: } + :param extra_lines: [, ,...] + :param privileges: [(user, host, runas, command, nopassword),] + """ + + asset = models.ForeignKey(Asset, null=True, blank=True, related_name='sudos') + host_alias = models.ManyToManyField(HostAlia, related_name='sudos', blank=True) + user_alias = models.ManyToManyField(UserAlia, related_name='sudos', blank=True) + cmnd_alias = models.ManyToManyField(CmdAlia, related_name='sudos', blank=True) + runas_alias = models.ManyToManyField(RunasAlia, related_name='sudos', blank=True) + extra_lines = models.ManyToManyField(Extra_conf, related_name='sudos', blank=True) + privilege_items = models.ManyToManyField(Privilege, related_name='sudos', blank=True) + + @property + def users(self): + ret = {} + for user in self.user_alias.all(): + ret[user.name] = user.user_items.split(',') + return ret + + @property + def commands(self): + ret = {} + for cmd in self.cmnd_alias.all(): + ret[cmd.name] = cmd.cmd_items.split(',') + return ret + + @property + def hosts(self): + ret = {} + for host in self.host_alias.all(): + ret[host.name] = host.host_items.split(',') + return ret + + @property + def runas(self): + ret = {} + for runas in self.runas_alias.all(): + ret[runas.name] = runas.runas_items.split(',') + return ret + + @property + def extras(self): + return [extra.line for extra in self.extra_lines.all()] + + @property + def privileges(self): + return [privilege.to_tuple() for privilege in self.privilege_items.all()] @property def content(self): - pass + template = Template(self.__sudoers_jinja2_tmp__) + context = {"User_Alias": self.users, + "Cmnd_Alias": self.commands, + "Host_Alias": self.hosts, + "Runas_Alias": self.runas, + "Extra_Lines": self.extras, + "privileges": self.privileges} + return template.render(context) + + @property + def __sudoers_jinja2_tmp__(self): + return """# management by JumpServer +# This file MUST be edited with the 'visudo' command as root. +# +# Please consider adding local content in /etc/sudoers.d/ instead of +# directly modifying this file. +# +# See the man page for details on how to write a sudoers file. +# +Defaults env_reset +Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + +# JumpServer Generate Other Configure is here +{% if Extra_Lines -%} +{% for line in Extra_Lines -%} +{{ line }} +{% endfor %} +{%- endif %} + +# Host alias specification +{% if Host_Alias -%} +{% for flag, items in Host_Alias.iteritems() -%} +Host_Alias {{ flag }} = {{ items|join(', ') }} +{% endfor %} +{%- endif %} + +# User alias specification +{% if User_Alias -%} +{% for flag, items in User_Alias.iteritems() -%} +User_Alias {{ flag }} = {{ items|join(', ') }} +{% endfor %} +{%- endif %} + + +# Cmnd alias specification +{% if Cmnd_Alias -%} +{% for flag, items in Cmnd_Alias.iteritems() -%} +Cmnd_Alias {{ flag }} = {{ items|join(', ') }} +{% endfor %} +{%- endif %} + +# Run as alias specification +{% if Runas_Alias -%} +{% for flag, items in Runas_Alias.iteritems() -%} +Runas_Alias {{ flag }} = {{ items|join(', ') }} +{% endfor %} +{%- endif %} + +# User privilege specification +root ALL=(ALL:ALL) ALL + +# JumpServer Generate User privilege is here. +# Note privileges is a tuple list like [(user, host, runas, command, nopassword),] +{% if privileges -%} +{% for User_Flag, Host_Flag, Runas_Flag, Command_Flag, NopassWord in privileges -%} +{% if NopassWord -%} +{{ User_Flag }} {{ Host_Flag }}=({{ Runas_Flag }}) NOPASSWD: {{ Command_Flag }} +{%- else -%} +{{ User_Flag }} {{ Host_Flag }}=({{ Runas_Flag }}) {{ Command_Flag }} +{%- endif %} +{% endfor %} +{%- endif %} + +# Members of the admin group may gain root privileges +%admin ALL=(ALL) ALL + +# Allow members of group sudo to execute any command +%sudo ALL=(ALL:ALL) ALL + +# See sudoers(5) for more information on "#include" directives: + +#includedir /etc/sudoers.d +""" diff --git a/apps/ops/sudo_api.py b/apps/ops/sudo_api.py deleted file mode 100644 index 331371d10..000000000 --- a/apps/ops/sudo_api.py +++ /dev/null @@ -1,147 +0,0 @@ -# ~*~ coding: utf-8 ~*~ -from __future__ import unicode_literals - -""" -该模块主要用于提供一个统一的api来管理sudo的配置文件, -支持管理的系统包括: ubuntu(/etc/sudoers) - -因为sudoers配置文件很危险,所以采用生成临时文件, 验证ok后, 进行替换来变更 -""" - - -from jinja2 import Template - - -__sudoers_tmp__ = """# management by JumpServer -# This file MUST be edited with the 'visudo' command as root. -# -# Please consider adding local content in /etc/sudoers.d/ instead of -# directly modifying this file. -# -# See the man page for details on how to write a sudoers file. -# -Defaults env_reset -Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" - -# JumpServer Generate Other Configure is here -{% if Extra_Lines -%} -{% for line in Extra_Lines -%} -{{ line }} -{% endfor %} -{%- endif %} - -# Host alias specification -{% if Host_Alias -%} -{% for flag, items in Host_Alias.iteritems() -%} -Host_Alias {{ flag }} = {{ items|join(', ') }} -{% endfor %} -{%- endif %} - -# User alias specification -{% if User_Alias -%} -{% for flag, items in User_Alias.iteritems() -%} -User_Alias {{ flag }} = {{ items|join(', ') }} -{% endfor %} -{%- endif %} - - -# Cmnd alias specification -{% if Cmnd_Alias -%} -{% for flag, items in Cmnd_Alias.iteritems() -%} -Cmnd_Alias {{ flag }} = {{ items|join(', ') }} -{% endfor %} -{%- endif %} - -# Run as alias specification -{% if Runas_Alias -%} -{% for flag, items in Runas_Alias.iteritems() -%} -Runas_Alias {{ flag }} = {{ items|join(', ') }} -{% endfor %} -{%- endif %} - -# User privilege specification -root ALL=(ALL:ALL) ALL - -# JumpServer Generate User privilege is here. -# Note privileges is a tuple list like [(user, host, runas, command, nopassword),] -{% if privileges -%} -{% for User_Flag, Host_Flag, Runas_Flag, Command_Flag, NopassWord in privileges -%} -{% if NopassWord -%} -{{ User_Flag }} {{ Host_Flag }}=({{ Runas_Flag }}) NOPASSWD: {{ Command_Flag }} -{%- else -%} -{{ User_Flag }} {{ Host_Flag }}=({{ Runas_Flag }}) {{ Command_Flag }} -{%- endif %} -{% endfor %} -{%- endif %} - -# Members of the admin group may gain root privileges -%admin ALL=(ALL) ALL - -# Allow members of group sudo to execute any command -%sudo ALL=(ALL:ALL) ALL - -# See sudoers(5) for more information on "#include" directives: - -#includedir /etc/sudoers.d -""" - - -class Sudo(object): - """ - Sudo配置文件API, 用于配置sudo的配置文件 - - :param user_alias: {: } - :param cmnd_alias: {: } - :param host_alias: {: } - :param runas_alias: {: } - :param extra_lines: [, ,...] - :param privileges: [(user, host, runas, command, nopassword),] - """ - - def __init__(self, user_alias, cmnd_alias, privileges, host_alias=None, runas_alias=None, extra_lines=None): - self.extras = extra_lines - self.users = user_alias - self.commands = cmnd_alias - self.hosts = host_alias - self.runas = runas_alias - self.privileges = privileges - - def get_tmp(self): - template = Template(__sudoers_tmp__) - context = {"User_Alias": self.users, - "Cmnd_Alias": self.commands, - "Host_Alias": self.hosts, - "Runas_Alias": self.runas, - "Extra_Lines": self.extras, - "privileges": self.privileges} - return template.render(context) - - def gen_privileges(self): - pass - - def get_sudo_from_db(self): - pass - - def check_users(self): - pass - - def check_commands(self): - pass - - def check_hosts(self): - pass - - def check_runas(self): - pass - - def check_privileges(self): - pass - - -if __name__ == "__main__": - users = {"a": ['host1, host2'], "b": ["host3", "host4"]} - commands = {"dba": ["bin/bash"], "dev": ["bin/bash"]} - privileges = [("a", "ALL", "root", "dba", True), ("a", "ALL", "root", "dba", False)] - sudo = Sudo(users, commands, privileges, extra_lines=['aaaaaasf sdfasdf', 'bbbbb sfdsdf']) - print sudo.get_tmp() - From 82412831d5bf160f813c7b1a37ed5f11d43f5a96 Mon Sep 17 00:00:00 2001 From: Administrator Date: Wed, 16 Nov 2016 14:20:44 +0800 Subject: [PATCH 13/42] initial sudo views --- apps/ops/hands.py | 14 ++++++ apps/ops/models.py | 4 +- apps/ops/utils.py | 41 ++++++++++++++- apps/ops/views.py | 122 ++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 176 insertions(+), 5 deletions(-) create mode 100644 apps/ops/hands.py diff --git a/apps/ops/hands.py b/apps/ops/hands.py new file mode 100644 index 000000000..1cdbd13a2 --- /dev/null +++ b/apps/ops/hands.py @@ -0,0 +1,14 @@ +""" + jumpserver.__app__.hands.py + ~~~~~~~~~~~~~~~~~ + + This app depends other apps api, function .. should be import or write mack here. + + Other module of this app shouldn't connect with other app. + + :copyright: (c) 2014-2016 by Jumpserver Team. + :license: GPL v2, see LICENSE for more details. +""" + + +from users.utils import AdminUserRequiredMixin \ No newline at end of file diff --git a/apps/ops/models.py b/apps/ops/models.py index 3f0f0205d..70fe0cc7f 100644 --- a/apps/ops/models.py +++ b/apps/ops/models.py @@ -334,7 +334,7 @@ class Sudo(models.Model): "Host_Alias": self.hosts, "Runas_Alias": self.runas, "Extra_Lines": self.extras, - "privileges": self.privileges} + "Privileges": self.privileges} return template.render(context) @property @@ -392,7 +392,7 @@ root ALL=(ALL:ALL) ALL # JumpServer Generate User privilege is here. # Note privileges is a tuple list like [(user, host, runas, command, nopassword),] {% if privileges -%} -{% for User_Flag, Host_Flag, Runas_Flag, Command_Flag, NopassWord in privileges -%} +{% for User_Flag, Host_Flag, Runas_Flag, Command_Flag, NopassWord in Privileges -%} {% if NopassWord -%} {{ User_Flag }} {{ Host_Flag }}=({{ Runas_Flag }}) NOPASSWD: {{ Command_Flag }} {%- else -%} diff --git a/apps/ops/utils.py b/apps/ops/utils.py index c84951fd7..7307f91ca 100644 --- a/apps/ops/utils.py +++ b/apps/ops/utils.py @@ -1,2 +1,41 @@ # ~*~ coding: utf-8 ~*~ -# + + +class CreateHostAliasMinxin(object): + pass + + +class CreateUserAliasMinxin(object): + pass + + +class CreateCmdAliasMinxin(object): + pass + + +class CreateRunasAliasMinxin(object): + pass + + +class CreateExtralineAliasMinxin(object): + pass + + +class UpdateHostAliasMinxin(object): + pass + + +class UpdateUserAliasMinxin(object): + pass + + +class UpdateCmdAliasMinxin(object): + pass + + +class UpdateRunasAliasMinxin(object): + pass + + +class UpdateExtralineAliasMinxin(object): + pass \ No newline at end of file diff --git a/apps/ops/views.py b/apps/ops/views.py index 91ea44a21..b161cd0e5 100644 --- a/apps/ops/views.py +++ b/apps/ops/views.py @@ -1,3 +1,121 @@ -from django.shortcuts import render +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals -# Create your views here. +from django.conf import settings +from django.views.generic.list import ListView, MultipleObjectMixin +from django.views.generic.edit import CreateView, DeleteView, UpdateView +from django.views.generic.detail import DetailView, SingleObjectMixin + +from .hands import AdminUserRequiredMixin +from .utils import * + + +class SudoListView(AdminUserRequiredMixin, ListView): + paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + model = Asset + context_object_name = 'asset_list' + template_name = 'assets/asset_list.html' + + def get_queryset(self): + queryset = super(AssetListView, self).get_queryset() + queryset = sorted(queryset, key=self.sorted_by_valid_and_ip) + return queryset + + @staticmethod + def sorted_by_valid_and_ip(asset): + ip_list = int_seq(asset.ip.split('.')) + ip_list.insert(0, asset.is_valid()[0]) + return ip_list + + def get_context_data(self, **kwargs): + context = { + 'app': 'Assets', + 'action': 'asset list', + 'tag_list': [(i.id,i.name,i.asset_set.all().count())for i in Tag.objects.all().order_by('name')] + + } + kwargs.update(context) + return super(AssetListView, self).get_context_data(**kwargs) + + +class SudoCreateView(AdminUserRequiredMixin, + CreateHostAliasMinxin, + CreateUserAliasMinxin, + CreateCmdAliasMinxin, + CreateRunasAliasMinxin, + CreateExtralineAliasMinxin, + CreateView): + model = Asset + tag_type = 'asset' + form_class = AssetCreateForm + template_name = 'assets/asset_create.html' + success_url = reverse_lazy('assets:asset-list') + + def form_valid(self, form): + asset = form.save() + asset.created_by = self.request.user.username or 'Admin' + asset.save() + return super(AssetCreateView, self).form_valid(form) + + def form_invalid(self, form): + print(form.errors) + return super(AssetCreateView, self).form_invalid(form) + + + def get_context_data(self, **kwargs): + context = { + 'app': 'Assets', + 'action': 'Create asset', + } + kwargs.update(context) + + return super(AssetCreateView, self).get_context_data(**kwargs) + + +class SudoUpdateView(AdminUserRequiredMixin, + UpdateHostAliasMinxin, + UpdateUserAliasMinxin, + UpdateCmdAliasMinxin, + UpdateRunasAliasMinxin, + UpdateExtralineAliasMinxin, + UpdateView): + model = Asset + form_class = AssetCreateForm + template_name = 'assets/asset_update.html' + success_url = reverse_lazy('assets:asset-list') + + def get_context_data(self, **kwargs): + context = { + 'app': 'Assets', + 'action': 'Update asset', + } + kwargs.update(context) + return super(AssetUpdateView, self).get_context_data(**kwargs) + + def form_invalid(self, form): + print(form.errors) + return super(AssetUpdateView, self).form_invalid(form) + + +class SudoDeleteView(DeleteView): + model = Asset + template_name = 'assets/delete_confirm.html' + success_url = reverse_lazy('assets:asset-list') + + +class SudoDetailView(DetailView): + model = Asset + context_object_name = 'asset' + template_name = 'assets/asset_detail.html' + + def get_context_data(self, **kwargs): + asset_groups = self.object.groups.all() + context = { + 'app': 'Assets', + 'action': 'Asset detail', + 'asset_groups_remain': [asset_group for asset_group in AssetGroup.objects.all() + if asset_group not in asset_groups], + 'asset_groups': asset_groups, + } + kwargs.update(context) + return super(AssetDetailView, self).get_context_data(**kwargs) From c588436d55be8ebcaf220a75fc45750ce734671b Mon Sep 17 00:00:00 2001 From: Administrator Date: Wed, 16 Nov 2016 15:00:46 +0800 Subject: [PATCH 14/42] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E5=92=8Capi=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/api/__init__.py | 0 apps/ops/api/serializers.py | 0 apps/ops/api/views.py | 0 apps/ops/templates/sudo/create.html | 10 ++++++++++ apps/ops/templates/sudo/detail.html | 10 ++++++++++ apps/ops/templates/sudo/list.html | 10 ++++++++++ apps/ops/templates/sudo/update.html | 10 ++++++++++ apps/ops/views.py | 17 ++--------------- 8 files changed, 42 insertions(+), 15 deletions(-) create mode 100644 apps/ops/api/__init__.py create mode 100644 apps/ops/api/serializers.py create mode 100644 apps/ops/api/views.py create mode 100644 apps/ops/templates/sudo/create.html create mode 100644 apps/ops/templates/sudo/detail.html create mode 100644 apps/ops/templates/sudo/list.html create mode 100644 apps/ops/templates/sudo/update.html diff --git a/apps/ops/api/__init__.py b/apps/ops/api/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/ops/api/serializers.py b/apps/ops/api/serializers.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/ops/api/views.py b/apps/ops/api/views.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/ops/templates/sudo/create.html b/apps/ops/templates/sudo/create.html new file mode 100644 index 000000000..566549bdf --- /dev/null +++ b/apps/ops/templates/sudo/create.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/apps/ops/templates/sudo/detail.html b/apps/ops/templates/sudo/detail.html new file mode 100644 index 000000000..566549bdf --- /dev/null +++ b/apps/ops/templates/sudo/detail.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/apps/ops/templates/sudo/list.html b/apps/ops/templates/sudo/list.html new file mode 100644 index 000000000..566549bdf --- /dev/null +++ b/apps/ops/templates/sudo/list.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/apps/ops/templates/sudo/update.html b/apps/ops/templates/sudo/update.html new file mode 100644 index 000000000..566549bdf --- /dev/null +++ b/apps/ops/templates/sudo/update.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/apps/ops/views.py b/apps/ops/views.py index b161cd0e5..89344583f 100644 --- a/apps/ops/views.py +++ b/apps/ops/views.py @@ -7,7 +7,6 @@ from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.views.generic.detail import DetailView, SingleObjectMixin from .hands import AdminUserRequiredMixin -from .utils import * class SudoListView(AdminUserRequiredMixin, ListView): @@ -38,13 +37,7 @@ class SudoListView(AdminUserRequiredMixin, ListView): return super(AssetListView, self).get_context_data(**kwargs) -class SudoCreateView(AdminUserRequiredMixin, - CreateHostAliasMinxin, - CreateUserAliasMinxin, - CreateCmdAliasMinxin, - CreateRunasAliasMinxin, - CreateExtralineAliasMinxin, - CreateView): +class SudoCreateView(AdminUserRequiredMixin, CreateView): model = Asset tag_type = 'asset' form_class = AssetCreateForm @@ -72,13 +65,7 @@ class SudoCreateView(AdminUserRequiredMixin, return super(AssetCreateView, self).get_context_data(**kwargs) -class SudoUpdateView(AdminUserRequiredMixin, - UpdateHostAliasMinxin, - UpdateUserAliasMinxin, - UpdateCmdAliasMinxin, - UpdateRunasAliasMinxin, - UpdateExtralineAliasMinxin, - UpdateView): +class SudoUpdateView(AdminUserRequiredMixin, UpdateView): model = Asset form_class = AssetCreateForm template_name = 'assets/asset_update.html' From 961ecb3ee860ad0386cdbdd76d7077b62119a9da Mon Sep 17 00:00:00 2001 From: Administrator Date: Sun, 20 Nov 2016 12:18:44 +0800 Subject: [PATCH 15/42] =?UTF-8?q?[future]=20=E6=B7=BB=E5=8A=A0sudo?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E7=9A=84api=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/api.py | 5 - apps/ops/api/serializers.py | 36 +++++++ apps/ops/api/views.py | 195 ++++++++++++++++++++++++++++++++++++ apps/ops/urls.py | 34 +++++++ 4 files changed, 265 insertions(+), 5 deletions(-) delete mode 100644 apps/ops/api.py create mode 100644 apps/ops/urls.py diff --git a/apps/ops/api.py b/apps/ops/api.py deleted file mode 100644 index e36433172..000000000 --- a/apps/ops/api.py +++ /dev/null @@ -1,5 +0,0 @@ -# ~*~ coding: utf-8 ~*~ - -from __future__ import unicode_literals - - diff --git a/apps/ops/api/serializers.py b/apps/ops/api/serializers.py index e69de29bb..e580028f6 100644 --- a/apps/ops/api/serializers.py +++ b/apps/ops/api/serializers.py @@ -0,0 +1,36 @@ +# ~*~ coding: utf-8 ~*~ + +from __future__ import unicode_literals, print_function +from ..models import HostAlia, UserAlia, CmdAlia, RunasAlia, Extra_conf +from rest_framework import serializers + + +class HostAliaSerializer(serializers.ModelSerializer): + + class Meta: + model = HostAlia + + +class CmdAliaSerializer(serializers.ModelSerializer): + + class Meta: + model = CmdAlia + + +class UserAliaSerializer(serializers.ModelSerializer): + + class Meta: + model = UserAlia + + +class RunasAliaSerializer(serializers.ModelSerializer): + + class Meta: + model = RunasAlia + + +class ExtraconfSerializer(serializers.ModelSerializer): + + class Meta: + model = Extra_conf + diff --git a/apps/ops/api/views.py b/apps/ops/api/views.py index e69de29bb..065a04639 100644 --- a/apps/ops/api/views.py +++ b/apps/ops/api/views.py @@ -0,0 +1,195 @@ +# ~*~ coding: utf-8 ~*~ + +from __future__ import unicode_literals +from rest_framework import viewsets +from rest_framework import status +from rest_framework.response import Response + +from serializers import * + + +class HostAliaViewSet(viewsets.GenericViewSet): + queryset = HostAlia.objects.all() + serializer_class = HostAliaSerializer + permission_classes = None + + def list(self): + h_alias = self.get_queryset() + h_serializer = self.get_serializer(h_alias, many=True) + return Response(h_serializer.data) + + def create(self): + serializer = self.get_serializer(data=self.request.data) + serializer.is_valid(raise_exception=True) + self.perform_create() + return Response(serializer.data, status=status.HTTP_201_CREATED) + + def retrieve(self, *args, **kwargs): + h_alias = self.get_object() + serializer = self.get_serializer(h_alias) + return Response(serializer.data) + + def update(self, *args, **kwargs): + h_alias = self.get_object() + serializer = self.get_serializer(h_alias, data=self.request.data) + serializer.is_valid(raise_exception=False) + self.perform_create() + return Response(serializer.data) + + def destroy(self, *args, **kwargs): + h_alias = self.get_object() + h_alias.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + def perform_create(self): + pass + + +class CmdAliaViewSet(viewsets.GenericViewSet): + queryset = CmdAlia.objects.all() + serializer_class = CmdAliaSerializer + permission_classes = None + + def list(self): + c_alias = self.get_queryset() + c_serializer = self.get_serializer(c_alias, many=True) + return Response(c_serializer.data) + + def create(self): + serializer = self.get_serializer(data=self.request.data) + serializer.is_valid(raise_exception=True) + self.perform_create() + return Response(serializer.data, status=status.HTTP_201_CREATED) + + def retrieve(self, *args, **kwargs): + c_alias = self.get_object() + serializer = self.get_serializer(c_alias) + return Response(serializer.data) + + def update(self, *args, **kwargs): + c_alias = self.get_object() + serializer = self.get_serializer(c_alias, data=self.request.data) + serializer.is_valid(raise_exception=False) + self.perform_create() + return Response(serializer.data) + + def destroy(self, *args, **kwargs): + c_alias = self.get_object() + c_alias.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + def perform_create(self): + pass + + +class UserAliaViewSet(viewsets.GenericViewSet): + queryset = UserAlia.objects.all() + serializer_class = UserAliaSerializer + permission_classes = None + + def list(self): + u_alias = self.get_queryset() + u_serializer = self.get_serializer(u_alias, many=True) + return Response(u_serializer.data) + + def create(self): + serializer = self.get_serializer(data=self.request.data) + serializer.is_valid(raise_exception=True) + self.perform_create() + return Response(serializer.data, status=status.HTTP_201_CREATED) + + def retrieve(self, *args, **kwargs): + u_alias = self.get_object() + serializer = self.get_serializer(u_alias) + return Response(serializer.data) + + def update(self, *args, **kwargs): + u_alias = self.get_object() + serializer = self.get_serializer(u_alias, data=self.request.data) + serializer.is_valid(raise_exception=False) + self.perform_create() + return Response(serializer.data) + + def destroy(self, *args, **kwargs): + u_alias = self.get_object() + u_alias.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + def perform_create(self): + pass + + +class RunasAliaViewSet(viewsets.GenericViewSet): + queryset = RunasAlia.objects.all() + serializer_class = RunasAliaSerializer + permission_classes = None + + def list(self): + r_alias = self.get_queryset() + r_serializer = self.get_serializer(r_alias, many=True) + return Response(r_serializer.data) + + def create(self): + serializer = self.get_serializer(data=self.request.data) + serializer.is_valid(raise_exception=True) + self.perform_create() + return Response(serializer.data, status=status.HTTP_201_CREATED) + + def retrieve(self, *args, **kwargs): + r_alias = self.get_object() + serializer = self.get_serializer(r_alias) + return Response(serializer.data) + + def update(self, *args, **kwargs): + r_alias = self.get_object() + serializer = self.get_serializer(r_alias, data=self.request.data) + serializer.is_valid(raise_exception=False) + self.perform_create() + return Response(serializer.data) + + def destroy(self, *args, **kwargs): + r_alias = self.get_object() + r_alias.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + def perform_create(self): + pass + + +class ExtraconfViewSet(viewsets.GenericViewSet): + queryset = Extra_conf.objects.all() + serializer_class = ExtraconfSerializer + permission_classes = None + + def list(self): + e_alias = self.get_queryset() + e_serializer = self.get_serializer(e_alias, many=True) + return Response(e_serializer.data) + + def create(self): + serializer = self.get_serializer(data=self.request.data) + serializer.is_valid(raise_exception=True) + self.perform_create() + return Response(serializer.data, status=status.HTTP_201_CREATED) + + def retrieve(self, *args, **kwargs): + e_alias = self.get_object() + serializer = self.get_serializer(e_alias) + return Response(serializer.data) + + def update(self, *args, **kwargs): + e_alias = self.get_object() + serializer = self.get_serializer(e_alias, data=self.request.data) + serializer.is_valid(raise_exception=False) + self.perform_create() + return Response(serializer.data) + + def destroy(self, *args, **kwargs): + e_alias = self.get_object() + e_alias.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + def perform_create(self): + pass + + diff --git a/apps/ops/urls.py b/apps/ops/urls.py new file mode 100644 index 000000000..9c28176d7 --- /dev/null +++ b/apps/ops/urls.py @@ -0,0 +1,34 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals + +from rest_framework.routers import DefaultRouter +from django.conf.urls import url, include + + +from api import views as api_view +import views as mvc_view + + +app_name = 'ops' + +router = DefaultRouter() +router.register(r'HostAlias', api_view.HostAliaViewSet) +router.register(r'UserAlias', api_view.UserAliaViewSet) +router.register(r'CmdAlias', api_view.CmdAliaViewSet) +router.register(r'RunasAlias', api_view.RunasAliaViewSet) +router.register(r'Extraconf', api_view.ExtraconfViewSet) + +urlpatterns = [ + # Resource Sudo url + url(r'^sudo/list$', mvc_view.SudoListView.as_view(), name='sudo-list'), + url(r'^sudo/create', mvc_view.SudoCreateView(), name='sudo-create'), + url(r'^sudo/detail', mvc_view.SudoDetailView(), name='sudo-detail'), + url(r'^sudo/update', mvc_view.SudoUpdateView(), name='sudo-update'), + url(r'^sudo/delete', mvc_view.SudoDeleteView(), name='sudo-delete'), +] + +urlpatterns += [ + url(r'^api/ops/sudo', include(router.urls)), +] + + From 39ae4a3a10fe97393c479941ddec3b00ae6da097 Mon Sep 17 00:00:00 2001 From: Administrator Date: Sun, 20 Nov 2016 12:22:56 +0800 Subject: [PATCH 16/42] =?UTF-8?q?[future]=20url=20=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/urls.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/ops/urls.py b/apps/ops/urls.py index 9c28176d7..c2ff9f62c 100644 --- a/apps/ops/urls.py +++ b/apps/ops/urls.py @@ -21,10 +21,10 @@ router.register(r'Extraconf', api_view.ExtraconfViewSet) urlpatterns = [ # Resource Sudo url url(r'^sudo/list$', mvc_view.SudoListView.as_view(), name='sudo-list'), - url(r'^sudo/create', mvc_view.SudoCreateView(), name='sudo-create'), - url(r'^sudo/detail', mvc_view.SudoDetailView(), name='sudo-detail'), - url(r'^sudo/update', mvc_view.SudoUpdateView(), name='sudo-update'), - url(r'^sudo/delete', mvc_view.SudoDeleteView(), name='sudo-delete'), + url(r'^sudo/create', mvc_view.SudoCreateView.as_view(), name='sudo-create'), + url(r'^sudo/detail', mvc_view.SudoDetailView.as_view(), name='sudo-detail'), + url(r'^sudo/update', mvc_view.SudoUpdateView.as_view(), name='sudo-update'), + url(r'^sudo/delete', mvc_view.SudoDeleteView.as_view(), name='sudo-delete'), ] urlpatterns += [ From 5ae2711c6ecc0e7d860bd7d215a2f1d7b92b71ee Mon Sep 17 00:00:00 2001 From: Administrator Date: Sun, 20 Nov 2016 14:48:18 +0800 Subject: [PATCH 17/42] =?UTF-8?q?sudo=20privilege=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E8=B5=B0api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/api/serializers.py | 15 +++++- apps/ops/api/views.py | 52 +++++++++++++++++++ apps/ops/models.py | 30 ++--------- apps/ops/urls.py | 10 ++-- apps/ops/utils.py | 41 +++------------ apps/ops/views.py | 101 +++++------------------------------- 6 files changed, 98 insertions(+), 151 deletions(-) diff --git a/apps/ops/api/serializers.py b/apps/ops/api/serializers.py index e580028f6..4111a64ff 100644 --- a/apps/ops/api/serializers.py +++ b/apps/ops/api/serializers.py @@ -1,7 +1,7 @@ # ~*~ coding: utf-8 ~*~ from __future__ import unicode_literals, print_function -from ..models import HostAlia, UserAlia, CmdAlia, RunasAlia, Extra_conf +from ..models import HostAlia, UserAlia, CmdAlia, RunasAlia, Extra_conf, Privilege, Sudo from rest_framework import serializers @@ -34,3 +34,16 @@ class ExtraconfSerializer(serializers.ModelSerializer): class Meta: model = Extra_conf + +class PrivilegeSerializer(serializers.ModelSerializer): + + class Meta: + model = Privilege + + +class SudoSerializer(serializers.ModelSerializer): + + class Meta: + model = Sudo + + diff --git a/apps/ops/api/views.py b/apps/ops/api/views.py index 065a04639..b0eec81e0 100644 --- a/apps/ops/api/views.py +++ b/apps/ops/api/views.py @@ -193,3 +193,55 @@ class ExtraconfViewSet(viewsets.GenericViewSet): pass +class PrivilegeViewSet(viewsets.GenericViewSet): + queryset = Privilege.objects.all() + serializer_class = PrivilegeSerializer + permission_classes = None + + def list(self): + pass + + def create(self): + pass + + def retrieve(self, *args, **kwargs): + pass + + def update(self, *args, **kwargs): + pass + + def destroy(self, *args, **kwargs): + privilege = self.get_object() + privilege.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + def perform_create(self): + pass + + +class SudoViewSet(viewsets.GenericViewSet): + queryset = Sudo.objects.all() + serializer_class = SudoSerializer + permission_classes = None + + def list(self): + pass + + def create(self): + pass + + def retrieve(self, *args, **kwargs): + pass + + def update(self, *args, **kwargs): + pass + + def destroy(self, *args, **kwargs): + sudo = self.get_object() + sudo.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + def perform_create(self): + pass + + diff --git a/apps/ops/models.py b/apps/ops/models.py index 70fe0cc7f..ba5c4cc1e 100644 --- a/apps/ops/models.py +++ b/apps/ops/models.py @@ -274,49 +274,29 @@ class Sudo(models.Model): """ Sudo配置文件对象, 用于配置sudo的配置文件 - :param user_alias: {: } - :param cmnd_alias: {: } - :param host_alias: {: } - :param runas_alias: {: } :param extra_lines: [, ,...] :param privileges: [(user, host, runas, command, nopassword),] """ asset = models.ForeignKey(Asset, null=True, blank=True, related_name='sudos') - host_alias = models.ManyToManyField(HostAlia, related_name='sudos', blank=True) - user_alias = models.ManyToManyField(UserAlia, related_name='sudos', blank=True) - cmnd_alias = models.ManyToManyField(CmdAlia, related_name='sudos', blank=True) - runas_alias = models.ManyToManyField(RunasAlia, related_name='sudos', blank=True) extra_lines = models.ManyToManyField(Extra_conf, related_name='sudos', blank=True) privilege_items = models.ManyToManyField(Privilege, related_name='sudos', blank=True) @property def users(self): - ret = {} - for user in self.user_alias.all(): - ret[user.name] = user.user_items.split(',') - return ret + return {privilege.user.name: privilege.user.user_items.split(',') for privilege in self.privilege_items.all()} @property def commands(self): - ret = {} - for cmd in self.cmnd_alias.all(): - ret[cmd.name] = cmd.cmd_items.split(',') - return ret + return {privilege.command.name: privilege.command.cmd_items.split(',') for privilege in self.privilege_items.all()} @property def hosts(self): - ret = {} - for host in self.host_alias.all(): - ret[host.name] = host.host_items.split(',') - return ret + return {privilege.host.name: privilege.host.host_items.split(',') for privilege in self.privilege_items.all()} @property def runas(self): - ret = {} - for runas in self.runas_alias.all(): - ret[runas.name] = runas.runas_items.split(',') - return ret + return {privilege.runas.name: privilege.runas.runas_items.split(',') for privilege in self.privilege_items.all()} @property def extras(self): @@ -391,7 +371,7 @@ root ALL=(ALL:ALL) ALL # JumpServer Generate User privilege is here. # Note privileges is a tuple list like [(user, host, runas, command, nopassword),] -{% if privileges -%} +{% if Privileges -%} {% for User_Flag, Host_Flag, Runas_Flag, Command_Flag, NopassWord in Privileges -%} {% if NopassWord -%} {{ User_Flag }} {{ Host_Flag }}=({{ Runas_Flag }}) NOPASSWD: {{ Command_Flag }} diff --git a/apps/ops/urls.py b/apps/ops/urls.py index c2ff9f62c..ea4b669b7 100644 --- a/apps/ops/urls.py +++ b/apps/ops/urls.py @@ -20,11 +20,11 @@ router.register(r'Extraconf', api_view.ExtraconfViewSet) urlpatterns = [ # Resource Sudo url - url(r'^sudo/list$', mvc_view.SudoListView.as_view(), name='sudo-list'), - url(r'^sudo/create', mvc_view.SudoCreateView.as_view(), name='sudo-create'), - url(r'^sudo/detail', mvc_view.SudoDetailView.as_view(), name='sudo-detail'), - url(r'^sudo/update', mvc_view.SudoUpdateView.as_view(), name='sudo-update'), - url(r'^sudo/delete', mvc_view.SudoDeleteView.as_view(), name='sudo-delete'), + url(r'^sudo/list$', mvc_view.SudoListView.as_view(), name='sudo-list'), + url(r'^sudo/create$', mvc_view.SudoCreateView.as_view(), name='sudo-create'), + url(r'^sudo/detail$', mvc_view.SudoDetailView.as_view(), name='sudo-detail'), + url(r'^sudo/update$', mvc_view.SudoUpdateView.as_view(), name='sudo-update'), + url(r'^sudo/delete$', mvc_view.SudoDeleteView.as_view(), name='sudo-delete'), ] urlpatterns += [ diff --git a/apps/ops/utils.py b/apps/ops/utils.py index 7307f91ca..9cb69df23 100644 --- a/apps/ops/utils.py +++ b/apps/ops/utils.py @@ -1,41 +1,16 @@ # ~*~ coding: utf-8 ~*~ -class CreateHostAliasMinxin(object): - pass +class CreateSudoPrivilegesMixin(object): + + def create_privilege(self): + pass -class CreateUserAliasMinxin(object): - pass +class ListSudoPrivilegesMixin(object): + + def get_all_privilege(self): + pass -class CreateCmdAliasMinxin(object): - pass - -class CreateRunasAliasMinxin(object): - pass - - -class CreateExtralineAliasMinxin(object): - pass - - -class UpdateHostAliasMinxin(object): - pass - - -class UpdateUserAliasMinxin(object): - pass - - -class UpdateCmdAliasMinxin(object): - pass - - -class UpdateRunasAliasMinxin(object): - pass - - -class UpdateExtralineAliasMinxin(object): - pass \ No newline at end of file diff --git a/apps/ops/views.py b/apps/ops/views.py index 89344583f..6b05b0253 100644 --- a/apps/ops/views.py +++ b/apps/ops/views.py @@ -7,102 +7,29 @@ from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.views.generic.detail import DetailView, SingleObjectMixin from .hands import AdminUserRequiredMixin +from .utils import CreateSudoPrivilegesMixin, ListSudoPrivilegesMixin +from models import * -class SudoListView(AdminUserRequiredMixin, ListView): +class SudoListView(AdminUserRequiredMixin, ListSudoPrivilegesMixin, ListView): paginate_by = settings.CONFIG.DISPLAY_PER_PAGE - model = Asset - context_object_name = 'asset_list' - template_name = 'assets/asset_list.html' - - def get_queryset(self): - queryset = super(AssetListView, self).get_queryset() - queryset = sorted(queryset, key=self.sorted_by_valid_and_ip) - return queryset - - @staticmethod - def sorted_by_valid_and_ip(asset): - ip_list = int_seq(asset.ip.split('.')) - ip_list.insert(0, asset.is_valid()[0]) - return ip_list - - def get_context_data(self, **kwargs): - context = { - 'app': 'Assets', - 'action': 'asset list', - 'tag_list': [(i.id,i.name,i.asset_set.all().count())for i in Tag.objects.all().order_by('name')] - - } - kwargs.update(context) - return super(AssetListView, self).get_context_data(**kwargs) + model = Sudo + context_object_name = 'sudos' + template_name = 'sudo/list.html' -class SudoCreateView(AdminUserRequiredMixin, CreateView): - model = Asset - tag_type = 'asset' - form_class = AssetCreateForm - template_name = 'assets/asset_create.html' - success_url = reverse_lazy('assets:asset-list') - - def form_valid(self, form): - asset = form.save() - asset.created_by = self.request.user.username or 'Admin' - asset.save() - return super(AssetCreateView, self).form_valid(form) - - def form_invalid(self, form): - print(form.errors) - return super(AssetCreateView, self).form_invalid(form) - - - def get_context_data(self, **kwargs): - context = { - 'app': 'Assets', - 'action': 'Create asset', - } - kwargs.update(context) - - return super(AssetCreateView, self).get_context_data(**kwargs) +class SudoCreateView(AdminUserRequiredMixin, CreateSudoPrivilegesMixin, CreateView): + model = Sudo + template_name = 'sudo/create.html' class SudoUpdateView(AdminUserRequiredMixin, UpdateView): - model = Asset - form_class = AssetCreateForm - template_name = 'assets/asset_update.html' - success_url = reverse_lazy('assets:asset-list') - - def get_context_data(self, **kwargs): - context = { - 'app': 'Assets', - 'action': 'Update asset', - } - kwargs.update(context) - return super(AssetUpdateView, self).get_context_data(**kwargs) - - def form_invalid(self, form): - print(form.errors) - return super(AssetUpdateView, self).form_invalid(form) - - -class SudoDeleteView(DeleteView): - model = Asset - template_name = 'assets/delete_confirm.html' - success_url = reverse_lazy('assets:asset-list') + model = Sudo + template_name = 'sudo/update.html' class SudoDetailView(DetailView): - model = Asset - context_object_name = 'asset' - template_name = 'assets/asset_detail.html' + model = Sudo + context_object_name = 'sudo' + template_name = 'sudo/detail.html' - def get_context_data(self, **kwargs): - asset_groups = self.object.groups.all() - context = { - 'app': 'Assets', - 'action': 'Asset detail', - 'asset_groups_remain': [asset_group for asset_group in AssetGroup.objects.all() - if asset_group not in asset_groups], - 'asset_groups': asset_groups, - } - kwargs.update(context) - return super(AssetDetailView, self).get_context_data(**kwargs) From 76f72dfb581ccff4a3c5fe80ea37dc7a3d811992 Mon Sep 17 00:00:00 2001 From: Administrator Date: Sun, 20 Nov 2016 16:22:41 +0800 Subject: [PATCH 18/42] =?UTF-8?q?[future]=20=E6=B7=BB=E5=8A=A0=E8=B7=AF?= =?UTF-8?q?=E7=94=B1,=20=E5=A2=9E=E5=8A=A0api=E8=AE=A4=E8=AF=81,=20?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E6=89=80=E6=9C=89=E6=B7=BB=E5=8A=A0=E7=9A=84?= =?UTF-8?q?api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/urls.py | 1 + apps/ops/api/serializers.py | 2 +- apps/ops/api/views.py | 62 +++++++++++++++++++------------------ apps/ops/urls.py | 15 ++++----- 4 files changed, 42 insertions(+), 38 deletions(-) diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index 7a1c3ae95..54596074a 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -25,6 +25,7 @@ urlpatterns = [ url(r'^(api/)?users/', include('users.urls')), url(r'^assets/', include('assets.urls')), url(r'^perms/', include('perms.urls')), + url(r'^(api/)?ops/', include('ops.urls')), ] diff --git a/apps/ops/api/serializers.py b/apps/ops/api/serializers.py index 4111a64ff..7e6ede6cb 100644 --- a/apps/ops/api/serializers.py +++ b/apps/ops/api/serializers.py @@ -1,6 +1,6 @@ # ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals -from __future__ import unicode_literals, print_function from ..models import HostAlia, UserAlia, CmdAlia, RunasAlia, Extra_conf, Privilege, Sudo from rest_framework import serializers diff --git a/apps/ops/api/views.py b/apps/ops/api/views.py index b0eec81e0..60072250b 100644 --- a/apps/ops/api/views.py +++ b/apps/ops/api/views.py @@ -1,24 +1,26 @@ # ~*~ coding: utf-8 ~*~ - from __future__ import unicode_literals from rest_framework import viewsets from rest_framework import status from rest_framework.response import Response from serializers import * +from permissions import * + +import exc class HostAliaViewSet(viewsets.GenericViewSet): queryset = HostAlia.objects.all() serializer_class = HostAliaSerializer - permission_classes = None + permission_classes = (AdminUserRequired,) - def list(self): + def list(self, *args, **kwargs): h_alias = self.get_queryset() h_serializer = self.get_serializer(h_alias, many=True) return Response(h_serializer.data) - def create(self): + def create(self, *args, **kwargs): serializer = self.get_serializer(data=self.request.data) serializer.is_valid(raise_exception=True) self.perform_create() @@ -48,14 +50,14 @@ class HostAliaViewSet(viewsets.GenericViewSet): class CmdAliaViewSet(viewsets.GenericViewSet): queryset = CmdAlia.objects.all() serializer_class = CmdAliaSerializer - permission_classes = None + permission_classes = (AdminUserRequired,) - def list(self): + def list(self, *args, **kwargs): c_alias = self.get_queryset() c_serializer = self.get_serializer(c_alias, many=True) return Response(c_serializer.data) - def create(self): + def create(self, *args, **kwargs): serializer = self.get_serializer(data=self.request.data) serializer.is_valid(raise_exception=True) self.perform_create() @@ -85,14 +87,14 @@ class CmdAliaViewSet(viewsets.GenericViewSet): class UserAliaViewSet(viewsets.GenericViewSet): queryset = UserAlia.objects.all() serializer_class = UserAliaSerializer - permission_classes = None + permission_classes = (AdminUserRequired,) - def list(self): + def list(self, *args, **kwargs): u_alias = self.get_queryset() u_serializer = self.get_serializer(u_alias, many=True) return Response(u_serializer.data) - def create(self): + def create(self, *args, **kwargs): serializer = self.get_serializer(data=self.request.data) serializer.is_valid(raise_exception=True) self.perform_create() @@ -122,14 +124,14 @@ class UserAliaViewSet(viewsets.GenericViewSet): class RunasAliaViewSet(viewsets.GenericViewSet): queryset = RunasAlia.objects.all() serializer_class = RunasAliaSerializer - permission_classes = None + permission_classes = (AdminUserRequired,) - def list(self): + def list(self, *args, **kwargs): r_alias = self.get_queryset() r_serializer = self.get_serializer(r_alias, many=True) return Response(r_serializer.data) - def create(self): + def create(self, *args, **kwargs): serializer = self.get_serializer(data=self.request.data) serializer.is_valid(raise_exception=True) self.perform_create() @@ -159,14 +161,14 @@ class RunasAliaViewSet(viewsets.GenericViewSet): class ExtraconfViewSet(viewsets.GenericViewSet): queryset = Extra_conf.objects.all() serializer_class = ExtraconfSerializer - permission_classes = None + permission_classes = (AdminUserRequired,) - def list(self): + def list(self, *args, **kwargs): e_alias = self.get_queryset() e_serializer = self.get_serializer(e_alias, many=True) return Response(e_serializer.data) - def create(self): + def create(self, *args, **kwargs): serializer = self.get_serializer(data=self.request.data) serializer.is_valid(raise_exception=True) self.perform_create() @@ -196,19 +198,19 @@ class ExtraconfViewSet(viewsets.GenericViewSet): class PrivilegeViewSet(viewsets.GenericViewSet): queryset = Privilege.objects.all() serializer_class = PrivilegeSerializer - permission_classes = None + permission_classes = (AdminUserRequired,) - def list(self): - pass + def list(self, *args, **kwargs): + raise exc.ServiceNotImplemented - def create(self): - pass + def create(self, *args, **kwargs): + raise exc.ServiceNotImplemented def retrieve(self, *args, **kwargs): - pass + raise exc.ServiceNotImplemented def update(self, *args, **kwargs): - pass + raise exc.ServiceNotImplemented def destroy(self, *args, **kwargs): privilege = self.get_object() @@ -222,19 +224,19 @@ class PrivilegeViewSet(viewsets.GenericViewSet): class SudoViewSet(viewsets.GenericViewSet): queryset = Sudo.objects.all() serializer_class = SudoSerializer - permission_classes = None + permission_classes = (AdminUserRequired,) - def list(self): - pass + def list(self, *args, **kwargs): + raise exc.ServiceNotImplemented - def create(self): - pass + def create(self, *args, **kwargs): + raise exc.ServiceNotImplemented def retrieve(self, *args, **kwargs): - pass + raise exc.ServiceNotImplemented def update(self, *args, **kwargs): - pass + raise exc.ServiceNotImplemented def destroy(self, *args, **kwargs): sudo = self.get_object() diff --git a/apps/ops/urls.py b/apps/ops/urls.py index ea4b669b7..c4a42c9b5 100644 --- a/apps/ops/urls.py +++ b/apps/ops/urls.py @@ -12,11 +12,13 @@ import views as mvc_view app_name = 'ops' router = DefaultRouter() -router.register(r'HostAlias', api_view.HostAliaViewSet) -router.register(r'UserAlias', api_view.UserAliaViewSet) -router.register(r'CmdAlias', api_view.CmdAliaViewSet) -router.register(r'RunasAlias', api_view.RunasAliaViewSet) -router.register(r'Extraconf', api_view.ExtraconfViewSet) +router.register(r'host_alia', api_view.HostAliaViewSet) +router.register(r'user_alia', api_view.UserAliaViewSet) +router.register(r'cmd_alia', api_view.CmdAliaViewSet) +router.register(r'runas_alia', api_view.RunasAliaViewSet) +router.register(r'extra_conf', api_view.ExtraconfViewSet) +router.register(r'privilege', api_view.PrivilegeViewSet) +router.register(r'sudo', api_view.SudoViewSet) urlpatterns = [ # Resource Sudo url @@ -24,11 +26,10 @@ urlpatterns = [ url(r'^sudo/create$', mvc_view.SudoCreateView.as_view(), name='sudo-create'), url(r'^sudo/detail$', mvc_view.SudoDetailView.as_view(), name='sudo-detail'), url(r'^sudo/update$', mvc_view.SudoUpdateView.as_view(), name='sudo-update'), - url(r'^sudo/delete$', mvc_view.SudoDeleteView.as_view(), name='sudo-delete'), ] urlpatterns += [ - url(r'^api/ops/sudo', include(router.urls)), + url(r'^v1/sudo', include(router.urls)), ] From 1e835d2fa99b73b88cce34eb7ef4d76b32a70fcf Mon Sep 17 00:00:00 2001 From: Administrator Date: Sun, 20 Nov 2016 16:23:45 +0800 Subject: [PATCH 19/42] =?UTF-8?q?[future]=20=E5=A2=9E=E5=8A=A0503=E5=92=8C?= =?UTF-8?q?501=E7=9A=84=E8=87=AA=E5=AE=9A=E4=B9=89=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/api/exc.py | 16 ++++++++++++++++ apps/ops/api/permissions.py | 19 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 apps/ops/api/exc.py create mode 100644 apps/ops/api/permissions.py diff --git a/apps/ops/api/exc.py b/apps/ops/api/exc.py new file mode 100644 index 000000000..81deb805c --- /dev/null +++ b/apps/ops/api/exc.py @@ -0,0 +1,16 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals, print_function + +from rest_framework.exceptions import APIException +from django.utils.translation import ugettext as _ + + +class ServiceUnavailable(APIException): + status_code = default_code = 503 + default_detail = _('Service temporarily unavailable, try again later.') + + +class ServiceNotImplemented(APIException): + status_code = default_code = 501 + default_detail = _('This service maybe implemented in the future, but now not implemented!') + diff --git a/apps/ops/api/permissions.py b/apps/ops/api/permissions.py new file mode 100644 index 000000000..0fc0d0861 --- /dev/null +++ b/apps/ops/api/permissions.py @@ -0,0 +1,19 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals + +from rest_framework import permissions + + +class AdminUserRequired(permissions.BasePermission): + """ + Custom permission to only allow admin user to access the resource. + """ + + def has_object_permission(self, request, view, obj): + # Read permissions are allowed to any request, + # so we'll always allow GET, HEAD or OPTIONS requests. + if request.method in permissions.SAFE_METHODS: + return True + + # Write permissions are only allowed to the admin role. + return request.user.is_staff From 79971d677db4ee7b0480168d6652edb04b8a4933 Mon Sep 17 00:00:00 2001 From: Administrator Date: Sun, 20 Nov 2016 18:12:18 +0800 Subject: [PATCH 20/42] =?UTF-8?q?[future]=20=E4=BD=BF=E7=94=A8mixin?= =?UTF-8?q?=E5=8E=BB=E6=8E=89=E9=87=8D=E5=A4=8D=E5=A4=9A=E4=BD=99=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/api/views.py | 249 ++++++++---------------------------------- 1 file changed, 43 insertions(+), 206 deletions(-) diff --git a/apps/ops/api/views.py b/apps/ops/api/views.py index 60072250b..b8a0436b0 100644 --- a/apps/ops/api/views.py +++ b/apps/ops/api/views.py @@ -1,249 +1,86 @@ # ~*~ coding: utf-8 ~*~ from __future__ import unicode_literals -from rest_framework import viewsets -from rest_framework import status -from rest_framework.response import Response +from rest_framework import viewsets, mixins from serializers import * from permissions import * -import exc - -class HostAliaViewSet(viewsets.GenericViewSet): +class HostAliaViewSet(mixins.CreateModelMixin, + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + mixins.UpdateModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet): queryset = HostAlia.objects.all() serializer_class = HostAliaSerializer permission_classes = (AdminUserRequired,) - def list(self, *args, **kwargs): - h_alias = self.get_queryset() - h_serializer = self.get_serializer(h_alias, many=True) - return Response(h_serializer.data) - def create(self, *args, **kwargs): - serializer = self.get_serializer(data=self.request.data) - serializer.is_valid(raise_exception=True) - self.perform_create() - return Response(serializer.data, status=status.HTTP_201_CREATED) - - def retrieve(self, *args, **kwargs): - h_alias = self.get_object() - serializer = self.get_serializer(h_alias) - return Response(serializer.data) - - def update(self, *args, **kwargs): - h_alias = self.get_object() - serializer = self.get_serializer(h_alias, data=self.request.data) - serializer.is_valid(raise_exception=False) - self.perform_create() - return Response(serializer.data) - - def destroy(self, *args, **kwargs): - h_alias = self.get_object() - h_alias.delete() - return Response(status=status.HTTP_204_NO_CONTENT) - - def perform_create(self): - pass - - -class CmdAliaViewSet(viewsets.GenericViewSet): +class CmdAliaViewSet(mixins.CreateModelMixin, + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + mixins.UpdateModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet): queryset = CmdAlia.objects.all() serializer_class = CmdAliaSerializer permission_classes = (AdminUserRequired,) - def list(self, *args, **kwargs): - c_alias = self.get_queryset() - c_serializer = self.get_serializer(c_alias, many=True) - return Response(c_serializer.data) - def create(self, *args, **kwargs): - serializer = self.get_serializer(data=self.request.data) - serializer.is_valid(raise_exception=True) - self.perform_create() - return Response(serializer.data, status=status.HTTP_201_CREATED) - - def retrieve(self, *args, **kwargs): - c_alias = self.get_object() - serializer = self.get_serializer(c_alias) - return Response(serializer.data) - - def update(self, *args, **kwargs): - c_alias = self.get_object() - serializer = self.get_serializer(c_alias, data=self.request.data) - serializer.is_valid(raise_exception=False) - self.perform_create() - return Response(serializer.data) - - def destroy(self, *args, **kwargs): - c_alias = self.get_object() - c_alias.delete() - return Response(status=status.HTTP_204_NO_CONTENT) - - def perform_create(self): - pass - - -class UserAliaViewSet(viewsets.GenericViewSet): +class UserAliaViewSet(mixins.CreateModelMixin, + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + mixins.UpdateModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet): queryset = UserAlia.objects.all() serializer_class = UserAliaSerializer permission_classes = (AdminUserRequired,) - def list(self, *args, **kwargs): - u_alias = self.get_queryset() - u_serializer = self.get_serializer(u_alias, many=True) - return Response(u_serializer.data) - def create(self, *args, **kwargs): - serializer = self.get_serializer(data=self.request.data) - serializer.is_valid(raise_exception=True) - self.perform_create() - return Response(serializer.data, status=status.HTTP_201_CREATED) - - def retrieve(self, *args, **kwargs): - u_alias = self.get_object() - serializer = self.get_serializer(u_alias) - return Response(serializer.data) - - def update(self, *args, **kwargs): - u_alias = self.get_object() - serializer = self.get_serializer(u_alias, data=self.request.data) - serializer.is_valid(raise_exception=False) - self.perform_create() - return Response(serializer.data) - - def destroy(self, *args, **kwargs): - u_alias = self.get_object() - u_alias.delete() - return Response(status=status.HTTP_204_NO_CONTENT) - - def perform_create(self): - pass - - -class RunasAliaViewSet(viewsets.GenericViewSet): +class RunasAliaViewSet(mixins.CreateModelMixin, + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + mixins.UpdateModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet): queryset = RunasAlia.objects.all() serializer_class = RunasAliaSerializer permission_classes = (AdminUserRequired,) - def list(self, *args, **kwargs): - r_alias = self.get_queryset() - r_serializer = self.get_serializer(r_alias, many=True) - return Response(r_serializer.data) - def create(self, *args, **kwargs): - serializer = self.get_serializer(data=self.request.data) - serializer.is_valid(raise_exception=True) - self.perform_create() - return Response(serializer.data, status=status.HTTP_201_CREATED) - - def retrieve(self, *args, **kwargs): - r_alias = self.get_object() - serializer = self.get_serializer(r_alias) - return Response(serializer.data) - - def update(self, *args, **kwargs): - r_alias = self.get_object() - serializer = self.get_serializer(r_alias, data=self.request.data) - serializer.is_valid(raise_exception=False) - self.perform_create() - return Response(serializer.data) - - def destroy(self, *args, **kwargs): - r_alias = self.get_object() - r_alias.delete() - return Response(status=status.HTTP_204_NO_CONTENT) - - def perform_create(self): - pass - - -class ExtraconfViewSet(viewsets.GenericViewSet): +class ExtraconfViewSet(mixins.CreateModelMixin, + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + mixins.UpdateModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet): queryset = Extra_conf.objects.all() serializer_class = ExtraconfSerializer permission_classes = (AdminUserRequired,) - def list(self, *args, **kwargs): - e_alias = self.get_queryset() - e_serializer = self.get_serializer(e_alias, many=True) - return Response(e_serializer.data) - def create(self, *args, **kwargs): - serializer = self.get_serializer(data=self.request.data) - serializer.is_valid(raise_exception=True) - self.perform_create() - return Response(serializer.data, status=status.HTTP_201_CREATED) - - def retrieve(self, *args, **kwargs): - e_alias = self.get_object() - serializer = self.get_serializer(e_alias) - return Response(serializer.data) - - def update(self, *args, **kwargs): - e_alias = self.get_object() - serializer = self.get_serializer(e_alias, data=self.request.data) - serializer.is_valid(raise_exception=False) - self.perform_create() - return Response(serializer.data) - - def destroy(self, *args, **kwargs): - e_alias = self.get_object() - e_alias.delete() - return Response(status=status.HTTP_204_NO_CONTENT) - - def perform_create(self): - pass - - -class PrivilegeViewSet(viewsets.GenericViewSet): +class PrivilegeViewSet(mixins.CreateModelMixin, + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + mixins.UpdateModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet): queryset = Privilege.objects.all() serializer_class = PrivilegeSerializer permission_classes = (AdminUserRequired,) - def list(self, *args, **kwargs): - raise exc.ServiceNotImplemented - def create(self, *args, **kwargs): - raise exc.ServiceNotImplemented - - def retrieve(self, *args, **kwargs): - raise exc.ServiceNotImplemented - - def update(self, *args, **kwargs): - raise exc.ServiceNotImplemented - - def destroy(self, *args, **kwargs): - privilege = self.get_object() - privilege.delete() - return Response(status=status.HTTP_204_NO_CONTENT) - - def perform_create(self): - pass - - -class SudoViewSet(viewsets.GenericViewSet): +class SudoViewSet(mixins.CreateModelMixin, + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + mixins.UpdateModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet): queryset = Sudo.objects.all() serializer_class = SudoSerializer permission_classes = (AdminUserRequired,) - def list(self, *args, **kwargs): - raise exc.ServiceNotImplemented - - def create(self, *args, **kwargs): - raise exc.ServiceNotImplemented - - def retrieve(self, *args, **kwargs): - raise exc.ServiceNotImplemented - - def update(self, *args, **kwargs): - raise exc.ServiceNotImplemented - - def destroy(self, *args, **kwargs): - sudo = self.get_object() - sudo.delete() - return Response(status=status.HTTP_204_NO_CONTENT) - - def perform_create(self): - pass From 954814da6518c9a8cb364a212b88d7da3c8af92e Mon Sep 17 00:00:00 2001 From: Administrator Date: Tue, 22 Nov 2016 10:41:18 +0800 Subject: [PATCH 21/42] =?UTF-8?q?[future]=20=E6=B7=BB=E5=8A=A0Cron?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E7=9A=84=E5=9F=BA=E7=A1=80API=E6=A1=86?= =?UTF-8?q?=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/api/serializers.py | 6 +++++- apps/ops/api/views.py | 11 +++++++++++ apps/ops/models.py | 25 +++++++++++++++++++++++++ apps/ops/urls.py | 1 + 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/apps/ops/api/serializers.py b/apps/ops/api/serializers.py index 7e6ede6cb..1cafdf574 100644 --- a/apps/ops/api/serializers.py +++ b/apps/ops/api/serializers.py @@ -1,7 +1,7 @@ # ~*~ coding: utf-8 ~*~ from __future__ import unicode_literals -from ..models import HostAlia, UserAlia, CmdAlia, RunasAlia, Extra_conf, Privilege, Sudo +from ..models import HostAlia, UserAlia, CmdAlia, RunasAlia, Extra_conf, Privilege, Sudo, CronTable from rest_framework import serializers @@ -47,3 +47,7 @@ class SudoSerializer(serializers.ModelSerializer): model = Sudo +class CronTableSerializer(serializers.ModelSerializer): + + class Meta: + model = CronTable diff --git a/apps/ops/api/views.py b/apps/ops/api/views.py index b8a0436b0..1b0de74ee 100644 --- a/apps/ops/api/views.py +++ b/apps/ops/api/views.py @@ -83,4 +83,15 @@ class SudoViewSet(mixins.CreateModelMixin, permission_classes = (AdminUserRequired,) +class CronTableViewSet(mixins.CreateModelMixin, + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + mixins.UpdateModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet): + queryset = CronTable.objects.all() + serializer_class = CronTableSerializer + permission_classes = (AdminUserRequired,) + + diff --git a/apps/ops/models.py b/apps/ops/models.py index ba5c4cc1e..23541a38f 100644 --- a/apps/ops/models.py +++ b/apps/ops/models.py @@ -393,6 +393,31 @@ root ALL=(ALL:ALL) ALL """ +class CronTable(models.Model): + name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Runas_Alias'), + help_text=_("Description of a crontab entry")) + month = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Month'), + help_text=_("Month of the year the job should run ( 1-12, *, */2, etc )")) + weekday = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('WeekDay'), + help_text=_("Day of the week that the job should run" + " ( 0-6 for Sunday-Saturday, *, etc )")) + day = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Day'), + help_text=_("Day of the month the job should run ( 1-31, *, */2, etc )")) + hour = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hour'), + help_text=_("Hour when the job should run ( 0-23, *, */2, etc )")) + minute = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Minute'), + help_text=_("Minute when the job should run ( 0-59, *, */2, etc )")) + job = models.CharField(max_length=4096, blank=True, null=True, verbose_name=_('Job'), + help_text=_("The command to execute or, if env is set, the value of " + "environment variable. Required if state=present.")) + user = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('User'), + help_text=_("The specific user whose crontab should be modified.")) + asset = models.ForeignKey(Asset, null=True, blank=True, related_name='crontables') + + @property + def describe(self): + return "http://docs.ansible.com/ansible/cron_module.html" + diff --git a/apps/ops/urls.py b/apps/ops/urls.py index c4a42c9b5..205abf6d0 100644 --- a/apps/ops/urls.py +++ b/apps/ops/urls.py @@ -19,6 +19,7 @@ router.register(r'runas_alia', api_view.RunasAliaViewSet) router.register(r'extra_conf', api_view.ExtraconfViewSet) router.register(r'privilege', api_view.PrivilegeViewSet) router.register(r'sudo', api_view.SudoViewSet) +router.register(r'cron', api_view.CronTableViewSet) urlpatterns = [ # Resource Sudo url From 18e0fee1a7619b373fdb7e07fd844bc3f2770be8 Mon Sep 17 00:00:00 2001 From: Administrator Date: Tue, 22 Nov 2016 21:08:45 +0800 Subject: [PATCH 22/42] =?UTF-8?q?[future]=20=E6=B7=BB=E5=8A=A0=E4=BD=9C?= =?UTF-8?q?=E4=B8=9A=E4=B8=AD=E5=BF=83=E5=9F=BA=E7=A1=80=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.po | 12 ++ apps/ops/models.py | 2 +- apps/ops/templates/cron/create.html | 10 ++ apps/ops/templates/cron/detail.html | 10 ++ apps/ops/templates/cron/list.html | 242 ++++++++++++++++++++++++++ apps/ops/templates/cron/update.html | 10 ++ apps/ops/templates/sudo/list.html | 250 ++++++++++++++++++++++++++- apps/ops/urls.py | 16 +- apps/ops/views.py | 23 +++ apps/templates/_nav.html | 11 ++ 10 files changed, 571 insertions(+), 15 deletions(-) create mode 100644 apps/ops/templates/cron/create.html create mode 100644 apps/ops/templates/cron/detail.html create mode 100644 apps/ops/templates/cron/list.html create mode 100644 apps/ops/templates/cron/update.html diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index b68a30613..a54cc74e6 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -1713,6 +1713,18 @@ msgstr "Token错误或失效" msgid "Password not same" msgstr "密码不一致" +#: templates/_nav.html:43 +msgid "Job Center" +msgstr "作业中心" + +#: templates/_nav.html:46 +msgid "Sudo" +msgstr "Sudo管理" + +#: templates/_nav.html:47 +msgid "Cron" +msgstr "Cron管理" + #~ msgid "Admin password" #~ msgstr "管理员密码" diff --git a/apps/ops/models.py b/apps/ops/models.py index 23541a38f..4d2346f37 100644 --- a/apps/ops/models.py +++ b/apps/ops/models.py @@ -394,7 +394,7 @@ root ALL=(ALL:ALL) ALL class CronTable(models.Model): - name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Runas_Alias'), + name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Name'), help_text=_("Description of a crontab entry")) month = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Month'), help_text=_("Month of the year the job should run ( 1-12, *, */2, etc )")) diff --git a/apps/ops/templates/cron/create.html b/apps/ops/templates/cron/create.html new file mode 100644 index 000000000..566549bdf --- /dev/null +++ b/apps/ops/templates/cron/create.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/apps/ops/templates/cron/detail.html b/apps/ops/templates/cron/detail.html new file mode 100644 index 000000000..566549bdf --- /dev/null +++ b/apps/ops/templates/cron/detail.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/apps/ops/templates/cron/list.html b/apps/ops/templates/cron/list.html new file mode 100644 index 000000000..b08a44b53 --- /dev/null +++ b/apps/ops/templates/cron/list.html @@ -0,0 +1,242 @@ +{% extends '_base_list.html' %} +{% load i18n static %} +{% block custom_head_css_js %} +{{ block.super }} + +{% endblock %} +{% block table_search %}{% endblock %} +{% block table_container %} + + + + + + + + + + + + + + + + + +
+
+
{% trans 'Name' %}{% trans 'Username' %}{% trans 'Role' %}{% trans 'User group' %}{% trans 'Asset num' %}{% trans 'Active' %}{% trans 'Action' %}
+
+
+ +
+ +
+
+
+{% include "users/_user_bulk_update_modal.html" %} +{% include "users/_user_import_modal.html" %} +{% endblock %} +{% block content_bottom_left %}{% endblock %} +{% block custom_foot_js %} + + +{% endblock %} + diff --git a/apps/ops/templates/cron/update.html b/apps/ops/templates/cron/update.html new file mode 100644 index 000000000..566549bdf --- /dev/null +++ b/apps/ops/templates/cron/update.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/apps/ops/templates/sudo/list.html b/apps/ops/templates/sudo/list.html index 566549bdf..b08a44b53 100644 --- a/apps/ops/templates/sudo/list.html +++ b/apps/ops/templates/sudo/list.html @@ -1,10 +1,242 @@ - - - - - Title - - +{% extends '_base_list.html' %} +{% load i18n static %} +{% block custom_head_css_js %} +{{ block.super }} + +{% endblock %} +{% block table_search %}{% endblock %} +{% block table_container %} + + + + + + + + + + + + + + + + + +
+
+
{% trans 'Name' %}{% trans 'Username' %}{% trans 'Role' %}{% trans 'User group' %}{% trans 'Asset num' %}{% trans 'Active' %}{% trans 'Action' %}
+
+
+ +
+ +
+
+
+{% include "users/_user_bulk_update_modal.html" %} +{% include "users/_user_import_modal.html" %} +{% endblock %} +{% block content_bottom_left %}{% endblock %} +{% block custom_foot_js %} + + +{% endblock %} - - \ No newline at end of file diff --git a/apps/ops/urls.py b/apps/ops/urls.py index 205abf6d0..2735b1202 100644 --- a/apps/ops/urls.py +++ b/apps/ops/urls.py @@ -6,7 +6,7 @@ from django.conf.urls import url, include from api import views as api_view -import views as mvc_view +import views as page_view app_name = 'ops' @@ -23,10 +23,16 @@ router.register(r'cron', api_view.CronTableViewSet) urlpatterns = [ # Resource Sudo url - url(r'^sudo/list$', mvc_view.SudoListView.as_view(), name='sudo-list'), - url(r'^sudo/create$', mvc_view.SudoCreateView.as_view(), name='sudo-create'), - url(r'^sudo/detail$', mvc_view.SudoDetailView.as_view(), name='sudo-detail'), - url(r'^sudo/update$', mvc_view.SudoUpdateView.as_view(), name='sudo-update'), + url(r'^sudo/list$', page_view.SudoListView.as_view(), name='page-sudo-list'), + url(r'^sudo/create$', page_view.SudoCreateView.as_view(), name='page-sudo-create'), + url(r'^sudo/detail$', page_view.SudoDetailView.as_view(), name='page-sudo-detail'), + url(r'^sudo/update$', page_view.SudoUpdateView.as_view(), name='page-sudo-update'), + + # Resource Cron url + url(r'^cron/list$', page_view.CronListView.as_view(), name='page-cron-list'), + url(r'^cron/create$', page_view.CronCreateView.as_view(), name='page-cron-create'), + url(r'^cron/detail$', page_view.CronDetailView.as_view(), name='page-cron-detail'), + url(r'^cron/update$', page_view.CronUpdateView.as_view(), name='page-cron-update'), ] urlpatterns += [ diff --git a/apps/ops/views.py b/apps/ops/views.py index 6b05b0253..4b312dd2c 100644 --- a/apps/ops/views.py +++ b/apps/ops/views.py @@ -33,3 +33,26 @@ class SudoDetailView(DetailView): context_object_name = 'sudo' template_name = 'sudo/detail.html' + +class CronListView(AdminUserRequiredMixin, ListSudoPrivilegesMixin, ListView): + paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + model = CronTable + context_object_name = 'crons' + template_name = 'sudo/list.html' + + +class CronCreateView(AdminUserRequiredMixin, CreateSudoPrivilegesMixin, CreateView): + model = CronTable + template_name = 'cron/create.html' + + +class CronUpdateView(AdminUserRequiredMixin, UpdateView): + model = CronTable + template_name = 'cron/update.html' + + +class CronDetailView(DetailView): + model = CronTable + context_object_name = 'sudo' + template_name = 'cron/detail.html' + diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index a0b9e82f8..fb3a47bf7 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -37,6 +37,17 @@ {# #} + +
  • + + {% trans 'Job Center' %} + + +
  • +
  • {% trans 'Audits' %} From 3abe2196dddad3995e7b9b17e1526ecca559d621 Mon Sep 17 00:00:00 2001 From: Administrator Date: Tue, 22 Nov 2016 21:38:38 +0800 Subject: [PATCH 23/42] =?UTF-8?q?[future]=20=E6=B7=BB=E5=8A=A0=E4=BD=9C?= =?UTF-8?q?=E4=B8=9A=E4=B8=AD=E5=BF=83=20i18n=E7=9B=B8=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.po | 1273 +++++++++++++++++++------- 1 file changed, 938 insertions(+), 335 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index a54cc74e6..cda86f2c0 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,220 +8,258 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-09-18 22:57+0800\n" +"POT-Creation-Date: 2016-11-22 21:30+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" +"Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: zh_CN\n" -#: assets/forms.py:26 -msgid "Tags" -msgstr "" - -#: assets/forms.py:57 assets/forms.py:201 +#: assets/forms.py:53 assets/forms.py:208 #: assets/templates/assets/admin_user_detail.html:191 perms/forms.py:27 -#: perms/templates/perms/asset_permission_asset.html:139 users/forms.py:115 +#: perms/templates/perms/asset_permission_asset.html:139 users/forms.py:124 msgid "Select asset groups" msgstr "添加到资产组" -#: assets/forms.py:59 +#: assets/forms.py:56 +#, fuzzy +#| msgid "Select assets" +msgid "Select asset tags" +msgstr "选择资产" + +#: assets/forms.py:58 #, fuzzy #| msgid "System user" msgid "Select asset system users" msgstr "系统" -#: assets/forms.py:60 +#: assets/forms.py:59 #, fuzzy #| msgid "Select assets" msgid "Select asset admin user" msgstr "选择资产" -#: assets/forms.py:71 assets/forms.py:102 assets/forms.py:133 -#: assets/forms.py:191 assets/models.py:347 +#: assets/forms.py:71 assets/forms.py:107 assets/forms.py:140 +#: assets/forms.py:198 assets/forms.py:270 #: perms/templates/perms/asset_permission_create_update.html:40 #: templates/_nav.html:21 msgid "Asset" msgstr "资产" -#: assets/forms.py:74 assets/forms.py:105 assets/forms.py:136 -#: assets/forms.py:194 perms/forms.py:25 users/forms.py:113 +#: assets/forms.py:74 assets/forms.py:110 assets/forms.py:143 +#: assets/forms.py:201 assets/forms.py:273 perms/forms.py:25 users/forms.py:122 msgid "Select assets" msgstr "选择资产" -#: assets/forms.py:124 assets/forms.py:179 assets/forms.py:251 -#: assets/models.py:14 assets/models.py:89 assets/models.py:154 -#: assets/models.py:238 assets/templates/assets/admin_user_detail.html:46 +#: assets/forms.py:96 +#, fuzzy +#| msgid "System user" +msgid "Select asset system user" +msgstr "系统" + +#: assets/forms.py:129 assets/forms.py:186 assets/forms.py:258 +#: assets/models.py:15 assets/models.py:94 assets/models.py:159 +#: assets/models.py:243 assets/templates/assets/admin_user_detail.html:46 #: assets/templates/assets/admin_user_list.html:10 #: assets/templates/assets/asset_group_detail.html:46 #: assets/templates/assets/asset_group_list.html:12 -#: assets/templates/assets/idc_list.html:10 +#: assets/templates/assets/idc_list.html:12 #: assets/templates/assets/system_user_asset_group.html:53 #: assets/templates/assets/system_user_detail.html:51 -#: assets/templates/assets/system_user_list.html:10 perms/models.py:19 +#: assets/templates/assets/system_user_list.html:10 ops/models.py:18 +#: ops/models.py:36 ops/models.py:48 ops/models.py:65 ops/models.py:397 +#: ops/templates/cron/list.html:26 ops/templates/sudo/list.html:26 +#: perms/models.py:19 #: perms/templates/perms/asset_permission_create_update.html:33 #: perms/templates/perms/asset_permission_detail.html:56 #: perms/templates/perms/asset_permission_list.html:12 -#: perms/templates/perms/asset_permission_user.html:66 users/models.py:20 -#: users/models.py:67 users/templates/users/user_asset_permission.html:66 +#: perms/templates/perms/asset_permission_user.html:66 users/models.py:23 +#: users/models.py:75 users/templates/users/_select_user_modal.html:13 +#: users/templates/users/user_asset_permission.html:66 #: users/templates/users/user_detail.html:58 #: users/templates/users/user_granted_asset.html:129 -#: users/templates/users/user_list.html:12 +#: users/templates/users/user_group_detail.html:90 +#: users/templates/users/user_group_list.html:25 +#: users/templates/users/user_list.html:26 msgid "Name" msgstr "名称" -#: assets/forms.py:141 assets/forms.py:206 +#: assets/forms.py:148 assets/forms.py:213 msgid "If also set private key, use that first" msgstr "如果设置私钥,则优先使用私钥" -#: assets/forms.py:180 assets/forms.py:252 assets/models.py:90 -#: assets/models.py:155 assets/templates/assets/admin_user_detail.html:50 +#: assets/forms.py:187 assets/forms.py:259 assets/models.py:95 +#: assets/models.py:160 assets/templates/assets/admin_user_detail.html:50 #: assets/templates/assets/admin_user_list.html:11 #: assets/templates/assets/system_user_detail.html:55 #: assets/templates/assets/system_user_list.html:11 +#: ops/templates/cron/list.html:27 ops/templates/sudo/list.html:27 #: perms/templates/perms/asset_permission_user.html:67 users/forms.py:13 -#: users/models.py:66 users/templates/users/login.html:53 +#: users/models.py:74 users/templates/users/_select_user_modal.html:14 +#: users/templates/users/login.html:53 #: users/templates/users/user_detail.html:62 -#: users/templates/users/user_list.html:13 +#: users/templates/users/user_list.html:27 #: users/templates/users/user_update.html:6 msgid "Username" msgstr "用户名" -#: assets/forms.py:197 assets/templates/assets/asset_detail.html:203 -#: templates/_nav.html:22 +#: assets/forms.py:204 templates/_nav.html:22 msgid "Asset group" msgstr "资产组" -#: assets/models.py:15 assets/templates/assets/idc_list.html:12 +#: assets/models.py:16 msgid "Bandwidth" msgstr "带宽" -#: assets/models.py:16 assets/templates/assets/idc_list.html:13 +#: assets/models.py:17 assets/templates/assets/idc_list.html:14 msgid "Contact" msgstr "联系人" -#: assets/models.py:17 assets/templates/assets/idc_list.html:14 -#: users/models.py:73 users/templates/users/user_detail.html:71 +#: assets/models.py:18 assets/templates/assets/idc_list.html:15 +#: users/models.py:81 users/templates/users/user_detail.html:71 msgid "Phone" msgstr "手机" -#: assets/models.py:18 assets/templates/assets/idc_list.html:15 +#: assets/models.py:19 msgid "Address" msgstr "地址" -#: assets/models.py:19 -msgid "Network" -msgstr "网络" +#: assets/models.py:20 +msgid "Intranet" +msgstr "" -#: assets/models.py:20 assets/models.py:241 assets/models.py:307 +#: assets/models.py:21 +msgid "Extranet" +msgstr "" + +#: assets/models.py:22 assets/models.py:246 assets/models.py:324 msgid "Date added" msgstr "加入日期" -#: assets/models.py:21 +#: assets/models.py:23 msgid "Operator" msgstr "运营商" -#: assets/models.py:22 assets/models.py:59 assets/models.py:97 -#: assets/models.py:168 assets/models.py:240 assets/models.py:305 -#: assets/templates/assets/admin_user_detail.html:58 -#: assets/templates/assets/asset_detail.html:127 +#: assets/models.py:24 assets/models.py:64 assets/models.py:102 +#: assets/models.py:173 assets/models.py:245 assets/models.py:322 +#: assets/models.py:370 assets/templates/assets/admin_user_detail.html:58 +#: assets/templates/assets/asset_detail.html:114 #: assets/templates/assets/asset_group_detail.html:54 +#: assets/templates/assets/asset_tag_detail.html:49 #: assets/templates/assets/system_user_detail.html:101 perms/models.py:29 -#: perms/templates/perms/asset_permission_detail.html:88 users/models.py:82 +#: perms/templates/perms/asset_permission_detail.html:88 users/models.py:90 #: users/templates/users/user_detail.html:90 msgid "Created by" msgstr "创建者" -#: assets/models.py:23 assets/models.py:61 assets/models.py:95 -#: assets/models.py:169 assets/models.py:242 assets/models.py:308 +#: assets/models.py:25 assets/models.py:66 assets/models.py:100 +#: assets/models.py:174 assets/models.py:247 assets/models.py:325 #: assets/templates/assets/admin_user_detail.html:62 #: assets/templates/assets/admin_user_list.html:14 -#: assets/templates/assets/asset_detail.html:135 +#: assets/templates/assets/asset_detail.html:122 #: assets/templates/assets/asset_group_detail.html:58 #: assets/templates/assets/asset_group_list.html:14 #: assets/templates/assets/system_user_asset_group.html:56 #: assets/templates/assets/system_user_detail.html:105 #: assets/templates/assets/system_user_list.html:15 perms/models.py:31 -#: perms/templates/perms/asset_permission_detail.html:92 users/models.py:21 -#: users/models.py:78 users/templates/users/user_detail.html:102 +#: perms/templates/perms/asset_permission_detail.html:92 users/models.py:24 +#: users/models.py:86 users/templates/users/user_detail.html:102 +#: users/templates/users/user_group_detail.html:94 +#: users/templates/users/user_group_list.html:28 msgid "Comment" msgstr "备注" -#: assets/models.py:57 +#: assets/models.py:32 assets/models.py:257 +#, fuzzy +#| msgid "As default" +msgid "Default" +msgstr "默认使用" + +#: assets/models.py:32 users/models.py:224 +msgid "System" +msgstr "系统" + +#: assets/models.py:32 +#, fuzzy +#| msgid "As default" +msgid "Default IDC" +msgstr "默认使用" + +#: assets/models.py:62 msgid "KEY" msgstr "KEY" -#: assets/models.py:58 assets/models.py:346 +#: assets/models.py:63 msgid "VALUE" msgstr "VALUE" -#: assets/models.py:69 assets/models.py:70 +#: assets/models.py:74 assets/models.py:75 msgid "status" msgstr "状态" -#: assets/models.py:69 +#: assets/models.py:74 #, fuzzy #| msgid "Admin user" msgid "In use" msgstr "管理用户" -#: assets/models.py:70 +#: assets/models.py:75 #, fuzzy #| msgid "Auto push" msgid "Out of use" msgstr "自动推送" -#: assets/models.py:71 assets/models.py:72 assets/models.py:73 -#: assets/models.py:74 assets/models.py:75 assets/models.py:76 +#: assets/models.py:76 assets/models.py:77 assets/models.py:78 +#: assets/models.py:79 assets/models.py:80 assets/models.py:81 msgid "type" msgstr "" -#: assets/models.py:71 +#: assets/models.py:76 msgid "Server" msgstr "" -#: assets/models.py:72 +#: assets/models.py:77 msgid "VM" msgstr "" -#: assets/models.py:73 +#: assets/models.py:78 msgid "Switch" msgstr "" -#: assets/models.py:74 +#: assets/models.py:79 #, fuzzy #| msgid "Role" msgid "Router" msgstr "角色" -#: assets/models.py:75 +#: assets/models.py:80 msgid "Firewall" msgstr "" -#: assets/models.py:76 +#: assets/models.py:81 msgid "Storage" msgstr "" -#: assets/models.py:77 assets/models.py:78 assets/models.py:79 +#: assets/models.py:82 assets/models.py:83 assets/models.py:84 msgid "env" msgstr "" -#: assets/models.py:77 +#: assets/models.py:82 msgid "Production" msgstr "" -#: assets/models.py:78 +#: assets/models.py:83 msgid "Development" msgstr "" -#: assets/models.py:79 +#: assets/models.py:84 #, fuzzy msgid "Testing" msgstr "设置" -#: assets/models.py:91 assets/models.py:156 users/forms.py:15 +#: assets/models.py:96 assets/models.py:161 users/forms.py:15 #: users/templates/users/login.html:56 #: users/templates/users/reset_password.html:52 #: users/templates/users/user_create.html:9 @@ -231,172 +269,169 @@ msgstr "设置" msgid "Password" msgstr "密码" -#: assets/models.py:92 assets/models.py:158 +#: assets/models.py:97 assets/models.py:163 msgid "SSH private key" msgstr "ssh密钥" -#: assets/models.py:93 assets/models.py:159 +#: assets/models.py:98 assets/models.py:164 msgid "SSH public key" msgstr "ssh公钥" -#: assets/models.py:94 assets/models.py:160 +#: assets/models.py:99 assets/models.py:165 #: assets/templates/assets/admin_user_create_update.html:43 #: assets/templates/assets/system_user_create_update.html:44 #: assets/templates/assets/system_user_detail.html:71 msgid "As default" msgstr "默认使用" -#: assets/models.py:157 assets/templates/assets/system_user_detail.html:59 +#: assets/models.py:162 assets/templates/assets/system_user_detail.html:59 msgid "Protocol" msgstr "协议" -#: assets/models.py:161 +#: assets/models.py:166 #: assets/templates/assets/system_user_create_update.html:50 #: assets/templates/assets/system_user_detail.html:63 msgid "Auto push" msgstr "自动推送" -#: assets/models.py:162 +#: assets/models.py:167 msgid "Auto update pass/key" msgstr "自动更新密码/密钥" -#: assets/models.py:163 assets/templates/assets/system_user_detail.html:75 +#: assets/models.py:168 assets/templates/assets/system_user_detail.html:75 +#: templates/_nav.html:46 msgid "Sudo" msgstr "Sudo" -#: assets/models.py:164 assets/templates/assets/system_user_detail.html:80 +#: assets/models.py:169 assets/templates/assets/system_user_detail.html:80 msgid "Shell" msgstr "Shell" -#: assets/models.py:165 assets/templates/assets/system_user_detail.html:86 +#: assets/models.py:170 assets/templates/assets/system_user_detail.html:86 #: templates/_header_bar.html:41 templates/_nav.html:4 msgid "Home" msgstr "仪表盘" -#: assets/models.py:166 assets/templates/assets/system_user_detail.html:92 +#: assets/models.py:171 assets/templates/assets/system_user_detail.html:92 msgid "Uid" msgstr "Uid" -#: assets/models.py:252 -#, fuzzy -#| msgid "As default" -msgid "Default" -msgstr "默认使用" - -#: assets/models.py:252 +#: assets/models.py:257 #, fuzzy #| msgid "Create asset group" msgid "Default asset group" msgstr "创建资产组" -#: assets/models.py:279 assets/templates/assets/admin_user_detail.html:92 -#: assets/templates/assets/asset_detail.html:57 +#: assets/models.py:291 assets/templates/assets/admin_user_detail.html:92 +#: assets/templates/assets/asset_detail.html:54 #: assets/templates/assets/asset_group_detail.html:88 -#: assets/templates/assets/asset_list.html:13 +#: assets/templates/assets/asset_list.html:58 +#: assets/templates/assets/asset_tag_detail.html:84 #: assets/templates/assets/system_user_asset.html:50 #: perms/templates/perms/asset_permission_asset.html:67 #: users/templates/users/user_granted_asset.html:67 msgid "IP" msgstr "IP" -#: assets/models.py:280 assets/templates/assets/asset_detail.html:61 +#: assets/models.py:292 assets/templates/assets/asset_detail.html:58 msgid "Other IP" msgstr "其它IP" -#: assets/models.py:281 assets/templates/assets/asset_detail.html:65 +#: assets/models.py:293 assets/templates/assets/asset_detail.html:62 msgid "Remote card IP" msgstr "远控卡IP" -#: assets/models.py:282 assets/templates/assets/admin_user_detail.html:91 -#: assets/templates/assets/asset_detail.html:53 +#: assets/models.py:294 assets/templates/assets/admin_user_detail.html:91 +#: assets/templates/assets/asset_detail.html:50 #: assets/templates/assets/asset_group_detail.html:87 -#: assets/templates/assets/asset_list.html:12 +#: assets/templates/assets/asset_list.html:57 +#: assets/templates/assets/asset_tag_detail.html:83 #: assets/templates/assets/system_user_asset.html:49 #: perms/templates/perms/asset_permission_asset.html:66 #: users/templates/users/user_granted_asset.html:66 msgid "Hostname" msgstr "主机名" -#: assets/models.py:283 assets/templates/assets/admin_user_detail.html:93 -#: assets/templates/assets/asset_detail.html:69 +#: assets/models.py:295 assets/templates/assets/admin_user_detail.html:93 +#: assets/templates/assets/asset_detail.html:66 #: assets/templates/assets/asset_group_detail.html:89 -#: assets/templates/assets/asset_list.html:14 +#: assets/templates/assets/asset_list.html:59 +#: assets/templates/assets/asset_tag_detail.html:85 #: assets/templates/assets/system_user_asset.html:51 #: perms/templates/perms/asset_permission_asset.html:68 #: users/templates/users/user_granted_asset.html:68 msgid "Port" msgstr "端口" -#: assets/models.py:284 +#: assets/models.py:296 assets/templates/assets/asset_detail.html:184 msgid "Asset groups" msgstr "用户组" -#: assets/models.py:286 templates/_nav.html:24 +#: assets/models.py:298 templates/_nav.html:24 msgid "Admin user" msgstr "管理用户" -#: assets/models.py:287 +#: assets/models.py:299 msgid "System User" msgstr "系统用户" -#: assets/models.py:288 templates/_nav.html:23 +#: assets/models.py:301 templates/_nav.html:23 msgid "IDC" msgstr "机房" -#: assets/models.py:289 assets/templates/assets/asset_detail.html:73 -#: assets/templates/assets/asset_detail.html:99 +#: assets/models.py:303 assets/templates/assets/asset_detail.html:70 msgid "Mac address" msgstr "Mac地址" -#: assets/models.py:290 +#: assets/models.py:304 msgid "Brand" msgstr "品牌" -#: assets/models.py:291 assets/templates/assets/asset_detail.html:77 +#: assets/models.py:305 assets/templates/assets/asset_detail.html:74 msgid "CPU" msgstr "CPU" -#: assets/models.py:292 assets/templates/assets/asset_detail.html:81 +#: assets/models.py:306 assets/templates/assets/asset_detail.html:78 msgid "Memory" msgstr "内存" -#: assets/models.py:293 assets/templates/assets/asset_detail.html:85 +#: assets/models.py:307 assets/templates/assets/asset_detail.html:82 msgid "Disk" msgstr "硬盘" -#: assets/models.py:294 assets/templates/assets/asset_detail.html:95 +#: assets/models.py:308 assets/templates/assets/asset_detail.html:86 msgid "OS" msgstr "操作系统" -#: assets/models.py:295 +#: assets/models.py:309 msgid "Cabinet number" msgstr "机柜编号" -#: assets/models.py:296 +#: assets/models.py:310 msgid "Cabinet position" msgstr "机柜层号" -#: assets/models.py:297 assets/templates/assets/asset_detail.html:123 +#: assets/models.py:311 assets/templates/assets/asset_detail.html:110 msgid "Asset number" msgstr "资产编号" -#: assets/models.py:299 assets/templates/assets/asset_detail.html:103 +#: assets/models.py:313 assets/templates/assets/asset_detail.html:90 msgid "Asset status" msgstr "资产状态" -#: assets/models.py:301 assets/templates/assets/asset_detail.html:111 +#: assets/models.py:316 assets/templates/assets/asset_detail.html:98 msgid "Asset type" msgstr "系统类型" -#: assets/models.py:303 assets/templates/assets/asset_detail.html:115 +#: assets/models.py:319 assets/templates/assets/asset_detail.html:102 msgid "Asset environment" msgstr "资产环境" -#: assets/models.py:304 assets/templates/assets/asset_detail.html:119 +#: assets/models.py:321 assets/templates/assets/asset_detail.html:106 msgid "Serial number" msgstr "序列号" -#: assets/models.py:306 assets/templates/assets/asset_detail.html:107 +#: assets/models.py:323 assets/templates/assets/asset_detail.html:94 msgid "Is active" msgstr "是否激活" @@ -416,11 +451,8 @@ msgstr "自动更新密码/密钥" #: assets/templates/assets/admin_user_create_update.html:53 #: assets/templates/assets/admin_user_detail.html:144 -#: assets/templates/assets/asset_create_update.html:45 -#: assets/templates/assets/asset_detail.html:184 -#: assets/templates/assets/asset_detail.html:192 -#: assets/templates/assets/asset_group_create.html:38 -#: assets/templates/assets/idc_create_update.html:44 +#: assets/templates/assets/asset_create.html:33 +#: assets/templates/assets/asset_update.html:61 #: assets/templates/assets/system_user_create_update.html:71 #: assets/templates/assets/system_user_detail.html:144 #: perms/templates/perms/asset_permission_create_update.html:67 @@ -431,23 +463,26 @@ msgid "Reset" msgstr "重置" #: assets/templates/assets/admin_user_create_update.html:54 -#: assets/templates/assets/asset_create_update.html:46 -#: assets/templates/assets/asset_group_create.html:39 +#: assets/templates/assets/asset_create.html:34 #: assets/templates/assets/asset_group_list.html:51 -#: assets/templates/assets/asset_list.html:64 -#: assets/templates/assets/idc_create_update.html:45 +#: assets/templates/assets/asset_list.html:105 +#: assets/templates/assets/asset_tags_list.html:50 +#: assets/templates/assets/asset_update.html:62 #: assets/templates/assets/system_user_create_update.html:72 +#: ops/templates/cron/list.html:47 ops/templates/sudo/list.html:47 #: perms/templates/perms/asset_permission_create_update.html:68 #: perms/templates/perms/asset_permission_list.html:65 #: users/templates/users/_user.html:71 #: users/templates/users/forgot_password.html:44 #: users/templates/users/user_asset_permission.html:144 -#: users/templates/users/user_list.html:64 +#: users/templates/users/user_group_list.html:40 +#: users/templates/users/user_list.html:47 msgid "Submit" msgstr "提交" #: assets/templates/assets/admin_user_detail.html:19 #: assets/templates/assets/asset_group_detail.html:18 +#: assets/templates/assets/asset_tag_detail.html:18 #: assets/templates/assets/system_user_asset.html:19 #: assets/templates/assets/system_user_asset_group.html:19 #: assets/templates/assets/system_user_detail.html:19 @@ -459,6 +494,7 @@ msgstr "" #: assets/templates/assets/admin_user_detail.html:54 #: assets/templates/assets/asset_group_detail.html:50 +#: assets/templates/assets/asset_tag_detail.html:53 #: assets/templates/assets/system_user_detail.html:97 perms/models.py:30 #: perms/templates/perms/asset_permission_detail.html:84 #, fuzzy @@ -468,6 +504,7 @@ msgstr "加入日期" #: assets/templates/assets/admin_user_detail.html:72 #: assets/templates/assets/asset_group_detail.html:68 +#: assets/templates/assets/asset_tag_detail.html:64 #: assets/templates/assets/system_user_asset_group.html:34 #: perms/templates/perms/asset_permission_asset.html:47 #, fuzzy @@ -477,6 +514,7 @@ msgstr "资产组列表" #: assets/templates/assets/admin_user_detail.html:94 #: assets/templates/assets/asset_group_detail.html:90 +#: assets/templates/assets/asset_tag_detail.html:86 #, fuzzy msgid "Alive" msgstr "激活" @@ -536,7 +574,6 @@ msgid "Replace asset admin user with this admin user" msgstr "" #: assets/templates/assets/admin_user_list.html:9 -#: assets/templates/assets/idc_list.html:9 #: assets/templates/assets/system_user_list.html:9 #, fuzzy #| msgid "IDC" @@ -545,10 +582,13 @@ msgstr "机房" #: assets/templates/assets/admin_user_list.html:12 #: assets/templates/assets/asset_group_list.html:13 -#: assets/templates/assets/idc_list.html:11 +#: assets/templates/assets/asset_tags_list.html:13 +#: assets/templates/assets/idc_list.html:13 #: assets/templates/assets/system_user_asset_group.html:54 #: assets/templates/assets/system_user_list.html:12 -#: users/templates/users/user_list.html:16 +#: ops/templates/cron/list.html:30 ops/templates/sudo/list.html:30 +#: users/templates/users/_select_user_modal.html:17 +#: users/templates/users/user_list.html:30 msgid "Asset num" msgstr "资产数量" @@ -562,128 +602,135 @@ msgid "Script" msgstr "" #: assets/templates/assets/admin_user_list.html:35 +#: assets/templates/assets/asset_detail.html:156 #: assets/templates/assets/system_user_list.html:37 msgid "Refresh" msgstr "" #: assets/templates/assets/admin_user_list.html:36 #: assets/templates/assets/asset_group_list.html:32 -#: assets/templates/assets/asset_list.html:44 -#: assets/templates/assets/idc_list.html:30 +#: assets/templates/assets/asset_list.html:83 +#: assets/templates/assets/asset_tags_list.html:31 +#: assets/templates/assets/idc_list.html:32 #: assets/templates/assets/system_user_list.html:38 +#: ops/templates/cron/list.html:79 ops/templates/sudo/list.html:79 #: perms/templates/perms/asset_permission_list.html:46 -#: users/templates/users/user_list.html:44 +#: users/templates/users/user_detail.html:167 +#: users/templates/users/user_group_list.html:64 +#: users/templates/users/user_list.html:79 msgid "Update" msgstr "更新" #: assets/templates/assets/admin_user_list.html:37 #: assets/templates/assets/asset_group_list.html:33 -#: assets/templates/assets/asset_list.html:45 -#: assets/templates/assets/idc_list.html:31 +#: assets/templates/assets/asset_list.html:84 +#: assets/templates/assets/asset_tags_list.html:32 +#: assets/templates/assets/idc_list.html:33 #: assets/templates/assets/system_user_list.html:39 +#: ops/templates/cron/list.html:80 ops/templates/sudo/list.html:80 #: perms/templates/perms/asset_permission_list.html:47 -#: users/templates/users/user_list.html:45 -#: users/templates/users/user_list.html:46 +#: users/templates/users/user_group_detail.html:129 +#: users/templates/users/user_group_detail.html:132 +#: users/templates/users/user_group_list.html:65 +#: users/templates/users/user_list.html:80 msgid "Delete" msgstr "删除" -#: assets/templates/assets/asset_create_update.html:14 +#: assets/templates/assets/asset_create.html:9 +#: assets/templates/assets/asset_update.html:14 msgid "Basic" msgstr "" -#: assets/templates/assets/asset_create_update.html:21 +#: assets/templates/assets/asset_create.html:16 +#: assets/templates/assets/asset_update.html:21 msgid "Group" msgstr "" -#: assets/templates/assets/asset_create_update.html:26 +#: assets/templates/assets/asset_create.html:21 +#: assets/templates/assets/asset_update.html:26 #, fuzzy #| msgid "Asset number" msgid "Asset user" msgstr "资产编号" -#: assets/templates/assets/asset_create_update.html:31 +#: assets/templates/assets/asset_create.html:26 +#: assets/templates/assets/asset_update.html:52 #: perms/templates/perms/asset_permission_create_update.html:45 #, fuzzy #| msgid "Other IP" msgid "Other" msgstr "其它IP" -#: assets/templates/assets/asset_detail.html:19 +#: assets/templates/assets/asset_detail.html:20 #, fuzzy #| msgid "Asset group list" msgid "Asset detail" msgstr "资产组列表" -#: assets/templates/assets/asset_detail.html:21 -#, fuzzy -#| msgid "Asset number" -msgid "Asset users" -msgstr "资产编号" - -#: assets/templates/assets/asset_detail.html:22 +#: assets/templates/assets/asset_detail.html:23 #, fuzzy #| msgid "Asset group list" msgid "Asset login log" msgstr "资产组列表" -#: assets/templates/assets/asset_detail.html:89 templates/_nav.html:26 -msgid "Label" -msgstr "标签" - -#: assets/templates/assets/asset_detail.html:131 +#: assets/templates/assets/asset_detail.html:118 #: users/templates/users/user_detail.html:94 msgid "Date joined" msgstr "创建日期" -#: assets/templates/assets/asset_detail.html:146 +#: assets/templates/assets/asset_detail.html:133 #: users/templates/users/user_detail.html:113 +#: users/templates/users/user_group_detail.html:115 msgid "Quick modify" msgstr "快速修改" -#: assets/templates/assets/asset_detail.html:152 perms/models.py:27 +#: assets/templates/assets/asset_detail.html:139 +#: ops/templates/cron/list.html:31 ops/templates/sudo/list.html:31 +#: perms/models.py:27 #: perms/templates/perms/asset_permission_create_update.html:47 +#: users/templates/users/_select_user_modal.html:18 #: users/templates/users/user_detail.html:119 -#: users/templates/users/user_list.html:17 +#: users/templates/users/user_list.html:31 #, fuzzy msgid "Active" msgstr "激活" -#: assets/templates/assets/asset_detail.html:166 users/models.py:74 -#: users/templates/users/_user.html:57 -#: users/templates/users/user_detail.html:133 -msgid "Enable OTP" -msgstr "二次验证" +#: assets/templates/assets/asset_detail.html:153 +msgid "Rrefresh hardware" +msgstr "" -#: assets/templates/assets/asset_detail.html:181 -#: users/templates/users/reset_password.html:45 -#: users/templates/users/user_detail.html:148 users/utils.py:99 -msgid "Reset password" -msgstr "重置密码" +#: assets/templates/assets/asset_detail.html:161 +#, fuzzy +#| msgid "Create user" +msgid "Test admin user" +msgstr "创建用户" -#: assets/templates/assets/asset_detail.html:189 -#: users/templates/users/user_detail.html:156 -msgid "Reset ssh key" -msgstr "重置密钥" +#: assets/templates/assets/asset_detail.html:164 +#: assets/templates/assets/asset_detail.html:172 +#, fuzzy +msgid "Test" +msgstr "设置" -#: assets/templates/assets/asset_detail.html:211 users/forms.py:33 -#: users/forms.py:51 users/templates/users/user_detail.html:178 -msgid "Join user groups" +#: assets/templates/assets/asset_detail.html:169 +#, fuzzy +#| msgid "System user" +msgid "Test system users" +msgstr "系统" + +#: assets/templates/assets/asset_detail.html:192 +#, fuzzy +#| msgid "Join user groups" +msgid "Join asset groups" msgstr "添加到用户组" -#: assets/templates/assets/asset_detail.html:220 +#: assets/templates/assets/asset_detail.html:201 #: perms/templates/perms/asset_permission_asset.html:148 #: perms/templates/perms/asset_permission_detail.html:164 #: perms/templates/perms/asset_permission_user.html:148 -#: users/templates/users/user_detail.html:187 +#: users/templates/users/user_detail.html:195 msgid "Join" msgstr "加入" -#: assets/templates/assets/asset_group_create.html:16 -#: assets/templates/assets/asset_group_list.html:5 assets/views.py:80 -#: assets/views.py:150 -msgid "Create asset group" -msgstr "创建资产组" - #: assets/templates/assets/asset_group_detail.html:20 #, fuzzy #| msgid "Asset group" @@ -691,110 +738,155 @@ msgid "Asset group perm" msgstr "资产组" #: assets/templates/assets/asset_group_detail.html:113 +#: assets/templates/assets/asset_tag_detail.html:109 #, fuzzy #| msgid "System user" msgid "Associate system user" msgstr "系统" #: assets/templates/assets/asset_group_detail.html:119 +#: assets/templates/assets/asset_tag_detail.html:115 #, fuzzy #| msgid "System user" msgid "repush system user" msgstr "系统" #: assets/templates/assets/asset_group_detail.html:129 +#: assets/templates/assets/asset_tag_detail.html:125 #, fuzzy #| msgid "System user" msgid "Select system user" msgstr "系统" #: assets/templates/assets/asset_group_detail.html:138 +#: assets/templates/assets/asset_tag_detail.html:134 msgid "Associate" msgstr "" #: assets/templates/assets/asset_group_detail.html:157 +#: assets/templates/assets/asset_tag_detail.html:153 #, fuzzy #| msgid "Asset group" msgid "Add asset to this group" msgstr "资产组" #: assets/templates/assets/asset_group_detail.html:165 +#: assets/templates/assets/asset_tag_detail.html:161 #, fuzzy #| msgid "Select assets" msgid "Select asset user" msgstr "选择资产" #: assets/templates/assets/asset_group_detail.html:174 +#: assets/templates/assets/asset_tag_detail.html:170 #: assets/templates/assets/system_user_asset_group.html:96 #: perms/templates/perms/asset_permission_asset.html:120 #: perms/templates/perms/asset_permission_user.html:120 +#: users/templates/users/user_group_detail.html:124 #, fuzzy #| msgid "Address" msgid "Add" msgstr "地址" +#: assets/templates/assets/asset_group_list.html:5 assets/views.py:157 +#: assets/views.py:234 +msgid "Create asset group" +msgstr "创建资产组" + #: assets/templates/assets/asset_group_list.html:43 -#: assets/templates/assets/asset_list.html:56 +#: assets/templates/assets/asset_list.html:98 +#: assets/templates/assets/asset_tags_list.html:42 +#: ops/templates/cron/list.html:41 ops/templates/sudo/list.html:41 #: perms/templates/perms/asset_permission_list.html:57 -#: users/templates/users/user_list.html:56 +#: users/templates/users/user_group_list.html:36 +#: users/templates/users/user_list.html:41 msgid "Delete selected" msgstr "批量删除" #: assets/templates/assets/asset_group_list.html:44 -#: assets/templates/assets/asset_list.html:57 +#: assets/templates/assets/asset_list.html:99 +#: assets/templates/assets/asset_tags_list.html:43 +#: ops/templates/cron/list.html:42 ops/templates/sudo/list.html:42 #: perms/templates/perms/asset_permission_list.html:58 -#: users/templates/users/user_list.html:57 +#: users/templates/users/user_list.html:42 msgid "Update selected" msgstr "批量更新" #: assets/templates/assets/asset_group_list.html:45 -#: assets/templates/assets/asset_list.html:58 +#: assets/templates/assets/asset_list.html:100 +#: assets/templates/assets/asset_tags_list.html:44 +#: ops/templates/cron/list.html:43 ops/templates/sudo/list.html:43 #: perms/templates/perms/asset_permission_list.html:59 -#: users/templates/users/user_list.html:58 +#: users/templates/users/user_list.html:43 msgid "Deactive selected" msgstr "禁用所选" #: assets/templates/assets/asset_group_list.html:46 -#: assets/templates/assets/asset_list.html:59 +#: assets/templates/assets/asset_list.html:101 +#: assets/templates/assets/asset_tags_list.html:45 #: perms/templates/perms/asset_permission_list.html:60 -#: users/templates/users/user_list.html:59 msgid "Export selected" msgstr "批量导出" -#: assets/templates/assets/asset_list.html:5 +#: assets/templates/assets/asset_list.html:21 msgid "Create asset" msgstr "创建资产" -#: assets/templates/assets/asset_list.html:15 +#: assets/templates/assets/asset_list.html:60 msgid "Type" msgstr "" -#: assets/templates/assets/asset_list.html:16 +#: assets/templates/assets/asset_list.html:61 +#: assets/templates/assets/asset_update.html:31 msgid "Hardware" msgstr "" -#: assets/templates/assets/asset_list.html:17 +#: assets/templates/assets/asset_list.html:62 msgid "Valid" msgstr "" +#: assets/templates/assets/asset_tag_detail.html:45 +#: assets/templates/assets/asset_tags_list.html:12 +#, fuzzy +#| msgid "Name" +msgid "Tag Name" +msgstr "名称" + +#: assets/templates/assets/asset_tags_list.html:5 +#, fuzzy +#| msgid "Create asset" +msgid "Create tag" +msgstr "创建资产" + +#: assets/templates/assets/asset_update.html:40 +#, fuzzy +#| msgid "Confirm delete" +msgid "Configuration" +msgstr "确认删除" + +#: assets/templates/assets/asset_update.html:47 +#, fuzzy +msgid "Location" +msgstr "激活" + #: assets/templates/assets/delete_confirm.html:6 #: perms/templates/perms/delete_confirm.html:6 #: users/templates/users/user_delete_confirm.html:6 msgid "Confirm delete" msgstr "确认删除" -#: assets/templates/assets/idc_create_update.html:16 -#, fuzzy -#| msgid "Created by" -msgid "Create idc" -msgstr "创建者" - -#: assets/templates/assets/idc_list.html:5 +#: assets/templates/assets/idc_list.html:5 assets/views.py:287 #, fuzzy #| msgid "Created by" msgid "Create IDC" msgstr "创建者" +#: assets/templates/assets/idc_list.html:16 +#, fuzzy +#| msgid "Operator" +msgid "operation" +msgstr "运营商" + #: assets/templates/assets/system_user_asset.html:22 #: assets/templates/assets/system_user_detail.html:23 #, fuzzy @@ -864,7 +956,7 @@ msgid "Select asset group" msgstr "添加到资产组" #: assets/templates/assets/system_user_create_update.html:16 -#: assets/templates/assets/system_user_list.html:5 assets/views.py:368 +#: assets/templates/assets/system_user_list.html:5 assets/views.py:469 #, fuzzy #| msgid "Create user" msgid "Create system user" @@ -887,67 +979,375 @@ msgstr "" msgid "Asset group num" msgstr "资产组" -#: assets/templates/assets/system_user_list.html:14 +#: assets/templates/assets/system_user_list.html:14 ops/models.py:69 msgid "Unreachable" msgstr "" -#: assets/views.py:79 assets/views.py:99 assets/views.py:133 -#: assets/views.py:149 assets/views.py:171 assets/views.py:238 -#: assets/views.py:337 assets/views.py:367 assets/views.py:390 -#: assets/views.py:408 templates/_nav.html:18 +#: assets/views.py:156 assets/views.py:182 assets/views.py:214 +#: assets/views.py:233 assets/views.py:257 assets/views.py:339 +#: assets/views.py:438 assets/views.py:468 assets/views.py:491 +#: assets/views.py:509 templates/_nav.html:18 msgid "Assets" msgstr "资产管理" -#: assets/views.py:100 +#: assets/views.py:183 msgid "Asset group list" msgstr "资产组列表" -#: assets/views.py:134 +#: assets/views.py:215 #, fuzzy #| msgid "Asset group list" msgid "Asset group detail" msgstr "资产组列表" -#: assets/views.py:172 +#: assets/views.py:258 msgid "IDC list" msgstr "" -#: assets/views.py:239 +#: assets/views.py:286 assets/views.py:313 +#, fuzzy +#| msgid "Assets" +msgid "assets" +msgstr "资产管理" + +#: assets/views.py:314 +#, fuzzy +#| msgid "Update" +msgid "Update IDC" +msgstr "更新" + +#: assets/views.py:340 #, fuzzy #| msgid "Admin user" msgid "Admin user list" msgstr "管理用户" -#: assets/views.py:275 +#: assets/views.py:376 #, fuzzy, python-format #| msgid "Create user %s success." msgid "Create admin user %s successfully." msgstr "创建用户 %s 成功" -#: assets/views.py:338 +#: assets/views.py:439 #, fuzzy #| msgid "System user" msgid "System user list" msgstr "系统" -#: assets/views.py:374 +#: assets/views.py:475 #, fuzzy, python-format #| msgid "Create user %s success." msgid "Create system user %s successfully." msgstr "创建用户 %s 成功" -#: assets/views.py:391 +#: assets/views.py:492 #, fuzzy #| msgid "Update user" msgid "Update system user" msgstr "编辑用户" -#: assets/views.py:409 +#: assets/views.py:510 #, fuzzy #| msgid "System user" msgid "System user detail" msgstr "系统" +#: assets/views.py:601 assets/views.py:619 assets/views.py:648 +#: assets/views.py:667 +msgid "Tag" +msgstr "" + +#: assets/views.py:602 assets/views.py:620 +#, fuzzy +#| msgid "Asset group list" +msgid "Asset Tags list" +msgstr "资产组列表" + +#: assets/views.py:649 assets/views.py:668 +#, fuzzy +#| msgid "Asset group list" +msgid "Asset Tags detail" +msgstr "资产组列表" + +#: common/mixins.py:28 +msgid "is discard" +msgstr "" + +#: common/mixins.py:29 +msgid "discard time" +msgstr "" + +#: ops/api/exc.py:10 +msgid "Service temporarily unavailable, try again later." +msgstr "" + +#: ops/api/exc.py:15 +msgid "This service maybe implemented in the future, but now not implemented!" +msgstr "" + +#: ops/models.py:17 ops/models.py:35 ops/models.py:47 +msgid "UUID" +msgstr "" + +#: ops/models.py:19 +msgid "Start Time" +msgstr "" + +#: ops/models.py:20 +msgid "End Time" +msgstr "" + +#: ops/models.py:21 +msgid "Exit Code" +msgstr "" + +#: ops/models.py:22 +msgid "Is Completed" +msgstr "" + +#: ops/models.py:23 +#, fuzzy +#| msgid "Hostname" +msgid "Hosts" +msgstr "主机名" + +#: ops/models.py:66 +msgid "Success" +msgstr "" + +#: ops/models.py:67 +msgid "Skipped" +msgstr "" + +#: ops/models.py:68 +msgid "Failed" +msgstr "" + +#: ops/models.py:70 +msgid "NoHost" +msgstr "" + +#: ops/models.py:217 +msgid "Host_Alias" +msgstr "" + +#: ops/models.py:218 ops/models.py:226 ops/models.py:234 ops/models.py:242 +msgid "Host_Items" +msgstr "" + +#: ops/models.py:225 +#, fuzzy +#| msgid "User list" +msgid "User_Alias" +msgstr "用户列表" + +#: ops/models.py:233 +msgid "Command_Alias" +msgstr "" + +#: ops/models.py:241 +msgid "Runas_Alias" +msgstr "" + +#: ops/models.py:253 +#, fuzzy +#| msgid "Password" +msgid "Is_NoPassword" +msgstr "密码" + +#: ops/models.py:267 +msgid "Extra_Item" +msgstr "" + +#: ops/models.py:398 +msgid "Description of a crontab entry" +msgstr "" + +#: ops/models.py:399 +msgid "Month" +msgstr "" + +#: ops/models.py:400 +msgid "Month of the year the job should run ( 1-12, *, */2, etc )" +msgstr "" + +#: ops/models.py:401 +msgid "WeekDay" +msgstr "" + +#: ops/models.py:402 +msgid "" +"Day of the week that the job should run ( 0-6 for Sunday-Saturday, *, etc )" +msgstr "" + +#: ops/models.py:404 +msgid "Day" +msgstr "" + +#: ops/models.py:405 +msgid "Day of the month the job should run ( 1-31, *, */2, etc )" +msgstr "" + +#: ops/models.py:406 +msgid "Hour" +msgstr "" + +#: ops/models.py:407 +msgid "Hour when the job should run ( 0-23, *, */2, etc )" +msgstr "" + +#: ops/models.py:408 +msgid "Minute" +msgstr "" + +#: ops/models.py:409 +msgid "Minute when the job should run ( 0-59, *, */2, etc )" +msgstr "" + +#: ops/models.py:410 +msgid "Job" +msgstr "" + +#: ops/models.py:411 +msgid "" +"The command to execute or, if env is set, the value of environment variable. " +"Required if state=present." +msgstr "" + +#: ops/models.py:413 +#: perms/templates/perms/asset_permission_create_update.html:36 +#: templates/_nav.html:12 templates/_user_profile.html:14 users/models.py:71 +#: users/templates/users/_user_bulk_update_modal.html:14 +msgid "User" +msgstr "用户" + +#: ops/models.py:414 +msgid "The specific user whose crontab should be modified." +msgstr "" + +#: ops/templates/cron/list.html:18 ops/templates/sudo/list.html:18 +#: users/templates/users/user_list.html:18 +#, fuzzy +#| msgid "Create user" +msgid "Import user" +msgstr "创建用户" + +#: ops/templates/cron/list.html:19 ops/templates/sudo/list.html:19 +#: users/templates/users/_user.html:17 users/templates/users/user_create.html:4 +#: users/templates/users/user_list.html:19 users/views.py:102 +msgid "Create user" +msgstr "创建用户" + +#: ops/templates/cron/list.html:28 ops/templates/sudo/list.html:28 +#: users/models.py:78 users/templates/users/_select_user_modal.html:15 +#: users/templates/users/_user_bulk_update_modal.html:9 +#: users/templates/users/user_detail.html:82 +#: users/templates/users/user_list.html:28 +msgid "Role" +msgstr "角色" + +#: ops/templates/cron/list.html:29 ops/templates/sudo/list.html:29 +#: templates/_nav.html:13 users/models.py:77 +#: users/templates/users/_select_user_modal.html:16 +#: users/templates/users/user_detail.html:178 +#: users/templates/users/user_list.html:29 +msgid "User group" +msgstr "用户组" + +#: ops/templates/cron/list.html:32 ops/templates/sudo/list.html:32 +#: users/templates/users/user_group_list.html:29 +#: users/templates/users/user_list.html:32 +#, fuzzy +msgid "Action" +msgstr "激活" + +#: ops/templates/cron/list.html:116 ops/templates/cron/list.html:178 +#: ops/templates/sudo/list.html:116 ops/templates/sudo/list.html:178 +#: users/templates/users/user_detail.html:329 +#: users/templates/users/user_detail.html:354 +#: users/templates/users/user_group_detail.html:169 +#: users/templates/users/user_group_list.html:102 +#: users/templates/users/user_group_list.html:125 +#: users/templates/users/user_list.html:116 +#: users/templates/users/user_list.html:178 +msgid "Are you sure?" +msgstr "" + +#: ops/templates/cron/list.html:117 ops/templates/sudo/list.html:117 +#: users/templates/users/user_list.html:117 +msgid "This will delete the selected users !!!" +msgstr "" + +#: ops/templates/cron/list.html:121 ops/templates/cron/list.html:183 +#: ops/templates/sudo/list.html:121 ops/templates/sudo/list.html:183 +#: templates/_modal.html:16 users/templates/users/user_detail.html:334 +#: users/templates/users/user_detail.html:359 +#: users/templates/users/user_detail.html:383 +#: users/templates/users/user_group_detail.html:174 +#: users/templates/users/user_group_list.html:107 +#: users/templates/users/user_group_list.html:130 +#: users/templates/users/user_list.html:121 +#: users/templates/users/user_list.html:183 +#, fuzzy +#| msgid "Confirm delete" +msgid "Confirm" +msgstr "确认删除" + +# msgid "Deleted!" +# msgstr "删除" +#: ops/templates/cron/list.html:125 ops/templates/cron/list.html:161 +#: ops/templates/sudo/list.html:125 ops/templates/sudo/list.html:161 +#: users/templates/users/user_list.html:125 +#: users/templates/users/user_list.html:161 +#, fuzzy +#| msgid "has been deleted." +msgid "User Deleted." +msgstr "已被删除" + +#: ops/templates/cron/list.html:126 ops/templates/cron/list.html:131 +#: ops/templates/cron/list.html:162 ops/templates/cron/list.html:167 +#: ops/templates/sudo/list.html:126 ops/templates/sudo/list.html:131 +#: ops/templates/sudo/list.html:162 ops/templates/sudo/list.html:167 +#: users/templates/users/user_list.html:126 +#: users/templates/users/user_list.html:131 +#: users/templates/users/user_list.html:162 +#: users/templates/users/user_list.html:167 +#, fuzzy +#| msgid "Delete" +msgid "User Delete" +msgstr "删除" + +#: ops/templates/cron/list.html:130 ops/templates/cron/list.html:166 +#: ops/templates/sudo/list.html:130 ops/templates/sudo/list.html:166 +#: users/templates/users/user_list.html:130 +#: users/templates/users/user_list.html:166 +#, fuzzy +#| msgid "User detail" +msgid "User Deleting failed." +msgstr "用户详情" + +#: ops/templates/cron/list.html:179 ops/templates/sudo/list.html:179 +#: users/templates/users/user_list.html:179 +msgid "This will delete the selected user." +msgstr "" + +#: ops/templates/cron/list.html:215 ops/templates/sudo/list.html:215 +#: users/templates/users/user_list.html:215 +msgid "The selected users has been updated successfully." +msgstr "" + +#: ops/templates/cron/list.html:216 ops/templates/sudo/list.html:216 +#: users/templates/users/user_list.html:216 +#, fuzzy +#| msgid "Update" +msgid "User Updated" +msgstr "更新" + +#: ops/templates/cron/list.html:232 ops/templates/sudo/list.html:232 +#: users/templates/users/user_list.html:232 +msgid "Import User Success." +msgstr "" + #: perms/forms.py:21 #, fuzzy #| msgid "Select assets" @@ -961,7 +1361,7 @@ msgid "Select user groups" msgstr "添加到资产组" #: perms/forms.py:29 perms/templates/perms/asset_permission_detail.html:155 -#: users/forms.py:117 +#: users/forms.py:126 #, fuzzy #| msgid "System user" msgid "Select system users" @@ -974,7 +1374,7 @@ msgid "Private for" msgstr "ssh密钥" #: perms/models.py:28 perms/templates/perms/asset_permission_detail.html:80 -#: users/models.py:81 users/templates/users/user_detail.html:86 +#: users/models.py:89 users/templates/users/user_detail.html:86 msgid "Date expired" msgstr "失效日期" @@ -1022,11 +1422,6 @@ msgstr "" msgid "Create asset permission " msgstr "创建资产组" -#: perms/templates/perms/asset_permission_create_update.html:36 -#: templates/_nav.html:12 templates/_user_profile.html:14 users/models.py:63 -msgid "User" -msgstr "用户" - #: perms/templates/perms/asset_permission_detail.html:60 #: perms/templates/perms/asset_permission_list.html:13 #, fuzzy @@ -1091,7 +1486,7 @@ msgstr "创建权限" msgid "User list of " msgstr "用户列表" -#: perms/templates/perms/asset_permission_user.html:68 users/models.py:68 +#: perms/templates/perms/asset_permission_user.html:68 users/models.py:76 #: users/templates/users/user_detail.html:66 msgid "Email" msgstr "邮件" @@ -1188,24 +1583,16 @@ msgstr "登录" msgid "Close" msgstr "" -#: templates/_modal.html:16 users/templates/users/user_detail.html:304 -#: users/templates/users/user_detail.html:326 -#, fuzzy -#| msgid "Confirm delete" -msgid "Confirm" -msgstr "确认删除" - -#: templates/_nav.html:9 users/views.py:102 users/views.py:115 -#: users/views.py:155 users/views.py:186 users/views.py:211 users/views.py:224 -#: users/views.py:340 +#: templates/_nav.html:9 users/templates/users/user_group_create.html:24 +#: users/templates/users/user_group_detail.html:102 users/views.py:89 +#: users/views.py:102 users/views.py:138 users/views.py:149 users/views.py:159 +#: users/views.py:172 users/views.py:197 users/views.py:219 users/views.py:320 msgid "Users" msgstr "用户管理" -#: templates/_nav.html:13 users/models.py:69 -#: users/templates/users/user_detail.html:170 -#: users/templates/users/user_list.html:15 -msgid "User group" -msgstr "用户组" +#: templates/_nav.html:26 +msgid "Label" +msgstr "标签" #: templates/_nav.html:33 users/templates/users/user_asset_permission.html:23 #: users/templates/users/user_detail.html:24 @@ -1215,27 +1602,35 @@ msgstr "用户组" msgid "Asset permission" msgstr "系统类型" -#: templates/_nav.html:42 +#: templates/_nav.html:43 +msgid "Job Center" +msgstr "作业中心" + +#: templates/_nav.html:47 +msgid "Cron" +msgstr "Cron" + +#: templates/_nav.html:53 msgid "Audits" msgstr "审计" -#: templates/_nav.html:47 +#: templates/_nav.html:58 msgid "File" msgstr "文件" -#: templates/_nav.html:50 +#: templates/_nav.html:61 msgid "File upload" msgstr "文件上传" -#: templates/_nav.html:51 +#: templates/_nav.html:62 msgid "File download" msgstr "文件下载" -#: templates/_nav.html:56 +#: templates/_nav.html:67 msgid "Settings" msgstr "设置" -#: templates/_nav.html:61 +#: templates/_nav.html:72 msgid "Visit us" msgstr "访问官网" @@ -1243,6 +1638,23 @@ msgstr "访问官网" msgid "Profile" msgstr "个人信息" +#: templates/base.html:27 +#, python-format +msgid "" +"\n" +" Your information was incomplete. Please click this link to complete your information.\n" +" " +msgstr "" + +#: templates/base.html:36 +msgid "" +"\n" +" Your ssh-public-key has been expired. Please click this link to update your ssh-public-key.\n" +" " +msgstr "" + #: templates/captcha/image.html:3 msgid "Play CAPTCHA as audio file" msgstr "" @@ -1256,94 +1668,94 @@ msgstr "验证码" msgid "Filters" msgstr "过滤" -#: users/forms.py:68 +#: users/forms.py:33 users/forms.py:58 +#: users/templates/users/user_detail.html:186 +msgid "Join user groups" +msgstr "添加到用户组" + +#: users/forms.py:75 #, fuzzy #| msgid "Name" msgid "name" msgstr "名称" -#: users/forms.py:69 +#: users/forms.py:76 #, fuzzy #| msgid "Avatar" msgid "avatar" msgstr "头像" -#: users/forms.py:70 +#: users/forms.py:77 #, fuzzy #| msgid "Wechat" msgid "wechat" msgstr "微信" -#: users/forms.py:71 +#: users/forms.py:78 #, fuzzy #| msgid "Phone" msgid "phone" msgstr "手机" -#: users/forms.py:72 +#: users/forms.py:79 #, fuzzy #| msgid "Enable OTP" msgid "enable otp" msgstr "二次验证" -#: users/forms.py:77 users/models.py:77 +#: users/forms.py:84 users/models.py:85 msgid "ssh public key" msgstr "ssh公钥" -#: users/forms.py:78 +#: users/forms.py:85 msgid "ssh-rsa AAAA..." msgstr "" -#: users/forms.py:79 +#: users/forms.py:86 msgid "Paste your id_ras.pub here." msgstr "" -#: users/forms.py:90 users/forms.py:93 +#: users/forms.py:91 +msgid "Public key should not be the same as your old one." +msgstr "" + +#: users/forms.py:99 users/forms.py:102 users/serializers.py:32 +#: users/serializers.py:35 #, fuzzy #| msgid "ssh private key" msgid "Not a valid ssh public key" msgstr "ssh密钥" -#: users/models.py:62 users/models.py:206 +#: users/models.py:70 users/models.py:220 msgid "Administrator" msgstr "管理员" -#: users/models.py:70 users/templates/users/user_detail.html:82 -#: users/templates/users/user_list.html:14 -msgid "Role" -msgstr "角色" - -#: users/models.py:71 +#: users/models.py:79 msgid "Avatar" msgstr "头像" -#: users/models.py:72 users/templates/users/user_detail.html:77 +#: users/models.py:80 users/templates/users/user_detail.html:77 msgid "Wechat" msgstr "微信" -#: users/models.py:76 +#: users/models.py:82 users/templates/users/_user.html:57 +#: users/templates/users/user_detail.html:133 +msgid "Enable OTP" +msgstr "二次验证" + +#: users/models.py:84 msgid "ssh private key" msgstr "ssh密钥" -#: users/models.py:209 +#: users/models.py:223 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" -#: users/models.py:210 -msgid "System" -msgstr "系统" - -#: users/serializers.py:55 +#: users/templates/users/_select_user_modal.html:5 #, fuzzy -#| msgid "ssh private key" -msgid "Not a valid ssh private key." -msgstr "ssh密钥" - -#: users/templates/users/_user.html:17 -#: users/templates/users/user_create.html:4 -#: users/templates/users/user_list.html:5 users/views.py:115 -msgid "Create user" -msgstr "创建用户" +#| msgid "Select assets" +msgid "Please Select User" +msgstr "选择资产" #: users/templates/users/_user.html:33 msgid "Account" @@ -1353,33 +1765,87 @@ msgstr "账户" msgid "Security and Role" msgstr "角色安全" -#: users/templates/users/_user_reset_pk_modal.html:4 +#: users/templates/users/_user_bulk_update_modal.html:4 +#, fuzzy +#| msgid "Update user" +msgid "Update User" +msgstr "编辑用户" + +#: users/templates/users/_user_bulk_update_modal.html:6 +msgid "Hint: only change the field you want to update." +msgstr "" + +#: users/templates/users/_user_bulk_update_modal.html:13 +#, fuzzy +#| msgid "Admin user" +msgid "Admin" +msgstr "管理用户" + +#: users/templates/users/_user_bulk_update_modal.html:19 +msgid "Groups" +msgstr "" + +#: users/templates/users/_user_bulk_update_modal.html:21 +#, fuzzy +#| msgid "Select asset groups" +msgid "Select Group" +msgstr "添加到资产组" + +#: users/templates/users/_user_bulk_update_modal.html:31 +#, fuzzy +#| msgid "Enable OTP" +msgid "Enable-OTP" +msgstr "二次验证" + +#: users/templates/users/_user_import_modal.html:4 +msgid "Import User" +msgstr "" + +#: users/templates/users/_user_import_modal.html:6 +msgid "Hint: your excel should organized in the following format." +msgstr "" + +#: users/templates/users/_user_import_modal.html:7 +msgid "* You should have a very worksheet named `users`." +msgstr "" + +#: users/templates/users/_user_import_modal.html:8 +msgid "" +"* Rows in this worksheet: username, email, enable_opt(0, 1), role(one of " +"['Admin', 'User'])" +msgstr "" + +#: users/templates/users/_user_import_modal.html:12 +msgid "Excel" +msgstr "" + +#: users/templates/users/_user_update_pk_modal.html:4 #, fuzzy #| msgid "SSH private key" -msgid "Reset User SSH Private Key" +msgid "Update User SSH Public Key" msgstr "ssh密钥" -#: users/templates/users/first_login.html:16 users/views.py:340 +#: users/templates/users/first_login.html:18 users/views.py:320 #, fuzzy #| msgid "Last login" msgid "First Login" msgstr "最后登录" -#: users/templates/users/first_login.html:33 +#: users/templates/users/first_login.html:35 #, fuzzy #| msgid "System" msgid "Step" msgstr "系统" -#: users/templates/users/first_login.html:55 +#: users/templates/users/first_login.html:57 msgid "first step" msgstr "" -#: users/templates/users/first_login.html:56 +#: users/templates/users/first_login.html:58 msgid "prev step" msgstr "" -#: users/templates/users/first_login.html:58 +#: users/templates/users/first_login.html:60 #, fuzzy #| msgid "Submit" msgid "submit" @@ -1398,6 +1864,11 @@ msgstr "输入您的邮箱, 将会发一封重置短信邮件到您的邮箱中" msgid "Captcha invalid" msgstr "验证码错误" +#: users/templates/users/reset_password.html:45 +#: users/templates/users/user_detail.html:148 users/utils.py:99 +msgid "Reset password" +msgstr "重置密码" + #: users/templates/users/reset_password.html:55 msgid "Password again" msgstr "再次输入密码" @@ -1409,7 +1880,7 @@ msgstr "设置" #: users/templates/users/user_asset_permission.html:20 #: users/templates/users/user_detail.html:21 -#: users/templates/users/user_granted_asset.html:20 users/views.py:186 +#: users/templates/users/user_granted_asset.html:20 users/views.py:149 msgid "User detail" msgstr "用户详情" @@ -1477,49 +1948,69 @@ msgstr "生成重置密码连接,通过邮件发送给用户" msgid "Last login" msgstr "最后登录" -#: users/templates/users/user_detail.html:236 +#: users/templates/users/user_detail.html:156 +msgid "Reset ssh key" +msgstr "重置密钥" + +#: users/templates/users/user_detail.html:164 +#, fuzzy +#| msgid "Update user" +msgid "Update ssh key" +msgstr "编辑用户" + +#: users/templates/users/user_detail.html:246 msgid "UserGroup Update Success!" msgstr "" -#: users/templates/users/user_detail.html:254 -#: users/templates/users/user_detail.html:260 +#: users/templates/users/user_detail.html:270 +#: users/templates/users/user_detail.html:282 #, fuzzy #| msgid "Create account successfully" msgid "Update Successfully!" msgstr "创建账户成功" -#: users/templates/users/user_detail.html:293 +#: users/templates/users/user_detail.html:319 msgid "E-mail sent successfully. An e-mail has been sent to the user\\" msgstr "" -#: users/templates/users/user_detail.html:294 +#: users/templates/users/user_detail.html:320 #, fuzzy #| msgid "Password" msgid "Password-Reset" msgstr "密码" -#: users/templates/users/user_detail.html:299 -#: users/templates/users/user_detail.html:321 -msgid "Are you sure?" -msgstr "" - -#: users/templates/users/user_detail.html:300 -#: users/templates/users/user_detail.html:322 +#: users/templates/users/user_detail.html:330 +#: users/templates/users/user_detail.html:355 msgid "This will reset the user\\" msgstr "" -#: users/templates/users/user_detail.html:315 +#: users/templates/users/user_detail.html:344 msgid "" "The reset-ssh-public-key E-mail has been sent successfully. Please inform " "the user to update his new ssh public key." msgstr "" -#: users/templates/users/user_detail.html:316 +#: users/templates/users/user_detail.html:345 #, fuzzy #| msgid "SSH private key" msgid "SSH-Public-Key Reset" msgstr "ssh密钥" +#: users/templates/users/user_detail.html:372 +msgid "Successfully updated the SSH public key." +msgstr "" + +#: users/templates/users/user_detail.html:373 +#: users/templates/users/user_detail.html:378 +#, fuzzy +#| msgid "SSH private key" +msgid "User SSH Public Key Update" +msgstr "ssh密钥" + +#: users/templates/users/user_detail.html:376 +msgid "Failed to update the user\\" +msgstr "" + #: users/templates/users/user_granted_asset.html:47 #, fuzzy #| msgid "Create asset group" @@ -1532,11 +2023,111 @@ msgstr "创建资产组" msgid "Asset groups granted of " msgstr "资产组列表" -#: users/templates/users/user_group_create.html:16 users/views.py:224 -msgid "Create user group" -msgstr "创建用户组" +#: users/templates/users/user_group_create.html:26 +#, fuzzy +#| msgid "Select assets" +msgid "Select User" +msgstr "选择资产" -#: users/templates/users/user_update.html:3 users/views.py:155 +#: users/templates/users/user_group_create.html:38 +msgid "Cancel" +msgstr "" + +#: users/templates/users/user_group_create.html:39 +#, fuzzy +#| msgid "Confirm delete" +msgid "confirm" +msgstr "确认删除" + +#: users/templates/users/user_group_detail.html:70 users/views.py:219 +#, fuzzy +#| msgid "Asset group list" +msgid "User Group Detail" +msgstr "资产组列表" + +#: users/templates/users/user_group_detail.html:98 +#, fuzzy +#| msgid "Create asset" +msgid "Created at" +msgstr "创建资产" + +#: users/templates/users/user_group_detail.html:121 +#, fuzzy +#| msgid "User" +msgid "Add User" +msgstr "用户" + +#: users/templates/users/user_group_detail.html:170 +msgid "This will delete the current group, but will not delete any user of it." +msgstr "" + +#: users/templates/users/user_group_detail.html:223 +msgid "The selected users has been added to current group." +msgstr "" + +#: users/templates/users/user_group_list.html:18 +#, fuzzy +#| msgid "Asset group" +msgid "Add User Group" +msgstr "资产组" + +#: users/templates/users/user_group_list.html:26 +#, fuzzy +#| msgid "User group" +msgid "User Amount" +msgstr "用户组" + +#: users/templates/users/user_group_list.html:27 +#, fuzzy +#| msgid "Asset group" +msgid "Asset Amount" +msgstr "资产组" + +#: users/templates/users/user_group_list.html:85 +#, fuzzy +#| msgid "Delete" +msgid "Group Deleted." +msgstr "删除" + +#: users/templates/users/user_group_list.html:86 +#: users/templates/users/user_group_list.html:91 +#, fuzzy +#| msgid "Delete" +msgid "Group Delete" +msgstr "删除" + +#: users/templates/users/user_group_list.html:90 +msgid "Group Deleting failed." +msgstr "" + +#: users/templates/users/user_group_list.html:103 +msgid "" +"This will delete the selected group, but will not remove any user in this " +"group." +msgstr "" + +#: users/templates/users/user_group_list.html:126 +msgid "This will delete the selected groups !!!" +msgstr "" + +#: users/templates/users/user_group_list.html:134 +#, fuzzy +#| msgid "User group list" +msgid "UserGroups Deleted." +msgstr "用户组列表" + +#: users/templates/users/user_group_list.html:135 +#: users/templates/users/user_group_list.html:140 +#, fuzzy +#| msgid "User group list" +msgid "UserGroups Delete" +msgstr "用户组列表" + +#: users/templates/users/user_group_list.html:139 +msgid "UserGroup Deleting failed." +msgstr "" + +#: users/templates/users/user_update.html:3 users/views.py:138 msgid "Update user" msgstr "编辑用户" @@ -1567,8 +2158,8 @@ msgid "" " click " "here to set your password\n" "
    \n" -" This link is valid for 1 hour. After it expires, request new one\n" +" This link is valid for 1 hour. After it expires, request new one\n" "\n" "
    \n" " ---\n" @@ -1609,8 +2200,8 @@ msgid "" " Click " "here reset password\n" "
    \n" -" This link is valid for 1 hour. After it expires, request new one<\n" +" This link is valid for 1 hour. After it expires, request new one<\n" "\n" "
    \n" " ---\n" @@ -1662,68 +2253,89 @@ msgid "" " " msgstr "" -#: users/views.py:73 +#: users/views.py:75 msgid "Logout success" msgstr "退出登录成功" -#: users/views.py:74 +#: users/views.py:76 msgid "Logout success, return login page" msgstr "退出登录成功,返回到登录页面" -#: users/views.py:102 +#: users/views.py:89 msgid "User list" msgstr "用户列表" -#: users/views.py:111 +#: users/views.py:98 #, fuzzy, python-format #| msgid "Create user %s success." msgid "Create user %s successfully." msgstr "创建用户 %s 成功" -#: users/views.py:211 +#: users/views.py:159 msgid "User group list" msgstr "用户组列表" -#: users/views.py:256 +#: users/views.py:172 +msgid "Create user group" +msgstr "创建用户组" + +#: users/views.py:198 +#, fuzzy +#| msgid "Update user" +msgid "Update User Group" +msgstr "编辑用户" + +#: users/views.py:235 msgid "Email address invalid, input again" msgstr "邮箱地址错误,重新输入" -#: users/views.py:267 +#: users/views.py:246 msgid "Send reset password message" msgstr "发送重置密码邮件" -#: users/views.py:268 +#: users/views.py:247 msgid "Send reset password mail success, login your mail box and follow it " msgstr "" "发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" -#: users/views.py:280 +#: users/views.py:259 msgid "Reset password success" msgstr "重置密码成功" -#: users/views.py:281 +#: users/views.py:260 msgid "Reset password success, return to login page" msgstr "重置密码成功,返回到登录页面" -#: users/views.py:297 users/views.py:310 +#: users/views.py:276 users/views.py:289 msgid "Token invalid or expired" msgstr "Token错误或失效" -#: users/views.py:306 +#: users/views.py:285 msgid "Password not same" msgstr "密码不一致" -#: templates/_nav.html:43 -msgid "Job Center" -msgstr "作业中心" +#: users/views.py:447 +msgid "Invalid file." +msgstr "" -#: templates/_nav.html:46 -msgid "Sudo" -msgstr "Sudo管理" +#: users/views.py:461 +#, fuzzy +#| msgid "ssh private key" +msgid "Not a valid Excel file." +msgstr "ssh密钥" -#: templates/_nav.html:47 -msgid "Cron" -msgstr "Cron管理" +#~ msgid "Network" +#~ msgstr "网络" + +#, fuzzy +#~| msgid "Asset number" +#~ msgid "Asset users" +#~ msgstr "资产编号" + +#, fuzzy +#~| msgid "Created by" +#~ msgid "Create idc" +#~ msgstr "创建者" #~ msgid "Admin password" #~ msgstr "管理员密码" @@ -1733,10 +2345,6 @@ msgstr "Cron管理" #~ msgid "Update system user %s successfully." #~ msgstr "创建用户 %s 成功" -#, fuzzy -#~ msgid "Action" -#~ msgstr "激活" - #, fuzzy #~| msgid "Create perm" #~ msgid "Create perm " @@ -1745,11 +2353,6 @@ msgstr "Cron管理" #~ msgid "Create perm" #~ msgstr "创建权限" -# msgid "Deleted!" -# msgstr "删除" -#~ msgid "has been deleted." -#~ msgstr "已被删除" - #~ msgid "User assets" #~ msgstr "用户资产" From fea76178eef8e8c2ec4d2ce7f1fb1b1d80f4a1fa Mon Sep 17 00:00:00 2001 From: Administrator Date: Tue, 22 Nov 2016 21:40:05 +0800 Subject: [PATCH 24/42] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BD=9C=E4=B8=9A?= =?UTF-8?q?=E4=B8=AD=E5=BF=83i18n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.mo | Bin 8864 -> 8637 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 58a2c50f2f9ad396716d7dc03399e41e13b34a23..a04d6205c35ada85a66a4caa386d99ae71f675d5 100644 GIT binary patch delta 3085 zcmXw*32aqG6o#j?wiSw{1-d|aytWjIY;9Qt6hR)9MV2D62xTi45U>>}2#7w`hD}AT zHfktQh$tussWcUB07VlbAR41a1Y^>&83QOriJ*S}yN5~t`R2@W=G=2qC;jkJ8nmZRq|0nnOJPw;;pM(s#?_p=yX#I(a z&fSIG1}4EE#FZ<6Eu0Ix2WceGF$UfZi_M;GTnx3rDi{kl8@EBte;u}hyR5&?`j5aI z`cFUwa1}yH8j<6hRUKP~5-NyHz-tGYyR!Tmi(FE4RPVgw~0WU$NIE}~D##vB~x=p-aIcNS{n z-;94i`MC-8O89Df0o(l4PL zorQXUD^LMlhy1x{PNdeQ!XYpR>g>$3{z|C8UJ27sB-K!kc0p~l*X$3VQdMjG6y{)` zhH3By)GKJ-+Fzdp6<`|Fx?CuKz0B?h^@<9i=7o!B=pE01ifA5GK#Psbtv?JE;073k zFF`pt0F{ZOHvSbH#dqR6cpP>}<%+_~M62~{+xXlBxea0WG7SZ=1Lnea{Q>ti)PnP7 zH$u((9ctrSW+#wWU8@vhwlU8*z&HZR-$b*g!F1jK`8KfLxCIit^UU4@mEr?X8+~Xz z1{Kh0sDOTe+TfCnU$yaDkaOr_`MhdXP{m|xRHh&UWQt5&3GHCzbPk3{Yg+y5P(WyuJ!k|{z9|IKm}H8 zb_tZ9a@Ykfh4Qo0XV|@O6KY`;11r&UXc zeMIWWYot|VeFNdhJB~^)#-PQBv*yZCF)BkkP-~IOOq7qFl0nfST!(UyKEr%#A|=Bp zOh^6DBs3K*K`M`+u2TMQCBhVT&0rf@tTo)jf zMQ94@C4=&Wk4W8}I*9n`a2?QaJ=U`{mZH(90(C@picb1Mq~G*Hq${NVd{l<`h|~k9 z{aQpU+uWw19nWks|6{B8vW!Giq$J@dSuQ`RMpoLM$!$;>&W{evs!P7jUtN>ZoP zoJy_0bKAG0yy$kB5zk(ByGyYRbst~Ye%MP2yqM5XzrCUEjfT2?jYrmcI|9`;gWGRw Z;{B4@-OI=-_7(7R)Q9(&C#0BJ{;FhQ$xZ##73PM_$T4EsA1rbqjx32r1DW8r~n&@Qe zh*@FoWYaiiHCc^Ln^XBmh}t-2GmX|vQ_t_-*T#4Feb0H{v%Tk>?>m>;VO1l1KSzb^ zbg)e*78Qp$&INeBF%Rr?xZ_+4a-1`8FWlP9aT*ct_C$bbFam!741-yaCe8%d1r}O- zD{P2g2_xY?$dEqgeG1J9)WW9l3Ty(en_p-A18RbQVJHlvlNbrrz7>2Dwzqg6iw}ZH z#5181D1i-NIgDU_XE6o-ob^1kvfYq5oGSC*gIeKf^FM`c@xOvf_%|2@A3|2-gtI&` z25O}pcqs97;}EERSumXWorx6qa|(HAMGK)4cm;AA&TCMaSHSjgH`IXVpxRx6YWFSF z)?bH8;5O_C|AcB6OO|Ta&6omxI_BOKv?YUKFdPOu!I7{BEP>kMx1a_%3|Xvm465Hp zPzinlmDpwTuR<;82dEvq0TbXos01Thu>Wch*CH@*BGeIdfl4F=D#0}4K;uZL0dpaX zbMl}PC^asGN_08Ycq^gC*$B1u6;S>6wP63%@m&JF;0dd^1zX}jfZbpOZvp;H>1~oti z^nWf;iA{u^VS&XrL$%*+{ywO7)lmIUnt#dazc&8lC+C010uPKKT!R`g5^CaBkldUk ziw`!Aglvv8*8FKuM^FUSuf(_vDxnQf2~|Lie?ay6{*PFLlaMdN`B)Y3DpZFX=KpT| z$Kqjefq~mVwNEsr7zY`%peCGX{#5gep-&ZM6!cv#wFY~PN1*CYnqO=D9BSp?nSU3m z-QQ3v4Qdx)I8;0u>T`~TU12;N1;@1G{FQlyHQWca1Mfg>7EiJU1yBRdg4+7!P#v~deWkI=;)l#XVg5O& z&+W3sufg8<*R4Lv6Bs`pD&bTg1x+voYO9K%GMi^y1{Gfm{fR&&SZRJWoQZ!1>KN#^_VC}?1>1?EFdumo~RIa{EPV!zcNglb=HJY(@o=6`AY z5h{^-sGYoT@gRP|)IS1J?{nH(APFkdRP*~n{|ew!#Ir5F7;50P#@C_RRl*?XLE2g^ zVkzRD@mrp;KYRvpBF;GG|KEmD8jjjnWC5IpCZJT*6Uj8W%!>;ArNHB9umSaYES5r9 z+pS+qncfX8Qum3z0RQ~URlpXa6)IpQXbGB)^3mhg2}eh=66r^#6zL_Cl?UK|YTL8S z*ABgc`XF7~eNh+Wo5iCL$9z)6`^Hl zFp_cGJJZonG%v7zN@l@u6Vj=0m-y{vV^YQxrXYS!oOP%N8iF2~ zZcf=Cv`7( z$`;L?Us&@+#O0unQt$jZ?&juQYtA+=3UYfy`5NVT%c-7Sb1Z6ma42oP2S<#~+XXclA3cTJDPocNiTkb9F?jDa`7T&X8a!R@acsiRcuXWOm2fFb=!B>9bZ>pwX$Ygo9dv@n|oK^ zuH5FP$98rLV(UZecO1UC;gp*jx7EE9wS}l1+;y<-!Y+4sLSEClQy<(qaI|hi)$Pi+-Teu%{{d`>PiFuC From 32a5aec34e35b73226180c14655a65b00ddcf6cf Mon Sep 17 00:00:00 2001 From: Administrator Date: Tue, 22 Nov 2016 23:02:12 +0800 Subject: [PATCH 25/42] =?UTF-8?q?[future]=20=E8=B0=83=E6=95=B4app=E6=9E=B6?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/models/__init__.py | 3 + apps/ops/{models.py => models/ansible.py} | 208 ----------- apps/ops/models/cron.py | 32 ++ apps/ops/models/sudo.py | 187 ++++++++++ apps/ops/tasks.py | 4 +- apps/ops/templates/cron/_cron.html | 97 ++++++ apps/ops/templates/cron/create.html | 26 +- apps/ops/templates/cron/detail.html | 401 +++++++++++++++++++++- apps/ops/templates/cron/update.html | 30 +- apps/ops/templates/sudo/_sudo.html | 97 ++++++ apps/ops/templates/sudo/create.html | 26 +- apps/ops/templates/sudo/detail.html | 401 +++++++++++++++++++++- apps/ops/templates/sudo/update.html | 30 +- apps/ops/tests/__init__.py | 0 apps/ops/{ => tests}/tests.py | 0 apps/ops/utils/__init__.py | 0 apps/ops/{ => utils}/ansible_api.py | 2 +- apps/ops/{utils.py => utils/mixins.py} | 5 +- apps/ops/views.py | 2 +- 19 files changed, 1277 insertions(+), 274 deletions(-) create mode 100644 apps/ops/models/__init__.py rename apps/ops/{models.py => models/ansible.py} (50%) create mode 100644 apps/ops/models/cron.py create mode 100644 apps/ops/models/sudo.py create mode 100644 apps/ops/templates/cron/_cron.html create mode 100644 apps/ops/templates/sudo/_sudo.html create mode 100644 apps/ops/tests/__init__.py rename apps/ops/{ => tests}/tests.py (100%) create mode 100644 apps/ops/utils/__init__.py rename apps/ops/{ => utils}/ansible_api.py (99%) rename apps/ops/{utils.py => utils/mixins.py} (92%) diff --git a/apps/ops/models/__init__.py b/apps/ops/models/__init__.py new file mode 100644 index 000000000..c54d2d467 --- /dev/null +++ b/apps/ops/models/__init__.py @@ -0,0 +1,3 @@ +from ansible import Tasker, AnsiblePlay, AnsibleTask, AnsibleHostResult +from sudo import HostAlia, UserAlia, CmdAlia, RunasAlia, Privilege, Extra_conf, Sudo +from cron import CronTable diff --git a/apps/ops/models.py b/apps/ops/models/ansible.py similarity index 50% rename from apps/ops/models.py rename to apps/ops/models/ansible.py index 4d2346f37..f71d115a8 100644 --- a/apps/ops/models.py +++ b/apps/ops/models/ansible.py @@ -4,9 +4,7 @@ from __future__ import unicode_literals, absolute_import import logging import json -from jinja2 import Template from django.db import models -from assets.models import Asset from django.utils.translation import ugettext_lazy as _ @@ -213,212 +211,6 @@ class AnsibleHostResult(models.Model): return {"msg": "deal with ping data failed, %s" % e.message, "data": None} -class HostAlia(models.Model): - name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Host_Alias')) - host_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) - - def __unicode__(self): - return self.name - - -class UserAlia(models.Model): - name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('User_Alias')) - user_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) - - def __unicode__(self): - return self.name - - -class CmdAlia(models.Model): - name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Command_Alias')) - cmd_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) - - def __unicode__(self): - return self.name - - -class RunasAlia(models.Model): - name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Runas_Alias')) - runas_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) - - def __unicode__(self): - return self.name - - -class Privilege(models.Model): - user = models.ForeignKey(UserAlia, blank=True, null=True, related_name='privileges') - host = models.ForeignKey(HostAlia, blank=True, null=True, related_name='privileges') - runas = models.ForeignKey(RunasAlia, blank=True, null=True, related_name='privileges') - command = models.ForeignKey(CmdAlia, blank=True, null=True, related_name='privileges') - nopassword = models.BooleanField(default=True, verbose_name=_('Is_NoPassword')) - - def __unicode__(self): - return "[%s %s %s %s %s]" % (self.user.name, - self.host.name, - self.runas.name, - self.command.name, - self.nopassword) - - def to_tuple(self): - return self.user.name, self.host.name, self.runas.name, self.command.name, self.nopassword - - -class Extra_conf(models.Model): - line = models.TextField(blank=True, null=True, verbose_name=_('Extra_Item')) - - def __unicode__(self): - return self.line - - -class Sudo(models.Model): - """ - Sudo配置文件对象, 用于配置sudo的配置文件 - - :param extra_lines: [, ,...] - :param privileges: [(user, host, runas, command, nopassword),] - """ - - asset = models.ForeignKey(Asset, null=True, blank=True, related_name='sudos') - extra_lines = models.ManyToManyField(Extra_conf, related_name='sudos', blank=True) - privilege_items = models.ManyToManyField(Privilege, related_name='sudos', blank=True) - - @property - def users(self): - return {privilege.user.name: privilege.user.user_items.split(',') for privilege in self.privilege_items.all()} - - @property - def commands(self): - return {privilege.command.name: privilege.command.cmd_items.split(',') for privilege in self.privilege_items.all()} - - @property - def hosts(self): - return {privilege.host.name: privilege.host.host_items.split(',') for privilege in self.privilege_items.all()} - - @property - def runas(self): - return {privilege.runas.name: privilege.runas.runas_items.split(',') for privilege in self.privilege_items.all()} - - @property - def extras(self): - return [extra.line for extra in self.extra_lines.all()] - - @property - def privileges(self): - return [privilege.to_tuple() for privilege in self.privilege_items.all()] - - @property - def content(self): - template = Template(self.__sudoers_jinja2_tmp__) - context = {"User_Alias": self.users, - "Cmnd_Alias": self.commands, - "Host_Alias": self.hosts, - "Runas_Alias": self.runas, - "Extra_Lines": self.extras, - "Privileges": self.privileges} - return template.render(context) - - @property - def __sudoers_jinja2_tmp__(self): - return """# management by JumpServer -# This file MUST be edited with the 'visudo' command as root. -# -# Please consider adding local content in /etc/sudoers.d/ instead of -# directly modifying this file. -# -# See the man page for details on how to write a sudoers file. -# -Defaults env_reset -Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" - -# JumpServer Generate Other Configure is here -{% if Extra_Lines -%} -{% for line in Extra_Lines -%} -{{ line }} -{% endfor %} -{%- endif %} - -# Host alias specification -{% if Host_Alias -%} -{% for flag, items in Host_Alias.iteritems() -%} -Host_Alias {{ flag }} = {{ items|join(', ') }} -{% endfor %} -{%- endif %} - -# User alias specification -{% if User_Alias -%} -{% for flag, items in User_Alias.iteritems() -%} -User_Alias {{ flag }} = {{ items|join(', ') }} -{% endfor %} -{%- endif %} - - -# Cmnd alias specification -{% if Cmnd_Alias -%} -{% for flag, items in Cmnd_Alias.iteritems() -%} -Cmnd_Alias {{ flag }} = {{ items|join(', ') }} -{% endfor %} -{%- endif %} - -# Run as alias specification -{% if Runas_Alias -%} -{% for flag, items in Runas_Alias.iteritems() -%} -Runas_Alias {{ flag }} = {{ items|join(', ') }} -{% endfor %} -{%- endif %} - -# User privilege specification -root ALL=(ALL:ALL) ALL - -# JumpServer Generate User privilege is here. -# Note privileges is a tuple list like [(user, host, runas, command, nopassword),] -{% if Privileges -%} -{% for User_Flag, Host_Flag, Runas_Flag, Command_Flag, NopassWord in Privileges -%} -{% if NopassWord -%} -{{ User_Flag }} {{ Host_Flag }}=({{ Runas_Flag }}) NOPASSWD: {{ Command_Flag }} -{%- else -%} -{{ User_Flag }} {{ Host_Flag }}=({{ Runas_Flag }}) {{ Command_Flag }} -{%- endif %} -{% endfor %} -{%- endif %} - -# Members of the admin group may gain root privileges -%admin ALL=(ALL) ALL - -# Allow members of group sudo to execute any command -%sudo ALL=(ALL:ALL) ALL - -# See sudoers(5) for more information on "#include" directives: - -#includedir /etc/sudoers.d -""" - - -class CronTable(models.Model): - name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Name'), - help_text=_("Description of a crontab entry")) - month = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Month'), - help_text=_("Month of the year the job should run ( 1-12, *, */2, etc )")) - weekday = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('WeekDay'), - help_text=_("Day of the week that the job should run" - " ( 0-6 for Sunday-Saturday, *, etc )")) - day = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Day'), - help_text=_("Day of the month the job should run ( 1-31, *, */2, etc )")) - hour = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hour'), - help_text=_("Hour when the job should run ( 0-23, *, */2, etc )")) - minute = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Minute'), - help_text=_("Minute when the job should run ( 0-59, *, */2, etc )")) - job = models.CharField(max_length=4096, blank=True, null=True, verbose_name=_('Job'), - help_text=_("The command to execute or, if env is set, the value of " - "environment variable. Required if state=present.")) - user = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('User'), - help_text=_("The specific user whose crontab should be modified.")) - asset = models.ForeignKey(Asset, null=True, blank=True, related_name='crontables') - - @property - def describe(self): - return "http://docs.ansible.com/ansible/cron_module.html" - - diff --git a/apps/ops/models/cron.py b/apps/ops/models/cron.py new file mode 100644 index 000000000..634e78be6 --- /dev/null +++ b/apps/ops/models/cron.py @@ -0,0 +1,32 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals, absolute_import + +from django.db import models +from assets.models import Asset +from django.utils.translation import ugettext_lazy as _ + + +class CronTable(models.Model): + name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Name'), + help_text=_("Description of a crontab entry")) + month = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Month'), + help_text=_("Month of the year the job should run ( 1-12, *, */2, etc )")) + weekday = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('WeekDay'), + help_text=_("Day of the week that the job should run" + " ( 0-6 for Sunday-Saturday, *, etc )")) + day = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Day'), + help_text=_("Day of the month the job should run ( 1-31, *, */2, etc )")) + hour = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hour'), + help_text=_("Hour when the job should run ( 0-23, *, */2, etc )")) + minute = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Minute'), + help_text=_("Minute when the job should run ( 0-59, *, */2, etc )")) + job = models.CharField(max_length=4096, blank=True, null=True, verbose_name=_('Job'), + help_text=_("The command to execute or, if env is set, the value of " + "environment variable. Required if state=present.")) + user = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('User'), + help_text=_("The specific user whose crontab should be modified.")) + asset = models.ForeignKey(Asset, null=True, blank=True, related_name='crontables') + + @property + def describe(self): + return "http://docs.ansible.com/ansible/cron_module.html" \ No newline at end of file diff --git a/apps/ops/models/sudo.py b/apps/ops/models/sudo.py new file mode 100644 index 000000000..707d03857 --- /dev/null +++ b/apps/ops/models/sudo.py @@ -0,0 +1,187 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals, absolute_import + +from jinja2 import Template +from django.db import models +from assets.models import Asset +from django.utils.translation import ugettext_lazy as _ + + +class HostAlia(models.Model): + name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Host_Alias')) + host_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) + + def __unicode__(self): + return self.name + + +class UserAlia(models.Model): + name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('User_Alias')) + user_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) + + def __unicode__(self): + return self.name + + +class CmdAlia(models.Model): + name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Command_Alias')) + cmd_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) + + def __unicode__(self): + return self.name + + +class RunasAlia(models.Model): + name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Runas_Alias')) + runas_items = models.TextField(blank=True, null=True, verbose_name=_('Host_Items')) + + def __unicode__(self): + return self.name + + +class Privilege(models.Model): + user = models.ForeignKey(UserAlia, blank=True, null=True, related_name='privileges') + host = models.ForeignKey(HostAlia, blank=True, null=True, related_name='privileges') + runas = models.ForeignKey(RunasAlia, blank=True, null=True, related_name='privileges') + command = models.ForeignKey(CmdAlia, blank=True, null=True, related_name='privileges') + nopassword = models.BooleanField(default=True, verbose_name=_('Is_NoPassword')) + + def __unicode__(self): + return "[%s %s %s %s %s]" % (self.user.name, + self.host.name, + self.runas.name, + self.command.name, + self.nopassword) + + def to_tuple(self): + return self.user.name, self.host.name, self.runas.name, self.command.name, self.nopassword + + +class Extra_conf(models.Model): + line = models.TextField(blank=True, null=True, verbose_name=_('Extra_Item')) + + def __unicode__(self): + return self.line + + +class Sudo(models.Model): + """ + Sudo配置文件对象, 用于配置sudo的配置文件 + + :param extra_lines: [, ,...] + :param privileges: [(user, host, runas, command, nopassword),] + """ + + asset = models.ForeignKey(Asset, null=True, blank=True, related_name='sudos') + extra_lines = models.ManyToManyField(Extra_conf, related_name='sudos', blank=True) + privilege_items = models.ManyToManyField(Privilege, related_name='sudos', blank=True) + + @property + def users(self): + return {privilege.user.name: privilege.user.user_items.split(',') for privilege in self.privilege_items.all()} + + @property + def commands(self): + return {privilege.command.name: privilege.command.cmd_items.split(',') for privilege in self.privilege_items.all()} + + @property + def hosts(self): + return {privilege.host.name: privilege.host.host_items.split(',') for privilege in self.privilege_items.all()} + + @property + def runas(self): + return {privilege.runas.name: privilege.runas.runas_items.split(',') for privilege in self.privilege_items.all()} + + @property + def extras(self): + return [extra.line for extra in self.extra_lines.all()] + + @property + def privileges(self): + return [privilege.to_tuple() for privilege in self.privilege_items.all()] + + @property + def content(self): + template = Template(self.__sudoers_jinja2_tmp__) + context = {"User_Alias": self.users, + "Cmnd_Alias": self.commands, + "Host_Alias": self.hosts, + "Runas_Alias": self.runas, + "Extra_Lines": self.extras, + "Privileges": self.privileges} + return template.render(context) + + @property + def __sudoers_jinja2_tmp__(self): + return """# management by JumpServer +# This file MUST be edited with the 'visudo' command as root. +# +# Please consider adding local content in /etc/sudoers.d/ instead of +# directly modifying this file. +# +# See the man page for details on how to write a sudoers file. +# +Defaults env_reset +Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + +# JumpServer Generate Other Configure is here +{% if Extra_Lines -%} +{% for line in Extra_Lines -%} +{{ line }} +{% endfor %} +{%- endif %} + +# Host alias specification +{% if Host_Alias -%} +{% for flag, items in Host_Alias.iteritems() -%} +Host_Alias {{ flag }} = {{ items|join(', ') }} +{% endfor %} +{%- endif %} + +# User alias specification +{% if User_Alias -%} +{% for flag, items in User_Alias.iteritems() -%} +User_Alias {{ flag }} = {{ items|join(', ') }} +{% endfor %} +{%- endif %} + + +# Cmnd alias specification +{% if Cmnd_Alias -%} +{% for flag, items in Cmnd_Alias.iteritems() -%} +Cmnd_Alias {{ flag }} = {{ items|join(', ') }} +{% endfor %} +{%- endif %} + +# Run as alias specification +{% if Runas_Alias -%} +{% for flag, items in Runas_Alias.iteritems() -%} +Runas_Alias {{ flag }} = {{ items|join(', ') }} +{% endfor %} +{%- endif %} + +# User privilege specification +root ALL=(ALL:ALL) ALL + +# JumpServer Generate User privilege is here. +# Note privileges is a tuple list like [(user, host, runas, command, nopassword),] +{% if Privileges -%} +{% for User_Flag, Host_Flag, Runas_Flag, Command_Flag, NopassWord in Privileges -%} +{% if NopassWord -%} +{{ User_Flag }} {{ Host_Flag }}=({{ Runas_Flag }}) NOPASSWD: {{ Command_Flag }} +{%- else -%} +{{ User_Flag }} {{ Host_Flag }}=({{ Runas_Flag }}) {{ Command_Flag }} +{%- endif %} +{% endfor %} +{%- endif %} + +# Members of the admin group may gain root privileges +%admin ALL=(ALL) ALL + +# Allow members of group sudo to execute any command +%sudo ALL=(ALL:ALL) ALL + +# See sudoers(5) for more information on "#include" directives: + +#includedir /etc/sudoers.d +""" \ No newline at end of file diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index 21b1470ba..13fe8d0c1 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -1,9 +1,9 @@ from __future__ import absolute_import, unicode_literals from celery import shared_task -from common import celery_app -from ops.ansible_api import Config, ADHocRunner +from common import celery_app +from ops.utils.ansible_api import Config, ADHocRunner @shared_task(name="get_asset_hardware_info") diff --git a/apps/ops/templates/cron/_cron.html b/apps/ops/templates/cron/_cron.html new file mode 100644 index 000000000..59d03e5ee --- /dev/null +++ b/apps/ops/templates/cron/_cron.html @@ -0,0 +1,97 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load static %} +{% load bootstrap %} +{% block custom_head_css_js %} + + + +{% endblock %} + +{% block content %} +
    +
    +
    +
    +
    +
    {% block user_template_title %}{% trans 'Create user' %}{% endblock %}
    + +
    +
    +
    + {% csrf_token %} +

    {% trans 'Account' %}

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

    {% trans 'Security and Role' %}

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

    {% trans 'Profile' %}

    + {{ form.phone|bootstrap_horizontal }} + {{ form.wechat|bootstrap_horizontal }} + {{ form.comment|bootstrap_horizontal }} +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +{% endblock %} +{% block custom_foot_js %} + + +{% endblock %} diff --git a/apps/ops/templates/cron/create.html b/apps/ops/templates/cron/create.html index 566549bdf..291a5656a 100644 --- a/apps/ops/templates/cron/create.html +++ b/apps/ops/templates/cron/create.html @@ -1,10 +1,16 @@ - - - - - Title - - - - - \ No newline at end of file +{% extends 'cron/_cron.html' %} +{% load i18n %} +{% load bootstrap %} +{% block user_template_title %}{% trans "Create user" %}{% endblock %} +{% block username %} + {{ form.username|bootstrap_horizontal }} +{% endblock %} +{% block password %} +

    {% trans 'Password' %}

    +
    + +
    + {% trans 'Reset link will be generated and sent to the user. ' %} +
    +
    +{% endblock %} \ No newline at end of file diff --git a/apps/ops/templates/cron/detail.html b/apps/ops/templates/cron/detail.html index 566549bdf..b87e17a39 100644 --- a/apps/ops/templates/cron/detail.html +++ b/apps/ops/templates/cron/detail.html @@ -1,10 +1,393 @@ - - - - - Title - - +{% extends 'base.html' %} +{% load common_tags %} +{% load users_tags %} +{% load static %} +{% load i18n %} - - \ No newline at end of file +{% block custom_head_css_js %} + + + + +{% endblock %} +{% block content %} +
    +
    +
    +
    + +
    +
    +
    +
    + {{ user_object.name }} +
    + + + + + + + + + + +
    +
    +
    + + + + + + + + + + + + + + + + + + {% if user_object.phone %} + + + + + {% endif %} + {% if user_object.wechat %} + + + + + {% endif %} + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    {% trans 'Name' %}:{{ user_object.name }}
    {% trans 'Username' %}:{{ user_object.username }}
    {% trans 'Email' %}:{{ user_object.email }}
    {% trans 'Phone' %}:{{ user_object.phone }}
    {% trans 'Wechat' %}:{{ user_object.wechat }}
    {% trans 'Role' %}:{{ user_object.get_role_display }}
    {% trans 'Date expired' %}:{{ user_object.date_expired|date:"Y-m-j H:i:s" }}
    {% trans 'Created by' %}:{{ user_object.created_by }}
    {% trans 'Date joined' %}:{{ user_object.date_joined|date:"Y-m-j H:i:s" }}
    {% trans 'Last login' %}:{{ user_object.last_login|date:"Y-m-j H:i:s" }}
    {% trans 'Comment' %}:{{ user_object.comment }}
    +
    +
    +
    +
    +
    +
    + {% trans 'Quick modify' %} +
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    {% trans 'Active' %}: +
    +
    + + +
    +
    +
    {% trans 'Enable OTP' %}: +
    +
    + + +
    +
    +
    {% trans 'Reset password' %}: + + + +
    {% trans 'Reset ssh key' %}: + + + +
    {% trans 'Update ssh key' %}: + + + +
    +
    +
    + +
    +
    + {% trans 'User group' %} +
    +
    + + + + + + + + + + + + {% for group in user_object.groups.all %} + + + + + {% endfor %} + +
    + +
    + +
    {{ group.name }} + +
    +
    +
    +
    +
    +
    +
    +
    +
    + {% include 'users/_user_update_pk_modal.html' %} +{% endblock %} +{% block custom_foot_js %} + +{% endblock %} diff --git a/apps/ops/templates/cron/update.html b/apps/ops/templates/cron/update.html index 566549bdf..f033e1ed8 100644 --- a/apps/ops/templates/cron/update.html +++ b/apps/ops/templates/cron/update.html @@ -1,10 +1,20 @@ - - - - - Title - - - - - \ No newline at end of file +{% extends 'cron/_cron.html' %} +{% load i18n %} +{% block user_template_title %}{% trans "Update user" %}{% endblock %} +{% block username %} +
    + +
    + +
    +
    +{% endblock %} +{% block password %} +

    {% trans 'Password' %}

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

    {% trans 'Account' %}

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

    {% trans 'Security and Role' %}

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

    {% trans 'Profile' %}

    + {{ form.phone|bootstrap_horizontal }} + {{ form.wechat|bootstrap_horizontal }} + {{ form.comment|bootstrap_horizontal }} +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +{% endblock %} +{% block custom_foot_js %} + + +{% endblock %} diff --git a/apps/ops/templates/sudo/create.html b/apps/ops/templates/sudo/create.html index 566549bdf..19727e102 100644 --- a/apps/ops/templates/sudo/create.html +++ b/apps/ops/templates/sudo/create.html @@ -1,10 +1,16 @@ - - - - - Title - - - - - \ No newline at end of file +{% extends 'sudo/_sudo.html' %} +{% load i18n %} +{% load bootstrap %} +{% block user_template_title %}{% trans "Create user" %}{% endblock %} +{% block username %} + {{ form.username|bootstrap_horizontal }} +{% endblock %} +{% block password %} +

    {% trans 'Password' %}

    +
    + +
    + {% trans 'Reset link will be generated and sent to the user. ' %} +
    +
    +{% endblock %} \ No newline at end of file diff --git a/apps/ops/templates/sudo/detail.html b/apps/ops/templates/sudo/detail.html index 566549bdf..b87e17a39 100644 --- a/apps/ops/templates/sudo/detail.html +++ b/apps/ops/templates/sudo/detail.html @@ -1,10 +1,393 @@ - - - - - Title - - +{% extends 'base.html' %} +{% load common_tags %} +{% load users_tags %} +{% load static %} +{% load i18n %} - - \ No newline at end of file +{% block custom_head_css_js %} + + + + +{% endblock %} +{% block content %} +
    +
    +
    +
    + +
    +
    +
    +
    + {{ user_object.name }} +
    + + + + + + + + + + +
    +
    +
    + + + + + + + + + + + + + + + + + + {% if user_object.phone %} + + + + + {% endif %} + {% if user_object.wechat %} + + + + + {% endif %} + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    {% trans 'Name' %}:{{ user_object.name }}
    {% trans 'Username' %}:{{ user_object.username }}
    {% trans 'Email' %}:{{ user_object.email }}
    {% trans 'Phone' %}:{{ user_object.phone }}
    {% trans 'Wechat' %}:{{ user_object.wechat }}
    {% trans 'Role' %}:{{ user_object.get_role_display }}
    {% trans 'Date expired' %}:{{ user_object.date_expired|date:"Y-m-j H:i:s" }}
    {% trans 'Created by' %}:{{ user_object.created_by }}
    {% trans 'Date joined' %}:{{ user_object.date_joined|date:"Y-m-j H:i:s" }}
    {% trans 'Last login' %}:{{ user_object.last_login|date:"Y-m-j H:i:s" }}
    {% trans 'Comment' %}:{{ user_object.comment }}
    +
    +
    +
    +
    +
    +
    + {% trans 'Quick modify' %} +
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    {% trans 'Active' %}: +
    +
    + + +
    +
    +
    {% trans 'Enable OTP' %}: +
    +
    + + +
    +
    +
    {% trans 'Reset password' %}: + + + +
    {% trans 'Reset ssh key' %}: + + + +
    {% trans 'Update ssh key' %}: + + + +
    +
    +
    + +
    +
    + {% trans 'User group' %} +
    +
    + + + + + + + + + + + + {% for group in user_object.groups.all %} + + + + + {% endfor %} + +
    + +
    + +
    {{ group.name }} + +
    +
    +
    +
    +
    +
    +
    +
    +
    + {% include 'users/_user_update_pk_modal.html' %} +{% endblock %} +{% block custom_foot_js %} + +{% endblock %} diff --git a/apps/ops/templates/sudo/update.html b/apps/ops/templates/sudo/update.html index 566549bdf..3172d23f4 100644 --- a/apps/ops/templates/sudo/update.html +++ b/apps/ops/templates/sudo/update.html @@ -1,10 +1,20 @@ - - - - - Title - - - - - \ No newline at end of file +{% extends 'sudo/_sudo.html' %} +{% load i18n %} +{% block user_template_title %}{% trans "Update user" %}{% endblock %} +{% block username %} +
    + +
    + +
    +
    +{% endblock %} +{% block password %} +

    {% trans 'Password' %}

    +
    + +
    + +
    +
    +{% endblock %} diff --git a/apps/ops/tests/__init__.py b/apps/ops/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/ops/tests.py b/apps/ops/tests/tests.py similarity index 100% rename from apps/ops/tests.py rename to apps/ops/tests/tests.py diff --git a/apps/ops/utils/__init__.py b/apps/ops/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/ops/ansible_api.py b/apps/ops/utils/ansible_api.py similarity index 99% rename from apps/ops/ansible_api.py rename to apps/ops/utils/ansible_api.py index 48a313767..1ecc707ed 100644 --- a/apps/ops/ansible_api.py +++ b/apps/ops/utils/ansible_api.py @@ -18,7 +18,7 @@ from ansible.utils.display import Display from ansible.playbook.play import Play from ansible.plugins.callback import CallbackBase -from models import Tasker, AnsiblePlay, AnsibleTask, AnsibleHostResult +from ops.models import Tasker, AnsiblePlay, AnsibleTask, AnsibleHostResult logger = logging.getLogger(__name__) diff --git a/apps/ops/utils.py b/apps/ops/utils/mixins.py similarity index 92% rename from apps/ops/utils.py rename to apps/ops/utils/mixins.py index 9cb69df23..9f925a420 100644 --- a/apps/ops/utils.py +++ b/apps/ops/utils/mixins.py @@ -10,7 +10,4 @@ class CreateSudoPrivilegesMixin(object): class ListSudoPrivilegesMixin(object): def get_all_privilege(self): - pass - - - + pass \ No newline at end of file diff --git a/apps/ops/views.py b/apps/ops/views.py index 4b312dd2c..5423b4942 100644 --- a/apps/ops/views.py +++ b/apps/ops/views.py @@ -7,7 +7,7 @@ from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.views.generic.detail import DetailView, SingleObjectMixin from .hands import AdminUserRequiredMixin -from .utils import CreateSudoPrivilegesMixin, ListSudoPrivilegesMixin +from .utils.mixins import CreateSudoPrivilegesMixin, ListSudoPrivilegesMixin from models import * From cd22c39078ac389af002454884167ae3bc38c1f3 Mon Sep 17 00:00:00 2001 From: Administrator Date: Wed, 23 Nov 2016 11:45:50 +0800 Subject: [PATCH 26/42] =?UTF-8?q?[future]=20=E5=B0=86Task=E7=A7=BB?= =?UTF-8?q?=E5=88=B0=E4=B8=80=E4=B8=AA=E5=8C=85=E5=86=85=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/hands.py | 14 -------------- apps/ops/tasks/__init__.py | 0 apps/ops/{tasks.py => tasks/_celery_tasks.py} | 0 apps/ops/{ => tasks}/taskers.py | 8 ++++---- apps/ops/views.py | 2 +- 5 files changed, 5 insertions(+), 19 deletions(-) delete mode 100644 apps/ops/hands.py create mode 100644 apps/ops/tasks/__init__.py rename apps/ops/{tasks.py => tasks/_celery_tasks.py} (100%) rename apps/ops/{ => tasks}/taskers.py (95%) diff --git a/apps/ops/hands.py b/apps/ops/hands.py deleted file mode 100644 index 1cdbd13a2..000000000 --- a/apps/ops/hands.py +++ /dev/null @@ -1,14 +0,0 @@ -""" - jumpserver.__app__.hands.py - ~~~~~~~~~~~~~~~~~ - - This app depends other apps api, function .. should be import or write mack here. - - Other module of this app shouldn't connect with other app. - - :copyright: (c) 2014-2016 by Jumpserver Team. - :license: GPL v2, see LICENSE for more details. -""" - - -from users.utils import AdminUserRequiredMixin \ No newline at end of file diff --git a/apps/ops/tasks/__init__.py b/apps/ops/tasks/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/ops/tasks.py b/apps/ops/tasks/_celery_tasks.py similarity index 100% rename from apps/ops/tasks.py rename to apps/ops/tasks/_celery_tasks.py diff --git a/apps/ops/taskers.py b/apps/ops/tasks/taskers.py similarity index 95% rename from apps/ops/taskers.py rename to apps/ops/tasks/taskers.py index 814a202ea..d7e24dbff 100644 --- a/apps/ops/taskers.py +++ b/apps/ops/tasks/taskers.py @@ -1,9 +1,9 @@ # ~*~ coding: utf-8 ~*~ from __future__ import unicode_literals -from .tasks import * +from ops.tasks import _celery_tasks -from .models import Tasker +from ops.models import Tasker from uuid import uuid1 from celery.result import AsyncResult @@ -52,7 +52,7 @@ def __get_result_by_tasker_id(tasker_uuid, deal_method): def start_get_hardware_info(*assets): name = "Get host hardware information" uuid = "tasker-" + uuid1().hex - get_asset_hardware_info.delay(name, uuid, *assets) + _celery_tasks.get_asset_hardware_info.delay(name, uuid, *assets) return uuid @@ -90,7 +90,7 @@ def get_hardware_info(tasker_uuid): def start_ping_test(*assets): name = "Test host connection" uuid = "tasker-" + uuid1().hex - asset_test_ping_check.delay(name, uuid, *assets) + _celery_tasks.asset_test_ping_check.delay(name, uuid, *assets) return uuid diff --git a/apps/ops/views.py b/apps/ops/views.py index 5423b4942..9730098f5 100644 --- a/apps/ops/views.py +++ b/apps/ops/views.py @@ -6,7 +6,7 @@ from django.views.generic.list import ListView, MultipleObjectMixin from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.views.generic.detail import DetailView, SingleObjectMixin -from .hands import AdminUserRequiredMixin +from users.utils import AdminUserRequiredMixin from .utils.mixins import CreateSudoPrivilegesMixin, ListSudoPrivilegesMixin from models import * From 7c4aefd959649f3dcbb5d023f53a4bf379bf8c5d Mon Sep 17 00:00:00 2001 From: Administrator Date: Thu, 24 Nov 2016 17:10:43 +0800 Subject: [PATCH 27/42] =?UTF-8?q?=E5=85=BC=E5=AE=B9py3=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/utils/ansible_api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/ops/utils/ansible_api.py b/apps/ops/utils/ansible_api.py index 1ecc707ed..d3cc7edfe 100644 --- a/apps/ops/utils/ansible_api.py +++ b/apps/ops/utils/ansible_api.py @@ -1,5 +1,5 @@ # ~*~ coding: utf-8 ~*~ -from __future__ import unicode_literals +from __future__ import unicode_literals, print_function import os import json @@ -304,7 +304,7 @@ class CallbackModule(CallbackBase): self.output['plays'] = self.results self.output['stats'] = summary - print "summary: %s" % summary + print("summary: %s", summary) class PlayBookRunner(InventoryMixin): @@ -513,8 +513,8 @@ def test_run(): hoc = ADHocRunner(conf, play_source, *assets) uuid = "tasker-" + uuid4().hex ext_code, result = hoc.run("test_task", uuid) - print ext_code - print result + print(ext_code) + print(result) if __name__ == "__main__": From 1bc88e5b116b7f742ebaee8463a0f40dc7d38282 Mon Sep 17 00:00:00 2001 From: Administrator Date: Thu, 24 Nov 2016 18:22:36 +0800 Subject: [PATCH 28/42] Merge branch 'ansible_api' into ops_dev # Conflicts: # apps/jumpserver/urls.py # apps/locale/zh/LC_MESSAGES/django.po # apps/templates/_nav.html # requirements.txt # run_server.py --- apps/jumpserver/api_router.py | 12 ++++++++++++ apps/jumpserver/urls.py | 14 +++++++++----- apps/ops/urls.py | 17 +---------------- run_server.py | 2 +- 4 files changed, 23 insertions(+), 22 deletions(-) create mode 100644 apps/jumpserver/api_router.py diff --git a/apps/jumpserver/api_router.py b/apps/jumpserver/api_router.py new file mode 100644 index 000000000..c1fe2e087 --- /dev/null +++ b/apps/jumpserver/api_router.py @@ -0,0 +1,12 @@ +from rest_framework.routers import DefaultRouter +from ops.api import views as ops_api_view + +router = DefaultRouter() +router.register(r'host_alia', ops_api_view.HostAliaViewSet) +router.register(r'user_alia', ops_api_view.UserAliaViewSet) +router.register(r'cmd_alia', ops_api_view.CmdAliaViewSet) +router.register(r'runas_alia', ops_api_view.RunasAliaViewSet) +router.register(r'extra_conf', ops_api_view.ExtraconfViewSet) +router.register(r'privilege', ops_api_view.PrivilegeViewSet) +router.register(r'sudo', ops_api_view.SudoViewSet) +router.register(r'cron', ops_api_view.CronTableViewSet) \ No newline at end of file diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index 846844096..041372aca 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -1,3 +1,6 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals + """jumpserver URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: @@ -17,12 +20,12 @@ from django.conf.urls import url, include from django.conf import settings from django.conf.urls.static import static from django.views.generic.base import TemplateView +from jumpserver.api_router import router urlpatterns = [ url(r'^captcha/', include('captcha.urls')), url(r'^$', TemplateView.as_view(template_name='base.html'), name='index'), - url(r'^users/', include('users.urls.views_urls', namespace='users')), url(r'^assets/', include('assets.urls.views_urls', namespace='assets')), url(r'^perms/', include('perms.urls.views_urls', namespace='perms')), @@ -33,13 +36,14 @@ urlpatterns = [ url(r'^api/perms/', include('perms.urls.api_urls', namespace='api-perms')), url(r'^api/audits/', include('audits.urls.api_urls', namespace='api-audits')), url(r'^api/terminal/', include('terminal.urls.api_urls', namespace='api-terminal')), - url(r'^(api/)?users/', include('users.urls')), - url(r'^assets/', include('assets.urls')), - url(r'^perms/', include('perms.urls')), - url(r'^(api/)?ops/', include('ops.urls')), + ] +urlpatterns += [ + url(r'^api/v1/ops', include(router.urls)), +] + if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/apps/ops/urls.py b/apps/ops/urls.py index 2735b1202..830d6b27a 100644 --- a/apps/ops/urls.py +++ b/apps/ops/urls.py @@ -1,25 +1,13 @@ # ~*~ coding: utf-8 ~*~ from __future__ import unicode_literals -from rest_framework.routers import DefaultRouter -from django.conf.urls import url, include - -from api import views as api_view +from django.conf.urls import url import views as page_view app_name = 'ops' -router = DefaultRouter() -router.register(r'host_alia', api_view.HostAliaViewSet) -router.register(r'user_alia', api_view.UserAliaViewSet) -router.register(r'cmd_alia', api_view.CmdAliaViewSet) -router.register(r'runas_alia', api_view.RunasAliaViewSet) -router.register(r'extra_conf', api_view.ExtraconfViewSet) -router.register(r'privilege', api_view.PrivilegeViewSet) -router.register(r'sudo', api_view.SudoViewSet) -router.register(r'cron', api_view.CronTableViewSet) urlpatterns = [ # Resource Sudo url @@ -35,8 +23,5 @@ urlpatterns = [ url(r'^cron/update$', page_view.CronUpdateView.as_view(), name='page-cron-update'), ] -urlpatterns += [ - url(r'^v1/sudo', include(router.urls)), -] diff --git a/run_server.py b/run_server.py index d5a987306..c3d3c9c9e 100644 --- a/run_server.py +++ b/run_server.py @@ -30,7 +30,7 @@ def start_celery(): os.chdir(apps_dir) os.environ.setdefault('C_FORCE_ROOT', '1') print('start celery') - subprocess.call('celery -A common worker -P eventlet -B -s /tmp/celerybeat-schedule -l info ', shell=True) + subprocess.call('celery -A common worker -P eventlet -s /tmp/celerybeat-schedule -l info ', shell=True) def main(): From 984391e2b27e46fe3225d21ce6425b7d02c71c95 Mon Sep 17 00:00:00 2001 From: Administrator Date: Thu, 24 Nov 2016 22:10:04 +0800 Subject: [PATCH 29/42] =?UTF-8?q?=E5=90=8C=E6=AD=A5Master=E5=88=86?= =?UTF-8?q?=E6=94=AF=E4=B8=8A=E7=9A=84=E4=BB=A3=E7=A0=81,=20=E8=A7=A3?= =?UTF-8?q?=E5=86=B3=E9=83=A8=E5=88=86=E5=86=B2=E7=AA=81=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/urls.py | 1 + apps/ops/templates/cron/list.html | 84 ++++++++++++++++--------------- apps/ops/templates/sudo/list.html | 84 ++++++++++++++++--------------- apps/ops/urls/__init__.py | 1 - apps/ops/urls/api_urls.py | 7 --- apps/ops/urls/views_urls.py | 8 --- apps/templates/_nav.html | 8 +-- 7 files changed, 91 insertions(+), 102 deletions(-) delete mode 100644 apps/ops/urls/__init__.py delete mode 100644 apps/ops/urls/api_urls.py delete mode 100644 apps/ops/urls/views_urls.py diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index 041372aca..5023462f6 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -42,6 +42,7 @@ urlpatterns = [ urlpatterns += [ url(r'^api/v1/ops', include(router.urls)), + url(r'^ops/', include('ops.urls')), ] if settings.DEBUG: diff --git a/apps/ops/templates/cron/list.html b/apps/ops/templates/cron/list.html index b08a44b53..5168bed8d 100644 --- a/apps/ops/templates/cron/list.html +++ b/apps/ops/templates/cron/list.html @@ -1,34 +1,31 @@ {% extends '_base_list.html' %} {% load i18n static %} -{% block custom_head_css_js %} -{{ block.super }} - +{% block table_search %} + {% endblock %} -{% block table_search %}{% endblock %} {% block table_container %} - + - - + + - - + @@ -65,34 +62,43 @@ $(document).ready(function(){ $(td).html(detail_btn.replace('99991937', rowData.id)); }}, {targets: 4, createdCell: function (td, cellData) { - var innerHtml = cellData.length > 8 ? cellData.substring(0, 8) + '...': cellData; - $(td).html('' + innerHtml + ''); + var innerHtml = cellData.length > 20 ? cellData.substring(0, 20) + '...': cellData; + $(td).html('' + innerHtml + ''); }}, - {targets: 6, createdCell: function (td, cellData) { + {targets: 5, createdCell: function (td, cellData) { if (!cellData) { $(td).html('') } else { $(td).html('') } }}, - {targets: 7, createdCell: function (td, cellData, rowData) { + {targets: 6, createdCell: function (td, cellData, rowData) { var update_btn = '{% trans "Update" %}'.replace('99991937', cellData); var del_btn = '{% trans "Delete" %}'.replace('99991937', cellData); - if (rowData.id === 1) { + if (rowData.id === 1 || rowData.username == "admin") { $(td).html(update_btn) } else { $(td).html(update_btn + del_btn) } }}], - ajax_url: '{% url "users:user-bulk-update-api" %}', - columns: [{data: function(){return ""}}, {data: "username" }, {data: "name" }, {data: "get_role_display" }, {data: "group_display" }, - {data: function(){return 999}}, {data: "active_display" }, {data: "id" }], + ajax_url: '{% url "api-users:user-list" %}', + columns: [{data: "id"}, {data: "username" }, {data: "name" }, {data: "get_role_display" }, + {data: "groups_display" }, {data: "is_valid" }, {data: "id" }], op_html: $('#actions').html() }; - jumpserver.initDataTable(options); + var table = jumpserver.initDataTable(options); + + $('.buttons-pdf').click(function () { + var users = []; + var rows = table.rows('.selected').data(); + $.each(rows, function (index, obj) { + users.push(obj.id) + }) + }); + }).on('click', '#btn_bulk_update', function(){ var action = $('#slct_bulk_update').val(); - var $data_table = $('#user_list_table').DataTable() + var $data_table = $('#user_list_table').DataTable(); var id_list = []; var plain_id_list = []; $data_table.rows({selected: true}).every(function(){ @@ -101,8 +107,8 @@ $(document).ready(function(){ }); if (id_list === []) { return false; - }; - var the_url = "{% url 'users:user-bulk-update-api' %}"; + } + var the_url = "{% url 'api-users:user-list' %}"; function doDeactive() { var body = $.each(id_list, function(index, user_object) { user_object['is_active'] = false; @@ -155,7 +161,7 @@ $(document).ready(function(){ var $this = $(this); function doDelete() { var uid = $this.data('uid'); - var the_url = '{% url "users:user-patch-api" pk=99991937 %}'.replace('99991937', uid); + var the_url = '{% url "api-users:user-detail" pk=99991937 %}'.replace('99991937', uid); var body = {}; var success = function() { var msg = "{% trans 'User Deleted.' %}"; @@ -165,7 +171,7 @@ $(document).ready(function(){ var fail = function() { var msg = "{% trans 'User Deleting failed.' %}"; swal("{% trans 'User Delete' %}", msg, "error"); - } + }; APIUpdateAttr({ url: the_url, body: JSON.stringify(body), @@ -208,15 +214,15 @@ $(document).ready(function(){ post_list.push(content); }); if (post_list === []) { - return false; - }; - var the_url = "{% url 'users:user-bulk-update-api' %}"; + return false + } + var the_url = "{% url 'api-users:user-list' %}"; var success = function() { var msg = "{% trans 'The selected users has been updated successfully.' %}"; swal("{% trans 'User Updated' %}", msg, "success"); $('#user_list_table').DataTable().ajax.reload(); jumpserver.checked = false; - } + }; APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success}); $('#user_bulk_update_modal').modal('hide'); }).on('click', '#btn_user_import', function() { @@ -229,13 +235,11 @@ $(document).ready(function(){ } else { $('#user_import_modal').modal('hide'); var $data_table = $('#user_list_table').DataTable(); - toastr.success("{% trans 'Import User Success.' %}") + toastr.success("{% trans 'Import User Success.' %}"); $data_table.ajax.reload(); } } $form.ajaxSubmit({success: success}); -}).on('change', '#id_excel', function() { - $(this).siblings('.help-block').remove(); }) {% endblock %} diff --git a/apps/ops/templates/sudo/list.html b/apps/ops/templates/sudo/list.html index b08a44b53..5168bed8d 100644 --- a/apps/ops/templates/sudo/list.html +++ b/apps/ops/templates/sudo/list.html @@ -1,34 +1,31 @@ {% extends '_base_list.html' %} {% load i18n static %} -{% block custom_head_css_js %} -{{ block.super }} - +{% block table_search %} + {% endblock %} -{% block table_search %}{% endblock %} {% block table_container %} - +
    -
    +{#
    #} +
    {% trans 'Name' %}{% trans 'Username' %}{% trans 'Name' %}{% trans 'Username' %} {% trans 'Role' %} {% trans 'User group' %}{% trans 'Asset num' %}{% trans 'Active' %}{% trans 'Active' %} {% trans 'Action' %}
    - - + + - - + @@ -65,34 +62,43 @@ $(document).ready(function(){ $(td).html(detail_btn.replace('99991937', rowData.id)); }}, {targets: 4, createdCell: function (td, cellData) { - var innerHtml = cellData.length > 8 ? cellData.substring(0, 8) + '...': cellData; - $(td).html('' + innerHtml + ''); + var innerHtml = cellData.length > 20 ? cellData.substring(0, 20) + '...': cellData; + $(td).html('' + innerHtml + ''); }}, - {targets: 6, createdCell: function (td, cellData) { + {targets: 5, createdCell: function (td, cellData) { if (!cellData) { $(td).html('') } else { $(td).html('') } }}, - {targets: 7, createdCell: function (td, cellData, rowData) { + {targets: 6, createdCell: function (td, cellData, rowData) { var update_btn = '{% trans "Update" %}'.replace('99991937', cellData); var del_btn = '{% trans "Delete" %}'.replace('99991937', cellData); - if (rowData.id === 1) { + if (rowData.id === 1 || rowData.username == "admin") { $(td).html(update_btn) } else { $(td).html(update_btn + del_btn) } }}], - ajax_url: '{% url "users:user-bulk-update-api" %}', - columns: [{data: function(){return ""}}, {data: "username" }, {data: "name" }, {data: "get_role_display" }, {data: "group_display" }, - {data: function(){return 999}}, {data: "active_display" }, {data: "id" }], + ajax_url: '{% url "api-users:user-list" %}', + columns: [{data: "id"}, {data: "username" }, {data: "name" }, {data: "get_role_display" }, + {data: "groups_display" }, {data: "is_valid" }, {data: "id" }], op_html: $('#actions').html() }; - jumpserver.initDataTable(options); + var table = jumpserver.initDataTable(options); + + $('.buttons-pdf').click(function () { + var users = []; + var rows = table.rows('.selected').data(); + $.each(rows, function (index, obj) { + users.push(obj.id) + }) + }); + }).on('click', '#btn_bulk_update', function(){ var action = $('#slct_bulk_update').val(); - var $data_table = $('#user_list_table').DataTable() + var $data_table = $('#user_list_table').DataTable(); var id_list = []; var plain_id_list = []; $data_table.rows({selected: true}).every(function(){ @@ -101,8 +107,8 @@ $(document).ready(function(){ }); if (id_list === []) { return false; - }; - var the_url = "{% url 'users:user-bulk-update-api' %}"; + } + var the_url = "{% url 'api-users:user-list' %}"; function doDeactive() { var body = $.each(id_list, function(index, user_object) { user_object['is_active'] = false; @@ -155,7 +161,7 @@ $(document).ready(function(){ var $this = $(this); function doDelete() { var uid = $this.data('uid'); - var the_url = '{% url "users:user-patch-api" pk=99991937 %}'.replace('99991937', uid); + var the_url = '{% url "api-users:user-detail" pk=99991937 %}'.replace('99991937', uid); var body = {}; var success = function() { var msg = "{% trans 'User Deleted.' %}"; @@ -165,7 +171,7 @@ $(document).ready(function(){ var fail = function() { var msg = "{% trans 'User Deleting failed.' %}"; swal("{% trans 'User Delete' %}", msg, "error"); - } + }; APIUpdateAttr({ url: the_url, body: JSON.stringify(body), @@ -208,15 +214,15 @@ $(document).ready(function(){ post_list.push(content); }); if (post_list === []) { - return false; - }; - var the_url = "{% url 'users:user-bulk-update-api' %}"; + return false + } + var the_url = "{% url 'api-users:user-list' %}"; var success = function() { var msg = "{% trans 'The selected users has been updated successfully.' %}"; swal("{% trans 'User Updated' %}", msg, "success"); $('#user_list_table').DataTable().ajax.reload(); jumpserver.checked = false; - } + }; APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success}); $('#user_bulk_update_modal').modal('hide'); }).on('click', '#btn_user_import', function() { @@ -229,13 +235,11 @@ $(document).ready(function(){ } else { $('#user_import_modal').modal('hide'); var $data_table = $('#user_list_table').DataTable(); - toastr.success("{% trans 'Import User Success.' %}") + toastr.success("{% trans 'Import User Success.' %}"); $data_table.ajax.reload(); } } $form.ajaxSubmit({success: success}); -}).on('change', '#id_excel', function() { - $(this).siblings('.help-block').remove(); }) {% endblock %} diff --git a/apps/ops/urls/__init__.py b/apps/ops/urls/__init__.py deleted file mode 100644 index 8b1378917..000000000 --- a/apps/ops/urls/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/apps/ops/urls/api_urls.py b/apps/ops/urls/api_urls.py deleted file mode 100644 index d0c6b5204..000000000 --- a/apps/ops/urls/api_urls.py +++ /dev/null @@ -1,7 +0,0 @@ -# coding:utf-8 - -from django.conf.urls import url -from rest_framework import routers - - - diff --git a/apps/ops/urls/views_urls.py b/apps/ops/urls/views_urls.py deleted file mode 100644 index a29072e4f..000000000 --- a/apps/ops/urls/views_urls.py +++ /dev/null @@ -1,8 +0,0 @@ -# coding:utf-8 - -from django.conf.urls import url -from .. import views - -app_name = 'ops' - - diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 4be5f8706..917a988ce 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -38,7 +38,8 @@
  • {% trans 'Terminal' %} - +   +
  • @@ -50,11 +51,6 @@
  • -
  • - - {% trans 'Audits' %} - -
  • {% trans 'Audits' %}
  • -
    +{#
    #} +
    {% trans 'Name' %}{% trans 'Username' %}{% trans 'Name' %}{% trans 'Username' %} {% trans 'Role' %} {% trans 'User group' %}{% trans 'Asset num' %}{% trans 'Active' %}{% trans 'Active' %} {% trans 'Action' %}
    diff --git a/apps/ops/templates/sudo/list.html b/apps/ops/templates/sudo/list.html index 5168bed8d..f408d8c71 100644 --- a/apps/ops/templates/sudo/list.html +++ b/apps/ops/templates/sudo/list.html @@ -1,15 +1,6 @@ {% extends '_base_list.html' %} {% load i18n static %} {% block table_search %} - {% endblock %} {% block table_container %} diff --git a/apps/ops/urls.py b/apps/ops/urls.py deleted file mode 100644 index 830d6b27a..000000000 --- a/apps/ops/urls.py +++ /dev/null @@ -1,27 +0,0 @@ -# ~*~ coding: utf-8 ~*~ -from __future__ import unicode_literals - - -from django.conf.urls import url -import views as page_view - - -app_name = 'ops' - - -urlpatterns = [ - # Resource Sudo url - url(r'^sudo/list$', page_view.SudoListView.as_view(), name='page-sudo-list'), - url(r'^sudo/create$', page_view.SudoCreateView.as_view(), name='page-sudo-create'), - url(r'^sudo/detail$', page_view.SudoDetailView.as_view(), name='page-sudo-detail'), - url(r'^sudo/update$', page_view.SudoUpdateView.as_view(), name='page-sudo-update'), - - # Resource Cron url - url(r'^cron/list$', page_view.CronListView.as_view(), name='page-cron-list'), - url(r'^cron/create$', page_view.CronCreateView.as_view(), name='page-cron-create'), - url(r'^cron/detail$', page_view.CronDetailView.as_view(), name='page-cron-detail'), - url(r'^cron/update$', page_view.CronUpdateView.as_view(), name='page-cron-update'), -] - - - From baba65ad439b8d9319f48c99eade92a3276e6dbe Mon Sep 17 00:00:00 2001 From: yumaojun03 <719118794@qq.com> Date: Mon, 5 Dec 2016 23:02:08 +0800 Subject: [PATCH 34/42] =?UTF-8?q?=E4=BF=AE=E6=94=B9url=E6=A8=A1=E5=9D=97,?= =?UTF-8?q?=20=E5=8C=B9=E9=85=8D=E6=95=B4=E4=BD=93=E6=9E=B6=E6=9E=84?= =?UTF-8?q?=E9=A3=8E=E6=A0=BC.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/urls.py | 2 +- apps/ops/urls/__init__.py | 0 apps/ops/urls/api_urls.py | 14 ++++++++++++++ apps/ops/urls/view_urls.py | 24 ++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 apps/ops/urls/__init__.py create mode 100644 apps/ops/urls/api_urls.py create mode 100644 apps/ops/urls/view_urls.py diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index fe4760b16..f7db52a50 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -30,7 +30,7 @@ urlpatterns = [ url(r'^perms/', include('perms.urls.views_urls', namespace='perms')), url(r'^audits/', include('audits.urls.views_urls', namespace='audits')), url(r'^terminal/', include('terminal.urls.views_urls', namespace='terminal')), - url('^ops/', include('ops.urls.view_urls'), name='ops'), + url('^ops/', include('ops.urls.view_urls', namespace='ops')), url(r'^api/users/', include('users.urls.api_urls', namespace='api-users')), url(r'^api/assets/', include('assets.urls.api_urls', namespace='api-assets')), diff --git a/apps/ops/urls/__init__.py b/apps/ops/urls/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/ops/urls/api_urls.py b/apps/ops/urls/api_urls.py new file mode 100644 index 000000000..aa885df9e --- /dev/null +++ b/apps/ops/urls/api_urls.py @@ -0,0 +1,14 @@ +from rest_framework.routers import DefaultRouter +from ops.api import views as ops_api_view + +api_router = DefaultRouter() +api_router.register(r'host_alia', ops_api_view.HostAliaViewSet) +api_router.register(r'user_alia', ops_api_view.UserAliaViewSet) +api_router.register(r'cmd_alia', ops_api_view.CmdAliaViewSet) +api_router.register(r'runas_alia', ops_api_view.RunasAliaViewSet) +api_router.register(r'extra_conf', ops_api_view.ExtraconfViewSet) +api_router.register(r'privilege', ops_api_view.PrivilegeViewSet) +api_router.register(r'sudo', ops_api_view.SudoViewSet) +api_router.register(r'cron', ops_api_view.CronTableViewSet) + +urlpatterns = api_router.urls \ No newline at end of file diff --git a/apps/ops/urls/view_urls.py b/apps/ops/urls/view_urls.py new file mode 100644 index 000000000..5dde31ed3 --- /dev/null +++ b/apps/ops/urls/view_urls.py @@ -0,0 +1,24 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals + + +from django.conf.urls import url +from ops import views as page_view + + +app_name = 'ops' + + +urlpatterns = [ + # Resource Sudo url + url(r'^sudo/list$', page_view.SudoListView.as_view(), name='page-sudo-list'), + url(r'^sudo/create$', page_view.SudoCreateView.as_view(), name='page-sudo-create'), + url(r'^sudo/detail$', page_view.SudoDetailView.as_view(), name='page-sudo-detail'), + url(r'^sudo/update$', page_view.SudoUpdateView.as_view(), name='page-sudo-update'), + + # Resource Cron url + url(r'^cron/list$', page_view.CronListView.as_view(), name='page-cron-list'), + url(r'^cron/create$', page_view.CronCreateView.as_view(), name='page-cron-create'), + url(r'^cron/detail$', page_view.CronDetailView.as_view(), name='page-cron-detail'), + url(r'^cron/update$', page_view.CronUpdateView.as_view(), name='page-cron-update'), +] \ No newline at end of file From 84613e51d8d4ec4548171c18cb475f92cfc0e12d Mon Sep 17 00:00:00 2001 From: yumaojun03 <719118794@qq.com> Date: Sun, 11 Dec 2016 12:05:11 +0800 Subject: [PATCH 35/42] =?UTF-8?q?=E4=B8=BA=E4=BA=86=E9=98=B2=E6=AD=A2?= =?UTF-8?q?=E5=BE=AA=E7=8E=AF=E5=AF=BC=E5=85=A5=EF=BC=8C=E9=87=87=E7=94=A8?= =?UTF-8?q?=5F=5Fall=5F=5F=E5=AF=BC=E5=87=BA=E6=A8=A1=E5=9D=97=E5=8F=98?= =?UTF-8?q?=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/api/__init__.py | 1 + apps/ops/api/views.py | 10 ++++++++++ apps/ops/models/__init__.py | 6 +++--- apps/ops/models/ansible.py | 2 ++ apps/ops/models/cron.py | 2 ++ apps/ops/models/sudo.py | 3 +++ apps/ops/tasks/__init__.py | 1 + apps/ops/tasks/taskers.py | 6 ++++++ apps/ops/urls/api_urls.py | 23 ++++++++++++++--------- apps/ops/urls/view_urls.py | 4 +--- apps/ops/views.py | 4 ++-- 11 files changed, 45 insertions(+), 17 deletions(-) diff --git a/apps/ops/api/__init__.py b/apps/ops/api/__init__.py index e69de29bb..c8b15abe0 100644 --- a/apps/ops/api/__init__.py +++ b/apps/ops/api/__init__.py @@ -0,0 +1 @@ +from views import * \ No newline at end of file diff --git a/apps/ops/api/views.py b/apps/ops/api/views.py index 69cb7ca54..bc5e40617 100644 --- a/apps/ops/api/views.py +++ b/apps/ops/api/views.py @@ -5,6 +5,16 @@ from rest_framework import viewsets from serializers import * from permissions import * +__all__ = ["HostAliaViewSet", + "CmdAliaViewSet", + "UserAliaViewSet", + "RunasAliaViewSet", + "ExtraconfViewSet", + "PrivilegeViewSet", + "SudoViewSet", + "CronTableViewSet", + ] + class HostAliaViewSet(viewsets.ModelViewSet): queryset = HostAlia.objects.all() diff --git a/apps/ops/models/__init__.py b/apps/ops/models/__init__.py index c54d2d467..248828548 100644 --- a/apps/ops/models/__init__.py +++ b/apps/ops/models/__init__.py @@ -1,3 +1,3 @@ -from ansible import Tasker, AnsiblePlay, AnsibleTask, AnsibleHostResult -from sudo import HostAlia, UserAlia, CmdAlia, RunasAlia, Privilege, Extra_conf, Sudo -from cron import CronTable +from ansible import * +from cron import * +from sudo import * diff --git a/apps/ops/models/ansible.py b/apps/ops/models/ansible.py index f71d115a8..c3043cd50 100644 --- a/apps/ops/models/ansible.py +++ b/apps/ops/models/ansible.py @@ -7,6 +7,8 @@ import json from django.db import models from django.utils.translation import ugettext_lazy as _ +__all__ = ["Tasker", "AnsiblePlay", "AnsibleTask", "AnsibleHostResult"] + logger = logging.getLogger(__name__) diff --git a/apps/ops/models/cron.py b/apps/ops/models/cron.py index 634e78be6..1cfbacedb 100644 --- a/apps/ops/models/cron.py +++ b/apps/ops/models/cron.py @@ -5,6 +5,8 @@ from django.db import models from assets.models import Asset from django.utils.translation import ugettext_lazy as _ +__all__ = ["CronTable"] + class CronTable(models.Model): name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Name'), diff --git a/apps/ops/models/sudo.py b/apps/ops/models/sudo.py index 5c6aae9d8..af5368a26 100644 --- a/apps/ops/models/sudo.py +++ b/apps/ops/models/sudo.py @@ -6,6 +6,9 @@ from django.db import models from assets.models import Asset, AssetGroup from django.utils.translation import ugettext_lazy as _ +__all__ = ["HostAlia", "UserAlia", "CmdAlia", "RunasAlia", "Privilege", + "Extra_conf", "Sudo"] + class HostAlia(models.Model): name = models.CharField(max_length=128, blank=True, null=True, unique=True, verbose_name=_('Host_Alias')) diff --git a/apps/ops/tasks/__init__.py b/apps/ops/tasks/__init__.py index e69de29bb..6fe598964 100644 --- a/apps/ops/tasks/__init__.py +++ b/apps/ops/tasks/__init__.py @@ -0,0 +1 @@ +from taskers import * \ No newline at end of file diff --git a/apps/ops/tasks/taskers.py b/apps/ops/tasks/taskers.py index d7e24dbff..07815b993 100644 --- a/apps/ops/tasks/taskers.py +++ b/apps/ops/tasks/taskers.py @@ -7,6 +7,12 @@ from ops.models import Tasker from uuid import uuid1 from celery.result import AsyncResult +__all__ = ["get_result", + "start_get_hardware_info", + "start_ping_test", + "get_hardware_info", + "get_ping_test"] + def get_result(task_id): result = AsyncResult(task_id) diff --git a/apps/ops/urls/api_urls.py b/apps/ops/urls/api_urls.py index aa885df9e..dd621ecb6 100644 --- a/apps/ops/urls/api_urls.py +++ b/apps/ops/urls/api_urls.py @@ -1,14 +1,19 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals + from rest_framework.routers import DefaultRouter -from ops.api import views as ops_api_view +from ops import api as v1_api + +__all__ = ["urlpatterns"] api_router = DefaultRouter() -api_router.register(r'host_alia', ops_api_view.HostAliaViewSet) -api_router.register(r'user_alia', ops_api_view.UserAliaViewSet) -api_router.register(r'cmd_alia', ops_api_view.CmdAliaViewSet) -api_router.register(r'runas_alia', ops_api_view.RunasAliaViewSet) -api_router.register(r'extra_conf', ops_api_view.ExtraconfViewSet) -api_router.register(r'privilege', ops_api_view.PrivilegeViewSet) -api_router.register(r'sudo', ops_api_view.SudoViewSet) -api_router.register(r'cron', ops_api_view.CronTableViewSet) +api_router.register(r'host_alia', v1_api.HostAliaViewSet) +api_router.register(r'user_alia', v1_api.UserAliaViewSet) +api_router.register(r'cmd_alia', v1_api.CmdAliaViewSet) +api_router.register(r'runas_alia', v1_api.RunasAliaViewSet) +api_router.register(r'extra_conf', v1_api.ExtraconfViewSet) +api_router.register(r'privilege', v1_api.PrivilegeViewSet) +api_router.register(r'sudo', v1_api.SudoViewSet) +api_router.register(r'cron', v1_api.CronTableViewSet) urlpatterns = api_router.urls \ No newline at end of file diff --git a/apps/ops/urls/view_urls.py b/apps/ops/urls/view_urls.py index 5dde31ed3..cc544db17 100644 --- a/apps/ops/urls/view_urls.py +++ b/apps/ops/urls/view_urls.py @@ -5,9 +5,7 @@ from __future__ import unicode_literals from django.conf.urls import url from ops import views as page_view - -app_name = 'ops' - +__all__ = ["urlpatterns"] urlpatterns = [ # Resource Sudo url diff --git a/apps/ops/views.py b/apps/ops/views.py index fed47ba39..27b24b23d 100644 --- a/apps/ops/views.py +++ b/apps/ops/views.py @@ -7,8 +7,8 @@ from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.views.generic.detail import DetailView, SingleObjectMixin from users.utils import AdminUserRequiredMixin -from .utils.mixins import CreateSudoPrivilegesMixin, ListSudoPrivilegesMixin -from models import * +from ops.utils.mixins import CreateSudoPrivilegesMixin, ListSudoPrivilegesMixin +from ops.models import * class SudoListView(AdminUserRequiredMixin, ListSudoPrivilegesMixin, ListView): From 806d38bbb2b0a33dc307dcc95159ab468becfa41 Mon Sep 17 00:00:00 2001 From: yumaojun03 <719118794@qq.com> Date: Sun, 11 Dec 2016 20:50:26 +0800 Subject: [PATCH 36/42] =?UTF-8?q?=E6=A8=A1=E6=8B=9F=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/models/ansible.py | 18 ++++++++++++++++++ apps/ops/models/sudo.py | 5 ++++- apps/ops/models/utils.py | 11 +++++++++++ apps/ops/templates/sudo/list.html | 24 +++++++++++------------- 4 files changed, 44 insertions(+), 14 deletions(-) create mode 100644 apps/ops/models/utils.py diff --git a/apps/ops/models/ansible.py b/apps/ops/models/ansible.py index c3043cd50..052463749 100644 --- a/apps/ops/models/ansible.py +++ b/apps/ops/models/ansible.py @@ -29,6 +29,24 @@ class Tasker(models.Model): def total_hosts(self): return self.hosts.split(',') + @classmethod + def generate_fake(cls, count=100): + from random import seed + import forgery_py + from django.db import IntegrityError + + seed() + for i in range(count): + group = cls(name=forgery_py.name.full_name(), + comment=forgery_py.lorem_ipsum.sentence(), + created_by='Fake') + try: + group.save() + logger.debug('Generate fake asset group: %s' % group.name) + except IntegrityError: + print('Error continue') + continue + class AnsiblePlay(models.Model): tasker = models.ForeignKey(Tasker, related_name='plays', blank=True, null=True) diff --git a/apps/ops/models/sudo.py b/apps/ops/models/sudo.py index af5368a26..48e6b6fc7 100644 --- a/apps/ops/models/sudo.py +++ b/apps/ops/models/sudo.py @@ -78,11 +78,14 @@ class Sudo(models.Model): :param privileges: [(user, host, runas, command, nopassword),] """ + name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'), + help_text=_('Name for this sudo')) + created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'), + help_text=_('The create time of this sudo')) assets = models.ManyToManyField(Asset, blank=True, related_name='sudos') asset_groups = models.ManyToManyField(AssetGroup, blank=True, related_name='sudos') extra_lines = models.ManyToManyField(Extra_conf, related_name='sudos', blank=True) privilege_items = models.ManyToManyField(Privilege, related_name='sudos', blank=True) - created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by')) @property def all_assets(self): diff --git a/apps/ops/models/utils.py b/apps/ops/models/utils.py new file mode 100644 index 000000000..98dd8e65a --- /dev/null +++ b/apps/ops/models/utils.py @@ -0,0 +1,11 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals + +from ansible import * +from cron import * +from sudo import * + + +def generate_fake(): + for cls in (AssetGroup, IDC, AdminUser, SystemUser, Asset): + cls.generate_fake() \ No newline at end of file diff --git a/apps/ops/templates/sudo/list.html b/apps/ops/templates/sudo/list.html index f408d8c71..141fd8710 100644 --- a/apps/ops/templates/sudo/list.html +++ b/apps/ops/templates/sudo/list.html @@ -5,7 +5,7 @@ {% block table_container %} -
    +
    - - - - - + + + @@ -46,7 +44,7 @@ + +{% endblock %} + +{% block content %} +
    +
    +
    +
    +
    +
    {% block user_template_title %}{% trans 'Create user' %}{% endblock %}
    + +
    +
    +
    + {% csrf_token %} +

    {% trans 'Account' %}

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

    {% trans 'Security and Role' %}

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

    {% trans 'Profile' %}

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

    {% trans 'Password' %}

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

    {% trans 'Password' %}

    +
    + +
    + +
    +
    +{% endblock %} From 150e1030c32a76656a072bfc457366fe4fff5b86 Mon Sep 17 00:00:00 2001 From: yumaojun03 <719118794@qq.com> Date: Mon, 19 Dec 2016 14:07:21 +0800 Subject: [PATCH 41/42] =?UTF-8?q?ansible=20Task=E6=8E=A5=E5=8F=A3=E6=9B=B4?= =?UTF-8?q?=E4=B8=8A=E5=B1=82=E6=8A=BD=E8=B1=A1=E7=9A=84=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/api/serializers.py | 8 ++++++- apps/ops/api/views.py | 6 ++++++ apps/ops/models/__init__.py | 1 + apps/ops/models/ansible.py | 20 ++++++++++++------ apps/ops/models/sudo.py | 6 +++--- apps/ops/models/utils.py | 2 +- apps/ops/tasks/taskers.py | 4 ++-- apps/ops/templates/task/list.html | 35 +++++++++++++++---------------- apps/ops/urls/api_urls.py | 1 + apps/ops/urls/view_urls.py | 6 ++++++ apps/ops/utils/ansible_api.py | 8 +++---- apps/ops/views.py | 29 +++++++++++++++++++++++++ apps/templates/_nav.html | 1 + 13 files changed, 92 insertions(+), 35 deletions(-) diff --git a/apps/ops/api/serializers.py b/apps/ops/api/serializers.py index 1cafdf574..3981552e2 100644 --- a/apps/ops/api/serializers.py +++ b/apps/ops/api/serializers.py @@ -1,7 +1,7 @@ # ~*~ coding: utf-8 ~*~ from __future__ import unicode_literals -from ..models import HostAlia, UserAlia, CmdAlia, RunasAlia, Extra_conf, Privilege, Sudo, CronTable +from ops.models import * from rest_framework import serializers @@ -51,3 +51,9 @@ class CronTableSerializer(serializers.ModelSerializer): class Meta: model = CronTable + +class TaskSerializer(serializers.ModelSerializer): + + class Meta: + model = Task + read_only_fields = ('record',) diff --git a/apps/ops/api/views.py b/apps/ops/api/views.py index bc5e40617..4bde44b9f 100644 --- a/apps/ops/api/views.py +++ b/apps/ops/api/views.py @@ -13,6 +13,7 @@ __all__ = ["HostAliaViewSet", "PrivilegeViewSet", "SudoViewSet", "CronTableViewSet", + "TaskViewSet", ] @@ -63,5 +64,10 @@ class CronTableViewSet(viewsets.ModelViewSet): serializer_class = CronTableSerializer permission_classes = (AdminUserRequired,) +class TaskViewSet(viewsets.ModelViewSet): + queryset = Task.objects.all() + serializer_class = TaskSerializer + permission_classes = (AdminUserRequired,) + diff --git a/apps/ops/models/__init__.py b/apps/ops/models/__init__.py index ef134dd20..ef0a6b8af 100644 --- a/apps/ops/models/__init__.py +++ b/apps/ops/models/__init__.py @@ -2,3 +2,4 @@ from ansible import * from cron import * from sudo import * from utils import * + diff --git a/apps/ops/models/ansible.py b/apps/ops/models/ansible.py index 7ebf59476..cebc5251a 100644 --- a/apps/ops/models/ansible.py +++ b/apps/ops/models/ansible.py @@ -9,13 +9,13 @@ from assets.models import Asset from django.db import models from django.utils.translation import ugettext_lazy as _ -__all__ = ["Task", "Tasker", "AnsiblePlay", "AnsibleTask", "AnsibleHostResult"] +__all__ = ["Task", "TaskRecord", "AnsiblePlay", "AnsibleTask", "AnsibleHostResult"] logger = logging.getLogger(__name__) -class Tasker(models.Model): +class TaskRecord(models.Model): uuid = models.CharField(max_length=128, verbose_name=_('UUID'), primary_key=True) name = models.CharField(max_length=128, blank=True, verbose_name=_('Name')) start = models.DateTimeField(auto_now_add=True, verbose_name=_('Start Time')) @@ -51,7 +51,7 @@ class Tasker(models.Model): class AnsiblePlay(models.Model): - tasker = models.ForeignKey(Tasker, related_name='plays', blank=True, null=True) + tasker = models.ForeignKey(TaskRecord, related_name='plays', blank=True, null=True) uuid = models.CharField(max_length=128, verbose_name=_('UUID'), primary_key=True) name = models.CharField(max_length=128, verbose_name=_('Name')) @@ -73,7 +73,7 @@ class AnsiblePlay(models.Model): name=forgery_py.name.full_name(), ) try: - play.tasker = choice(Tasker.objects.all()) + play.tasker = choice(TaskRecord.objects.all()) play.save() logger.debug('Generate fake play: %s' % play.name) except Exception as e: @@ -293,8 +293,16 @@ class AnsibleHostResult(models.Model): continue class Task(models.Model): + record = models.OneToOneField(TaskRecord) name = models.CharField(max_length=128, blank=True, verbose_name=_('Name')) - asset = models.ForeignKey(Asset, null=True, blank=True, related_name='crontables') + module_name = models.CharField(max_length=128, verbose_name=_('Ansible Module Name')) + module_args = models.CharField(max_length=512, blank=True, verbose_name=_("Ansible Module Args")) + register = models.CharField(max_length=128, blank=True, verbose_name=_('Ansible Task Register')) + is_gather_facts = models.BooleanField(default=False,verbose_name=_('Is Gather Ansible Facts')) + asset = models.ManyToManyField(Asset, related_name='tasks') def __unicode__(self): - pass \ No newline at end of file + return "%s %s" % (self.module_name, self.module_args) + + def run(self): + pass diff --git a/apps/ops/models/sudo.py b/apps/ops/models/sudo.py index 5aaba44ab..0c1a582c8 100644 --- a/apps/ops/models/sudo.py +++ b/apps/ops/models/sudo.py @@ -5,7 +5,7 @@ import logging from jinja2 import Template from django.db import models -from django.utils import timezone +from django.utils.timezone import now from assets.models import Asset, AssetGroup from django.utils.translation import ugettext_lazy as _ @@ -174,7 +174,7 @@ class Sudo(models.Model): name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'), help_text=_('Name for this sudo')) - created_time = models.DateTimeField(verbose_name=_('Created Time'), default=timezone.now(), + created_time = models.DateTimeField(verbose_name=_('Created Time'), auto_created=True, help_text=_('The create time of this sudo')) modify_time = models.DateTimeField(auto_now=True, verbose_name=_('Modify Time'), help_text=_('The recent modify time of this sudo')) @@ -310,7 +310,7 @@ root ALL=(ALL:ALL) ALL seed() for i in range(count): sudo = cls(name=forgery_py.name.full_name(), - created_time=timezone.now() + created_time=now() ) try: sudo.save() diff --git a/apps/ops/models/utils.py b/apps/ops/models/utils.py index 1f93e619d..17c7cbab1 100644 --- a/apps/ops/models/utils.py +++ b/apps/ops/models/utils.py @@ -9,6 +9,6 @@ __all__ = ["generate_fake"] def generate_fake(): - for cls in (Tasker, AnsiblePlay, AnsibleTask, AnsibleHostResult, CronTable, + for cls in (TaskRecord, AnsiblePlay, AnsibleTask, AnsibleHostResult, CronTable, HostAlia, UserAlia, CmdAlia, RunasAlia, Privilege, Sudo): cls.generate_fake() \ No newline at end of file diff --git a/apps/ops/tasks/taskers.py b/apps/ops/tasks/taskers.py index 07815b993..3a724b049 100644 --- a/apps/ops/tasks/taskers.py +++ b/apps/ops/tasks/taskers.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals from ops.tasks import _celery_tasks -from ops.models import Tasker +from ops.models import TaskRecord from uuid import uuid1 from celery.result import AsyncResult @@ -23,7 +23,7 @@ def get_result(task_id): def __get_result_by_tasker_id(tasker_uuid, deal_method): - tasker = Tasker.objects.get(uuid=tasker_uuid) + tasker = TaskRecord.objects.get(uuid=tasker_uuid) total = tasker.total_hosts total_len = len(total) host_results = [] diff --git a/apps/ops/templates/task/list.html b/apps/ops/templates/task/list.html index 8da993251..7109e4fe8 100644 --- a/apps/ops/templates/task/list.html +++ b/apps/ops/templates/task/list.html @@ -3,7 +3,7 @@ {% block table_search %} {% endblock %} {% block table_container %} - + {##} @@ -12,8 +12,9 @@ - - + + + @@ -35,8 +36,6 @@ -{#{% include "users/_user_bulk_update_modal.html" %}#} -{#{% include "users/_user_import_modal.html" %}#} {% endblock %} {% block content_bottom_left %}{% endblock %} {% block custom_foot_js %} @@ -47,23 +46,23 @@ $(document).ready(function(){ ele: $('#sudo_list_table'), columnDefs: [ {targets: 1, createdCell: function (td, cellData, rowData) { - var detail_btn = '' + cellData + ''; + var detail_btn = '' + cellData + ''; $(td).html(detail_btn.replace('99991937', rowData.id)); }}, - {targets: 4, createdCell: function (td, cellData, rowData) { - var update_btn = '{% trans "Update" %}'.replace('99991937', cellData); - var preview_btn = '{% trans "Preview" %}'.replace('99991937', cellData); - var job_btn = '{% trans "Job" %}'.replace('99991937', cellData); - var del_btn = '{% trans "Delete" %}'.replace('99991937', cellData); + {targets: 3, createdCell: function (td, cellData) { + if (!cellData) { + $(td).html('') + } else { + $(td).html('') + } + }}, + {targets: 4, createdCell: function (td, cellData) { + var del_btn = '{% trans "Delete" %}'.replace('99991937', cellData) + $(td).html(del_btn) - if (rowData.id === 1 || rowData.username == "admin") { - $(td).html(update_btn) - } else { - $(td).html(preview_btn + job_btn + update_btn + del_btn) - } }}], - ajax_url: '{% url "api-ops:sudo-list" %}', - columns: [{data: "id"}, {data: "name" }, {data: "privilege_items" }, {data: "extra_lines" }, {data: "id" }], + ajax_url: '{% url "api-ops:task-list" %}', + columns: [{data: "name"}, {data: "uuid" }, {data: "start" }, {data: "completed" }, {data: "id" }], op_html: $('#actions').html() }; var table = jumpserver.initDataTable(options); diff --git a/apps/ops/urls/api_urls.py b/apps/ops/urls/api_urls.py index 81a550f66..144d6a53e 100644 --- a/apps/ops/urls/api_urls.py +++ b/apps/ops/urls/api_urls.py @@ -15,5 +15,6 @@ api_router.register(r'v1/extra_conf', v1_api.ExtraconfViewSet) api_router.register(r'v1/privilege', v1_api.PrivilegeViewSet) api_router.register(r'v1/sudo', v1_api.SudoViewSet) api_router.register(r'v1/cron', v1_api.CronTableViewSet) +api_router.register(r'v1/task', v1_api.TaskViewSet) urlpatterns = api_router.urls \ No newline at end of file diff --git a/apps/ops/urls/view_urls.py b/apps/ops/urls/view_urls.py index d43f1a95c..9e9c68253 100644 --- a/apps/ops/urls/view_urls.py +++ b/apps/ops/urls/view_urls.py @@ -19,4 +19,10 @@ urlpatterns = [ url(r'^cron/create$', page_view.CronCreateView.as_view(), name='page-cron-create'), url(r'^cron/(?P[0-9]+)/detail$', page_view.CronDetailView.as_view(), name='page-cron-detail'), url(r'^cron/(?P[0-9]+)/update$', page_view.CronUpdateView.as_view(), name='page-cron-update'), + + # TResource Task url + url(r'^task/list$', page_view.TaskListView.as_view(), name='page-task-list'), + url(r'^task/create$', page_view.TaskCreateView.as_view(), name='page-task-create'), + url(r'^task/(?P[0-9]+)/detail$', page_view.TaskDetailView.as_view(), name='page-task-detail'), + url(r'^task/(?P[0-9]+)/update$', page_view.TaskUpdateView.as_view(), name='page-task-update'), ] \ No newline at end of file diff --git a/apps/ops/utils/ansible_api.py b/apps/ops/utils/ansible_api.py index d3cc7edfe..25ddc91bd 100644 --- a/apps/ops/utils/ansible_api.py +++ b/apps/ops/utils/ansible_api.py @@ -18,7 +18,7 @@ from ansible.utils.display import Display from ansible.playbook.play import Play from ansible.plugins.callback import CallbackBase -from ops.models import Tasker, AnsiblePlay, AnsibleTask, AnsibleHostResult +from ops.models import TaskRecord, AnsiblePlay, AnsibleTask, AnsibleHostResult logger = logging.getLogger(__name__) @@ -196,7 +196,7 @@ class CallbackModule(CallbackBase): } try: - tasker = Tasker.objects.get(uuid=self.tasker_id) + tasker = TaskRecord.objects.get(uuid=self.tasker_id) play = AnsiblePlay(tasker, name=ret['name'], uuid=ret['uuid']) play.save() except Exception as e: @@ -429,7 +429,7 @@ class ADHocRunner(InventoryMixin): @staticmethod def update_db_tasker(tasker_id, ext_code): try: - tasker = Tasker.objects.get(uuid=tasker_id) + tasker = TaskRecord.objects.get(uuid=tasker_id) tasker.end = timezone.now() tasker.completed = True tasker.exit_code = ext_code @@ -440,7 +440,7 @@ class ADHocRunner(InventoryMixin): def create_db_tasker(self, name, uuid): try: hosts = [host.get('name') for host in self.hosts] - tasker = Tasker(name=name, uuid=uuid, hosts=','.join(hosts), start=timezone.now()) + tasker = TaskRecord(name=name, uuid=uuid, hosts=','.join(hosts), start=timezone.now()) tasker.save() except Exception as e: logger.error("Save Tasker to database error!, %s" % e.message) diff --git a/apps/ops/views.py b/apps/ops/views.py index 5678d1d88..cf07aee59 100644 --- a/apps/ops/views.py +++ b/apps/ops/views.py @@ -56,3 +56,32 @@ class CronDetailView(DetailView): context_object_name = 'cron' template_name = 'cron/detail.html' +class TaskListView(AdminUserRequiredMixin, ListView): + paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + model = Task + context_object_name = 'tasks' + template_name = 'task/list.html' + + def get_context_data(self, **kwargs): + context = { + 'task': 'Assets', + 'action': 'Create asset', + } + kwargs.update(context) + return super(TaskListView, self).get_context_data(**kwargs) + + +class TaskCreateView(AdminUserRequiredMixin, CreateView): + model = Task + template_name = 'task/create.html' + + +class TaskUpdateView(AdminUserRequiredMixin, UpdateView): + model = Task + template_name = 'task/update.html' + + +class TaskDetailView(DetailView): + model = Task + context_object_name = 'task' + template_name = 'task/detail.html' diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 01e5427db..0ee86f8a4 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -46,6 +46,7 @@ {% trans 'Job Center' %} From 47d87e38a6995608807217d2572b888d9c6eba49 Mon Sep 17 00:00:00 2001 From: yumaojun03 <719118794@qq.com> Date: Mon, 19 Dec 2016 23:46:03 +0800 Subject: [PATCH 42/42] =?UTF-8?q?=E8=A1=A5=E5=85=85task=E9=9C=80=E8=A6=81?= =?UTF-8?q?=E7=9A=84=E4=B8=80=E4=BA=9B=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/api/serializers.py | 6 ++++ apps/ops/api/views.py | 6 ++++ apps/ops/models/__init__.py | 1 + apps/ops/models/ansible.py | 19 +----------- apps/ops/models/sudo.py | 3 +- apps/ops/models/task.py | 58 +++++++++++++++++++++++++++++++++++ apps/ops/urls/api_urls.py | 1 + apps/ops/utils/ansible_api.py | 7 +++-- 8 files changed, 78 insertions(+), 23 deletions(-) create mode 100644 apps/ops/models/task.py diff --git a/apps/ops/api/serializers.py b/apps/ops/api/serializers.py index 3981552e2..24abe7e2a 100644 --- a/apps/ops/api/serializers.py +++ b/apps/ops/api/serializers.py @@ -53,7 +53,13 @@ class CronTableSerializer(serializers.ModelSerializer): model = CronTable class TaskSerializer(serializers.ModelSerializer): + sub_tasks = serializers.PrimaryKeyRelatedField(many=True, read_only=True) class Meta: model = Task read_only_fields = ('record',) + +class SubTaskSerializer(serializers.ModelSerializer): + + class Meta: + model = SubTask diff --git a/apps/ops/api/views.py b/apps/ops/api/views.py index 4bde44b9f..eaacb2b76 100644 --- a/apps/ops/api/views.py +++ b/apps/ops/api/views.py @@ -14,6 +14,7 @@ __all__ = ["HostAliaViewSet", "SudoViewSet", "CronTableViewSet", "TaskViewSet", + "SubTaskViewSet", ] @@ -69,5 +70,10 @@ class TaskViewSet(viewsets.ModelViewSet): serializer_class = TaskSerializer permission_classes = (AdminUserRequired,) +class SubTaskViewSet(viewsets.ModelViewSet): + queryset = SubTask.objects.all() + serializer_class = SubTaskSerializer + permission_classes = (AdminUserRequired,) + diff --git a/apps/ops/models/__init__.py b/apps/ops/models/__init__.py index ef0a6b8af..b7bfa1e0d 100644 --- a/apps/ops/models/__init__.py +++ b/apps/ops/models/__init__.py @@ -2,4 +2,5 @@ from ansible import * from cron import * from sudo import * from utils import * +from task import * diff --git a/apps/ops/models/ansible.py b/apps/ops/models/ansible.py index cebc5251a..3cf059644 100644 --- a/apps/ops/models/ansible.py +++ b/apps/ops/models/ansible.py @@ -4,12 +4,10 @@ from __future__ import unicode_literals, absolute_import import logging import json -from assets.models import Asset - from django.db import models from django.utils.translation import ugettext_lazy as _ -__all__ = ["Task", "TaskRecord", "AnsiblePlay", "AnsibleTask", "AnsibleHostResult"] +__all__ = ["TaskRecord", "AnsiblePlay", "AnsibleTask", "AnsibleHostResult"] logger = logging.getLogger(__name__) @@ -291,18 +289,3 @@ class AnsibleHostResult(models.Model): except Exception as e: print('Error: %s, continue...' % e.message) continue - -class Task(models.Model): - record = models.OneToOneField(TaskRecord) - name = models.CharField(max_length=128, blank=True, verbose_name=_('Name')) - module_name = models.CharField(max_length=128, verbose_name=_('Ansible Module Name')) - module_args = models.CharField(max_length=512, blank=True, verbose_name=_("Ansible Module Args")) - register = models.CharField(max_length=128, blank=True, verbose_name=_('Ansible Task Register')) - is_gather_facts = models.BooleanField(default=False,verbose_name=_('Is Gather Ansible Facts')) - asset = models.ManyToManyField(Asset, related_name='tasks') - - def __unicode__(self): - return "%s %s" % (self.module_name, self.module_args) - - def run(self): - pass diff --git a/apps/ops/models/sudo.py b/apps/ops/models/sudo.py index 0c1a582c8..13713064c 100644 --- a/apps/ops/models/sudo.py +++ b/apps/ops/models/sudo.py @@ -11,8 +11,7 @@ from django.utils.translation import ugettext_lazy as _ logger = logging.getLogger(__name__) -__all__ = ["HostAlia", "UserAlia", "CmdAlia", "RunasAlia", "Privilege", - "Extra_conf", "Sudo"] +__all__ = ["HostAlia", "UserAlia", "CmdAlia", "RunasAlia", "Privilege", "Extra_conf", "Sudo"] class HostAlia(models.Model): diff --git a/apps/ops/models/task.py b/apps/ops/models/task.py new file mode 100644 index 000000000..fc35e0665 --- /dev/null +++ b/apps/ops/models/task.py @@ -0,0 +1,58 @@ +# ~*~ coding: utf-8 ~*~ +from __future__ import unicode_literals, absolute_import + +import logging + +from uuid import uuid4 +from assets.models import Asset +from ops.models import TaskRecord +from ops.utils.ansible_api import ADHocRunner, Config + +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +__all__ = ["Task", "SubTask"] + + +logger = logging.getLogger(__name__) + + +class Task(models.Model): + record = models.OneToOneField(TaskRecord) + name = models.CharField(max_length=128, blank=True, verbose_name=_('Name')) + is_gather_facts = models.BooleanField(default=False,verbose_name=_('Is Gather Ansible Facts')) + assets = models.ManyToManyField(Asset, related_name='tasks') + + def __unicode__(self): + return "%s" % self.name + + @property + def ansible_assets(self): + return [] + + def run(self): + conf = Config() + gather_facts = "yes" if self.is_gather_facts else "no" + play_source = { + "name": "Ansible Play", + "hosts": "default", + "gather_facts": gather_facts, + "tasks": [ + dict(action=dict(module='ping')), + ] + } + hoc = ADHocRunner(conf, play_source, *self.ansible_assets) + uuid = "tasker-" + uuid4().hex + ext_code, result = hoc.run("test_task", uuid) + print(ext_code) + print(result) + + +class SubTask(models.Model): + task = models.ForeignKey(Task, related_name='sub_tasks', verbose_name=_('Ansible Task')) + module_name = models.CharField(max_length=128, verbose_name=_('Ansible Module Name')) + module_args = models.CharField(max_length=512, blank=True, verbose_name=_("Ansible Module Args")) + register = models.CharField(max_length=128, blank=True, verbose_name=_('Ansible Task Register')) + + def __unicode__(self): + return "%s %s" % (self.module_name, self.module_args) \ No newline at end of file diff --git a/apps/ops/urls/api_urls.py b/apps/ops/urls/api_urls.py index 144d6a53e..77141ed35 100644 --- a/apps/ops/urls/api_urls.py +++ b/apps/ops/urls/api_urls.py @@ -16,5 +16,6 @@ api_router.register(r'v1/privilege', v1_api.PrivilegeViewSet) api_router.register(r'v1/sudo', v1_api.SudoViewSet) api_router.register(r'v1/cron', v1_api.CronTableViewSet) api_router.register(r'v1/task', v1_api.TaskViewSet) +api_router.register(r'v1/subtask', v1_api.SubTaskViewSet) urlpatterns = api_router.urls \ No newline at end of file diff --git a/apps/ops/utils/ansible_api.py b/apps/ops/utils/ansible_api.py index 25ddc91bd..3f3345c5f 100644 --- a/apps/ops/utils/ansible_api.py +++ b/apps/ops/utils/ansible_api.py @@ -20,6 +20,8 @@ from ansible.plugins.callback import CallbackBase from ops.models import TaskRecord, AnsiblePlay, AnsibleTask, AnsibleHostResult +__all__ = ["ADHocRunner", "Config"] + logger = logging.getLogger(__name__) @@ -392,7 +394,7 @@ class PlayBookRunner(InventoryMixin): class ADHocRunner(InventoryMixin): """ADHoc接口 """ - def __init__(self, config, play_data, *hosts, **group_vars): + def __init__(self, play_data, config=None, *hosts, **group_vars): """ :param hosts: 见PlaybookRunner参数 :param group_vars: 见PlaybookRunner参数 @@ -406,8 +408,7 @@ class ADHocRunner(InventoryMixin): tasks=[dict(action=dict(module='service', args={'name': 'vsftpd', 'state': 'restarted'}), async=async, poll=poll)] ) """ - - self.options = config + self.options = config if config != None else Config() # 设置verbosity级别, 及命令行的--verbose选项 self.display = Display()
    {% trans 'Name' %}{% trans 'Privileges' %}{% trans 'Extra Lines' %}{% trans 'UUID' %}{% trans 'Start' %}{% trans 'Completed' %} {% trans 'Action' %}