From d32f070b5ca0810625a1c04ab78d43f01e12cc03 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 28 Mar 2018 19:37:29 +0800 Subject: [PATCH 1/8] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9Inverntoy?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=8A=A0=E6=9B=B4=E5=A4=9A=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/asset.py | 12 ++++- apps/ops/ansible/inventory.py | 42 +++++++++++------- apps/ops/inventory.py | 84 +++++++++++++++++++++++------------ 3 files changed, 93 insertions(+), 45 deletions(-) diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index b8c1f768e..a5e578760 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -96,7 +96,7 @@ class Asset(models.Model): return False, warning def is_unixlike(self): - if self.platform not in ("Windows", "Other"): + if self.platform not in ("Windows",): return True else: return False @@ -132,6 +132,15 @@ class Asset(models.Model): info["gateways"] = [d.id for d in self.domain.gateway_set.all()] return info + def get_auth_info(self): + if self.admin_user: + return { + 'username': self.admin_user.username, + 'password': self.admin_user.password, + 'private_key': self.admin_user.private_key_file, + 'become': self.admin_user.become_info, + } + def _to_secret_json(self): """ Ansible use it create inventory, First using asset user, @@ -175,4 +184,3 @@ class Asset(models.Model): except IntegrityError: print('Error continue') continue - diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index c8224c8c8..e2f318f71 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -29,7 +29,6 @@ class BaseHost(Host): } "groups": [], "vars": {}, - "other_ansbile_vars": } """ self.host_data = host_data @@ -79,7 +78,7 @@ class BaseInventory(InventoryManager): variable_manager_class = VariableManager host_manager_class = BaseHost - def __init__(self, host_list=None): + def __init__(self, host_list=None, group_list=None): """ 用于生成动态构建Ansible Inventory. super().__init__ 会自动调用 host_list: [{ @@ -98,11 +97,14 @@ class BaseInventory(InventoryManager): "vars": {}, }, ] + group_list: [ + {"name: "", children: [""]}, + ] :param host_list: + :param group_list """ - if host_list is None: - host_list = [] - self.host_list = host_list + self.host_list = host_list or [] + self.group_list = group_list or [] assert isinstance(host_list, list) self.loader = self.loader_class() self.variable_manager = self.variable_manager_class() @@ -114,24 +116,34 @@ class BaseInventory(InventoryManager): def get_group(self, name): return self._inventory.groups.get(name, None) - def parse_sources(self, cache=False): - group_all = self.get_group('all') - ungrouped = self.get_group('ungrouped') + def get_or_create_group(self, name): + group = self.get_group(name) + if not group: + self.add_group(name) + return self.get_or_create_group(name) + else: + return group + def parse_groups(self): + for g in self.group_list: + parent = self.get_or_create_group(g.get("name")) + children = [self.get_or_create_group(n) for n in g.get('children', [])] + for child in children: + parent.add_child_group(child) + + def parse_hosts(self): for host_data in self.host_list: host = self.host_manager_class(host_data=host_data) self.hosts[host_data['hostname']] = host groups_data = host_data.get('groups') if groups_data: for group_name in groups_data: - group = self.get_group(group_name) - if group is None: - self.add_group(group_name) - group = self.get_group(group_name) + group = self.get_or_create_group(group_name) group.add_host(host) - else: - ungrouped.add_host(host) - group_all.add_host(host) + + def parse_sources(self, cache=False): + self.parse_groups() + self.parse_hosts() def get_matched_hosts(self, pattern): return self.get_hosts(pattern) diff --git a/apps/ops/inventory.py b/apps/ops/inventory.py index 6e3d6325c..6230e8167 100644 --- a/apps/ops/inventory.py +++ b/apps/ops/inventory.py @@ -9,29 +9,18 @@ __all__ = [ ] -def make_proxy_command(asset): - gateway = asset.domain.random_gateway() - - proxy_command = [ - "ssh", "-p", str(gateway.port), - "{}@{}".format(gateway.username, gateway.ip), - "-W", "%h:%p", "-q", - ] - - if gateway.password: - proxy_command.insert(0, "sshpass -p {}".format(gateway.password)) - if gateway.private_key: - proxy_command.append("-i {}".format(gateway.private_key_file)) - - return {"ansible_ssh_common_args": "'-o ProxyCommand={}'".format(" ".join(proxy_command))} - - class JMSInventory(BaseInventory): """ JMS Inventory is the manager with jumpserver assets, so you can write you own manager, construct you inventory """ def __init__(self, hostname_list, run_as_admin=False, run_as=None, become_info=None): + """ + :param hostname_list: ["test1", ] + :param run_as_admin: True 是否使用管理用户去执行, 每台服务器的管理用户可能不同 + :param run_as: 是否统一使用某个系统用户去执行 + :param become_info: 是否become成某个用户去执行 + """ self.hostname_list = hostname_list self.using_admin = run_as_admin self.run_as = run_as @@ -41,23 +30,14 @@ class JMSInventory(BaseInventory): host_list = [] for asset in assets: - vars = {} - if run_as_admin: - info = asset._to_secret_json() - else: - info = asset.to_json() - - info["vars"] = vars - if asset.domain and asset.domain.has_gateway(): - vars.update(make_proxy_command(asset)) - info.update(vars) - + info = self.convert_to_ansible(asset, run_as_admin=run_as_admin) host_list.append(info) if run_as: run_user_info = self.get_run_user_info() for host in host_list: host.update(run_user_info) + if become_info: for host in host_list: host.update(become_info) @@ -67,9 +47,57 @@ class JMSInventory(BaseInventory): assets = get_assets_by_hostname_list(self.hostname_list) return assets + def convert_to_ansible(self, asset, run_as_admin=False): + info = { + 'id': asset.id, + 'hostname': asset.hostname, + 'ip': asset.ip, + 'port': asset.port, + 'vars': dict(), + 'groups': [], + } + if asset.domain and asset.domain.has_gateway(): + info["vars"].update(self.make_proxy_command(asset)) + if run_as_admin: + info.update(asset.get_auth_info()) + for node in asset.nodes.all(): + info["groups"].append(node.value) + for label in asset.labels.all(): + info["vars"].update({ + label.name: label.value + }) + info["groups"].append("{}:{}".format(label.name, label.value)) + if asset.domain: + info["vars"].update({ + "domain": asset.domain.name, + }) + info["groups"].append("domain_"+asset.domain.name) + return info + def get_run_user_info(self): system_user = get_system_user_by_name(self.run_as) if not system_user: return {} else: return system_user._to_secret_json() + + @staticmethod + def make_proxy_command(asset): + gateway = asset.domain.random_gateway() + proxy_command_list = [ + "ssh", "-p", str(gateway.port), + "{}@{}".format(gateway.username, gateway.ip), + "-W", "%h:%p", "-q", + ] + + if gateway.password: + proxy_command_list.insert( + 0, "sshpass -p {}".format(gateway.password) + ) + if gateway.private_key: + proxy_command_list.append("-i {}".format(gateway.private_key_file)) + + proxy_command = "'-o ProxyCommand={}'".format( + " ".join(proxy_command_list) + ) + return {"ansible_ssh_common_args": proxy_command} From 09fc2776df4dfda5aee42b6ffb86f709397bdbd7 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 30 Mar 2018 22:03:43 +0800 Subject: [PATCH 2/8] [Update] Support history view --- apps/ops/ansible/callback.py | 8 +- apps/ops/ansible/display.py | 19 ++++ apps/ops/ansible/inventory.py | 5 + apps/ops/ansible/runner.py | 37 +++++--- apps/ops/api.py | 28 +++++- apps/ops/models.py | 38 +++++--- .../templates/ops/adhoc_history_output.html | 93 +++++++++++++++++++ apps/ops/urls/api_urls.py | 1 + apps/ops/urls/view_urls.py | 1 + apps/ops/views.py | 13 +++ 10 files changed, 219 insertions(+), 24 deletions(-) create mode 100644 apps/ops/ansible/display.py create mode 100644 apps/ops/templates/ops/adhoc_history_output.html diff --git a/apps/ops/ansible/callback.py b/apps/ops/ansible/callback.py index 810b14c51..0af872b25 100644 --- a/apps/ops/ansible/callback.py +++ b/apps/ops/ansible/callback.py @@ -1,14 +1,18 @@ # ~*~ coding: utf-8 ~*~ +import sys + from ansible.plugins.callback import CallbackBase from ansible.plugins.callback.default import CallbackModule +from .display import TeeObj + class AdHocResultCallback(CallbackModule): """ Task result Callback """ - def __init__(self, display=None, options=None): + def __init__(self, display=None, options=None, file_obj=None): # result_raw example: { # "ok": {"hostname": {"task_name": {},...},..}, # "failed": {"hostname": {"task_name": {}..}, ..}, @@ -22,6 +26,8 @@ class AdHocResultCallback(CallbackModule): self.results_raw = dict(ok={}, failed={}, unreachable={}, skipped={}) self.results_summary = dict(contacted=[], dark={}) super().__init__() + if file_obj is not None: + sys.stdout = TeeObj(file_obj) def gather_result(self, t, res): self._clean_results(res._result, res._task.action) diff --git a/apps/ops/ansible/display.py b/apps/ops/ansible/display.py new file mode 100644 index 000000000..1494eb5ef --- /dev/null +++ b/apps/ops/ansible/display.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# + +import sys + + +class TeeObj: + origin_stdout = sys.stdout + + def __init__(self, file_obj): + self.file_obj = file_obj + + def write(self, msg): + self.origin_stdout.write(msg) + self.file_obj.write(msg.replace('*', '')) + + def flush(self): + self.origin_stdout.flush() + self.file_obj.flush() diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index e2f318f71..707855cc3 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -132,6 +132,8 @@ class BaseInventory(InventoryManager): parent.add_child_group(child) def parse_hosts(self): + group_all = self.get_or_create_group('all') + ungrouped = self.get_or_create_group('ungrouped') for host_data in self.host_list: host = self.host_manager_class(host_data=host_data) self.hosts[host_data['hostname']] = host @@ -140,6 +142,9 @@ class BaseInventory(InventoryManager): for group_name in groups_data: group = self.get_or_create_group(group_name) group.add_host(host) + else: + ungrouped.add_host(host) + group_all.add_host(host) def parse_sources(self, cache=False): self.parse_groups() diff --git a/apps/ops/ansible/runner.py b/apps/ops/ansible/runner.py index d9a8c7e6e..3e168e987 100644 --- a/apps/ops/ansible/runner.py +++ b/apps/ops/ansible/runner.py @@ -9,6 +9,7 @@ from ansible.parsing.dataloader import DataLoader from ansible.executor.playbook_executor import PlaybookExecutor from ansible.playbook.play import Play import ansible.constants as C +from ansible.utils.display import Display from .callback import AdHocResultCallback, PlaybookResultCallBack, \ CommandResultCallback @@ -21,6 +22,13 @@ C.HOST_KEY_CHECKING = False logger = get_logger(__name__) +class CustomDisplay(Display): + def display(self, msg, color=None, stderr=False, screen_only=False, log_only=False): + pass + +display = CustomDisplay() + + Options = namedtuple('Options', [ 'listtags', 'listtasks', 'listhosts', 'syntax', 'connection', 'module_path', 'forks', 'remote_user', 'private_key_file', 'timeout', @@ -123,20 +131,22 @@ class AdHocRunner: ADHoc Runner接口 """ results_callback_class = AdHocResultCallback + results_callback = None loader_class = DataLoader variable_manager_class = VariableManager - options = get_default_options() default_options = get_default_options() def __init__(self, inventory, options=None): - if options: - self.options = options + self.options = self.update_options(options) self.inventory = inventory self.loader = DataLoader() self.variable_manager = VariableManager( loader=self.loader, inventory=self.inventory ) + def get_result_callback(self, file_obj=None): + return self.__class__.results_callback_class(file_obj=file_obj) + @staticmethod def check_module_args(module_name, module_args=''): if module_name in C.MODULE_REQUIRE_ARGS and not module_args: @@ -160,19 +170,24 @@ class AdHocRunner: cleaned_tasks.append(task) return cleaned_tasks - def set_option(self, k, v): - kwargs = {k: v} - self.options = self.options._replace(**kwargs) + def update_options(self, options): + if options and isinstance(options, dict): + options = self.__class__.default_options._replace(**options) + else: + options = self.__class__.default_options + return options - def run(self, tasks, pattern, play_name='Ansible Ad-hoc', gather_facts='no'): + def run(self, tasks, pattern, play_name='Ansible Ad-hoc', gather_facts='no', file_obj=None): """ :param tasks: [{'action': {'module': 'shell', 'args': 'ls'}, ...}, ] :param pattern: all, *, or others :param play_name: The play name + :param gather_facts: + :param file_obj: logging to file_obj :return: """ self.check_pattern(pattern) - results_callback = self.results_callback_class() + self.results_callback = self.get_result_callback(file_obj) cleaned_tasks = self.clean_tasks(tasks) play_source = dict( @@ -193,16 +208,16 @@ class AdHocRunner: variable_manager=self.variable_manager, loader=self.loader, options=self.options, - stdout_callback=results_callback, + stdout_callback=self.results_callback, passwords=self.options.passwords, ) - logger.debug("Get inventory matched hosts: {}".format( + print("Get matched hosts: {}".format( self.inventory.get_matched_hosts(pattern) )) try: tqm.run(play) - return results_callback + return self.results_callback except Exception as e: raise AnsibleError(e) finally: diff --git a/apps/ops/api.py b/apps/ops/api.py index 09f5e67e7..92933098c 100644 --- a/apps/ops/api.py +++ b/apps/ops/api.py @@ -1,8 +1,11 @@ # ~*~ coding: utf-8 ~*~ +import uuid +import re - +from django.core.cache import cache from django.shortcuts import get_object_or_404 from rest_framework import viewsets, generics +from rest_framework.generics import RetrieveAPIView from rest_framework.views import Response from .hands import IsSuperUser @@ -58,3 +61,26 @@ class AdHocRunHistorySet(viewsets.ModelViewSet): adhoc = get_object_or_404(AdHoc, id=adhoc_id) self.queryset = self.queryset.filter(adhoc=adhoc) return self.queryset + + +class AdHocHistoryOutputAPI(RetrieveAPIView): + queryset = AdHocRunHistory.objects.all() + permission_classes = (IsSuperUser,) + buff_size = 1024 * 10 + end = False + + def retrieve(self, request, *args, **kwargs): + history = self.get_object() + mark = request.query_params.get("mark") or str(uuid.uuid4()) + + with open(history.log_path, 'r') as f: + offset = cache.get(mark, 0) + f.seek(offset) + data = f.read(self.buff_size).replace('\n', '\r\n') + print(repr(data)) + mark = str(uuid.uuid4()) + cache.set(mark, f.tell(), 5) + + if history.is_finished and data == '': + self.end = True + return Response({"data": data, 'end': self.end, 'mark': mark}) diff --git a/apps/ops/models.py b/apps/ops/models.py index 1120bb206..8a34e125a 100644 --- a/apps/ops/models.py +++ b/apps/ops/models.py @@ -2,16 +2,21 @@ import json import uuid - +import os import time +import datetime + from django.db import models +from django.conf import settings from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from django_celery_beat.models import CrontabSchedule, IntervalSchedule, PeriodicTask +from django_celery_beat.models import CrontabSchedule, IntervalSchedule, \ + PeriodicTask from common.utils import get_signer, get_logger -from common.celery import delete_celery_periodic_task, create_or_update_celery_periodic_tasks, \ - disable_celery_periodic_task +from common.celery import delete_celery_periodic_task, \ + create_or_update_celery_periodic_tasks, \ + disable_celery_periodic_task from .ansible import AdHocRunner, AnsibleError from .inventory import JMSInventory @@ -209,7 +214,8 @@ class AdHoc(models.Model): history = AdHocRunHistory(adhoc=self, task=self.task) time_start = time.time() try: - raw, summary = self._run_only() + with open(history.log_path, 'w') as f: + raw, summary = self._run_only(file_obj=f) history.is_finished = True if summary.get('dark'): history.is_success = False @@ -225,13 +231,15 @@ class AdHoc(models.Model): history.timedelta = time.time() - time_start history.save() - def _run_only(self): - runner = AdHocRunner(self.inventory) - for k, v in self.options.items(): - runner.set_option(k, v) - + def _run_only(self, file_obj=None): + runner = AdHocRunner(self.inventory, options=self.options) try: - result = runner.run(self.tasks, self.pattern, self.task.name) + result = runner.run( + self.tasks, + self.pattern, + self.task.name, + file_obj=file_obj, + ) return result.results_raw, result.results_summary except AnsibleError as e: logger.warn("Failed run adhoc {}, {}".format(self.task.name, e)) @@ -316,6 +324,14 @@ class AdHocRunHistory(models.Model): def short_id(self): return str(self.id).split('-')[-1] + @property + def log_path(self): + dt = datetime.datetime.now().strftime('%Y-%m-%d') + log_dir = os.path.join(settings.PROJECT_DIR, 'data', 'ansible', dt) + if not os.path.exists(log_dir): + os.makedirs(log_dir) + return os.path.join(log_dir, str(self.id) + '.log') + @property def result(self): if self._result: diff --git a/apps/ops/templates/ops/adhoc_history_output.html b/apps/ops/templates/ops/adhoc_history_output.html new file mode 100644 index 000000000..34f8078bf --- /dev/null +++ b/apps/ops/templates/ops/adhoc_history_output.html @@ -0,0 +1,93 @@ +{% load static %} + + + + term.js + + + + +
+
+
+
+ + + + + + diff --git a/apps/ops/urls/api_urls.py b/apps/ops/urls/api_urls.py index ab007c383..26c1c89a1 100644 --- a/apps/ops/urls/api_urls.py +++ b/apps/ops/urls/api_urls.py @@ -15,6 +15,7 @@ router.register(r'v1/history', api.AdHocRunHistorySet, 'history') urlpatterns = [ url(r'^v1/tasks/(?P[0-9a-zA-Z\-]{36})/run/$', api.TaskRun.as_view(), name='task-run'), + url(r'^v1/history/(?P[0-9a-zA-Z\-]{36})/output/$', api.AdHocHistoryOutputAPI.as_view(), name='history-output'), ] urlpatterns += router.urls diff --git a/apps/ops/urls/view_urls.py b/apps/ops/urls/view_urls.py index 080bdd06c..980f9d070 100644 --- a/apps/ops/urls/view_urls.py +++ b/apps/ops/urls/view_urls.py @@ -18,4 +18,5 @@ urlpatterns = [ url(r'^adhoc/(?P[0-9a-zA-Z\-]{36})/$', views.AdHocDetailView.as_view(), name='adhoc-detail'), url(r'^adhoc/(?P[0-9a-zA-Z\-]{36})/history/$', views.AdHocHistoryView.as_view(), name='adhoc-history'), url(r'^adhoc/history/(?P[0-9a-zA-Z\-]{36})/$', views.AdHocHistoryDetailView.as_view(), name='adhoc-history-detail'), + url(r'^adhoc/history/(?P[0-9a-zA-Z\-]{36})/output/$', views.AdHocHistoryOutputView.as_view(), name='adhoc-history-output'), ] diff --git a/apps/ops/views.py b/apps/ops/views.py index ba9e2cfeb..fbc942b56 100644 --- a/apps/ops/views.py +++ b/apps/ops/views.py @@ -112,6 +112,19 @@ class AdHocHistoryDetailView(AdminUserRequiredMixin, DetailView): model = AdHocRunHistory template_name = 'ops/adhoc_history_detail.html' + def get_context_data(self, **kwargs): + context = { + 'app': _('Ops'), + 'action': _('Run history detail'), + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + +class AdHocHistoryOutputView(AdminUserRequiredMixin, DetailView): + model = AdHocRunHistory + template_name = 'ops/adhoc_history_output.html' + def get_context_data(self, **kwargs): context = { 'app': _('Ops'), From df80e8047af03fc266c83c623765774640492089 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sun, 1 Apr 2018 23:45:37 +0800 Subject: [PATCH 3/8] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E6=97=A5?= =?UTF-8?q?=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/api.py | 31 +++++ apps/common/celery.py | 15 ++- apps/common/const.py | 3 +- apps/common/templates/common/tail_file.html | 96 ++++++++++++++ apps/common/urls/api_urls.py | 1 + apps/common/urls/view_urls.py | 3 + apps/common/views.py | 37 +++++- apps/ops/api.py | 30 +++-- apps/ops/models.py | 19 ++- apps/ops/tasks.py | 8 +- apps/ops/templates/ops/adhoc_history.html | 3 +- .../templates/ops/adhoc_history_detail.html | 3 + .../templates/ops/adhoc_history_output.html | 122 +++++------------- .../ops/templates/ops/celery_task_output.html | 94 ++++++++++++++ apps/ops/templates/ops/task_adhoc.html | 4 + apps/ops/templates/ops/task_detail.html | 4 +- apps/ops/templates/ops/task_history.html | 5 +- apps/ops/templates/ops/task_list.html | 6 +- apps/ops/urls/api_urls.py | 2 +- apps/ops/urls/view_urls.py | 1 + apps/ops/views.py | 7 +- 21 files changed, 373 insertions(+), 121 deletions(-) create mode 100644 apps/common/templates/common/tail_file.html create mode 100644 apps/ops/templates/ops/celery_task_output.html diff --git a/apps/common/api.py b/apps/common/api.py index 58a5822c1..0e626cacd 100644 --- a/apps/common/api.py +++ b/apps/common/api.py @@ -1,7 +1,10 @@ # -*- coding: utf-8 -*- # +import os import json +import uuid +from django.core.cache import cache from rest_framework.views import APIView from rest_framework.views import Response from ldap3 import Server, Connection @@ -11,6 +14,7 @@ from django.conf import settings from .permissions import IsSuperUser, IsAppUser from .serializers import MailTestSerializer, LDAPTestSerializer +from .const import FILE_END_GUARD class MailTestingAPI(APIView): @@ -105,3 +109,30 @@ class DjangoSettingsAPI(APIView): if i.isupper(): configs[i] = str(getattr(settings, i)) return Response(configs) + + +class FileTailApi(APIView): + permission_classes = (IsSuperUser,) + default_buff_size = 1024 * 10 + end = False + buff_size = None + + def get(self, request, *args, **kwargs): + file_path = request.query_params.get("file") + self.buff_size = request.query_params.get('buffer') or self.default_buff_size + mark = request.query_params.get("mark") or str(uuid.uuid4()) + + if not os.path.isfile(file_path): + return Response({"data": _("Waiting ...")}, status=203) + + with open(file_path, 'r') as f: + offset = cache.get(mark, 0) + f.seek(offset) + data = f.read(self.buff_size).replace('\n', '\r\n') + mark = str(uuid.uuid4()) + cache.set(mark, f.tell(), 5) + + if FILE_END_GUARD in data: + data = data.replace(FILE_END_GUARD, '') + self.end = True + return Response({"data": data, 'end': self.end, 'mark': mark}) \ No newline at end of file diff --git a/apps/common/celery.py b/apps/common/celery.py index e79f1d434..352f1ff45 100644 --- a/apps/common/celery.py +++ b/apps/common/celery.py @@ -5,7 +5,7 @@ import json from functools import wraps from celery import Celery, subtask -from celery.signals import worker_ready, worker_shutdown +from celery.signals import worker_ready, worker_shutdown, task_prerun, task_postrun from django.db.utils import ProgrammingError, OperationalError from .utils import get_logger @@ -199,3 +199,16 @@ def after_app_shutdown(sender=None, headers=None, body=None, **kwargs): ', '.join(__AFTER_APP_SHUTDOWN_CLEAN_TASKS)) ) PeriodicTask.objects.filter(name__in=__AFTER_APP_SHUTDOWN_CLEAN_TASKS).delete() + + +@task_prerun.connect +def pre_run_task_signal_handler(sender, task, *args, **kwargs): + + print("Sender: {}".format(sender)) + print("Task: {}".format(task)) + + +@task_postrun.connect +def post_run_task_signal_handler(sender, task, *args, **kwargs): + print("Sender: {}".format(sender)) + print("Task: {}".format(task)) \ No newline at end of file diff --git a/apps/common/const.py b/apps/common/const.py index a28b0f1db..f3669c49d 100644 --- a/apps/common/const.py +++ b/apps/common/const.py @@ -4,4 +4,5 @@ from django.utils.translation import ugettext as _ create_success_msg = _("%(name)s was created successfully") -update_success_msg = _("%(name)s was updated successfully") \ No newline at end of file +update_success_msg = _("%(name)s was updated successfully") +FILE_END_GUARD = ">>> Content End <<<" diff --git a/apps/common/templates/common/tail_file.html b/apps/common/templates/common/tail_file.html new file mode 100644 index 000000000..8c24fa707 --- /dev/null +++ b/apps/common/templates/common/tail_file.html @@ -0,0 +1,96 @@ +{% load static %} + + term.js + + + +
+
+
+
+ + + + diff --git a/apps/common/urls/api_urls.py b/apps/common/urls/api_urls.py index a9075e66e..e1e6c19ee 100644 --- a/apps/common/urls/api_urls.py +++ b/apps/common/urls/api_urls.py @@ -10,4 +10,5 @@ urlpatterns = [ url(r'^v1/mail/testing/$', api.MailTestingAPI.as_view(), name='mail-testing'), url(r'^v1/ldap/testing/$', api.LDAPTestingAPI.as_view(), name='ldap-testing'), url(r'^v1/django-settings/$', api.DjangoSettingsAPI.as_view(), name='django-settings'), + url(r'^v1/tail-file/$', api.FileTailApi.as_view(), name='tail-file'), ] diff --git a/apps/common/urls/view_urls.py b/apps/common/urls/view_urls.py index 466f7c49c..d2135f2d4 100644 --- a/apps/common/urls/view_urls.py +++ b/apps/common/urls/view_urls.py @@ -11,4 +11,7 @@ urlpatterns = [ url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'), url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-setting'), url(r'^terminal/$', views.TerminalSettingView.as_view(), name='terminal-setting'), + + url(r'^tail-file/$', views.TailFileView.as_view(), name='tail-file'), + url(r'^celery/task/log/$', views.CeleryTaskLogView.as_view(), name='celery-task-log'), ] diff --git a/apps/common/views.py b/apps/common/views.py index 99b924423..a57834a77 100644 --- a/apps/common/views.py +++ b/apps/common/views.py @@ -1,5 +1,7 @@ -from django.views.generic import TemplateView -from django.shortcuts import render, redirect + +from django.core.cache import cache +from django.views.generic import TemplateView, View +from django.shortcuts import render, redirect, Http404, reverse from django.contrib import messages from django.utils.translation import ugettext as _ from django.conf import settings @@ -120,3 +122,34 @@ class TerminalSettingView(AdminUserRequiredMixin, TemplateView): context.update({"form": form}) return render(request, self.template_name, context) + +class TailFileView(AdminUserRequiredMixin, TemplateView): + template_name = 'common/tail_file.html' + + def get_context_data(self, **kwargs): + file_path = self.request.GET.get("file") + context = super().get_context_data(**kwargs) + context.update({"file_path": file_path}) + return context + + +class CeleryTaskLogView(AdminUserRequiredMixin, TemplateView): + template_name = 'common/tail_file.html' + task_log_path = None + + def get(self, request, *args, **kwargs): + task = self.request.GET.get('task') + if not task: + raise Http404("Not found task") + + self.task_log_path = cache.get(task) + if not self.task_log_path: + raise Http404("Not found task log file") + return super().get(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'file_path': self.task_log_path + }) + return context diff --git a/apps/ops/api.py b/apps/ops/api.py index 92933098c..db27653cb 100644 --- a/apps/ops/api.py +++ b/apps/ops/api.py @@ -1,19 +1,23 @@ # ~*~ coding: utf-8 ~*~ import uuid -import re +import os from django.core.cache import cache from django.shortcuts import get_object_or_404 +from django.utils.translation import ugettext as _ from rest_framework import viewsets, generics -from rest_framework.generics import RetrieveAPIView +from rest_framework.views import APIView from rest_framework.views import Response from .hands import IsSuperUser +from common.const import FILE_END_GUARD from .models import Task, AdHoc, AdHocRunHistory from .serializers import TaskSerializer, AdHocSerializer, AdHocRunHistorySerializer from .tasks import run_ansible_task + + class TaskViewSet(viewsets.ModelViewSet): queryset = Task.objects.all() serializer_class = TaskSerializer @@ -27,8 +31,8 @@ class TaskRun(generics.RetrieveAPIView): def retrieve(self, request, *args, **kwargs): task = self.get_object() - run_ansible_task.delay(str(task.id)) - return Response({"msg": "start"}) + t = run_ansible_task.delay(str(task.id)) + return Response({"task": t.id}) class AdHocViewSet(viewsets.ModelViewSet): @@ -63,24 +67,28 @@ class AdHocRunHistorySet(viewsets.ModelViewSet): return self.queryset -class AdHocHistoryOutputAPI(RetrieveAPIView): - queryset = AdHocRunHistory.objects.all() +class LogFileViewApi(APIView): permission_classes = (IsSuperUser,) buff_size = 1024 * 10 end = False - def retrieve(self, request, *args, **kwargs): - history = self.get_object() + def get(self, request, *args, **kwargs): + file_path = request.query_params.get("file") mark = request.query_params.get("mark") or str(uuid.uuid4()) - with open(history.log_path, 'r') as f: + if not os.path.isfile(file_path): + print(file_path) + return Response({"error": _("Log file not found")}, status=204) + + with open(file_path, 'r') as f: offset = cache.get(mark, 0) f.seek(offset) data = f.read(self.buff_size).replace('\n', '\r\n') - print(repr(data)) mark = str(uuid.uuid4()) cache.set(mark, f.tell(), 5) - if history.is_finished and data == '': + if FILE_END_GUARD in data: + data.replace(FILE_END_GUARD, '') self.end = True + return Response({"data": data, 'end': self.end, 'mark': mark}) diff --git a/apps/ops/models.py b/apps/ops/models.py index 8a34e125a..62bd7d084 100644 --- a/apps/ops/models.py +++ b/apps/ops/models.py @@ -6,6 +6,7 @@ import os import time import datetime +from celery import current_task from django.db import models from django.conf import settings from django.utils import timezone @@ -211,11 +212,20 @@ class AdHoc(models.Model): return self._run_only() def _run_and_record(self): - history = AdHocRunHistory(adhoc=self, task=self.task) - time_start = time.time() try: - with open(history.log_path, 'w') as f: - raw, summary = self._run_only(file_obj=f) + hid = current_task.request.id + except AttributeError: + hid = str(uuid.uuid4()) + history = AdHocRunHistory(id=hid, adhoc=self, task=self.task) + time_start = time.time() + # f = open(history.log_path, 'w') + try: + date_start = timezone.now().strftime('%Y-%m-%d %H:%M:%S') + # f.write("{} {}\r\n\r\n".format(date_start, self.task.name)) + raw, summary = self._run_only() + # raw, summary = self._run_only(file_obj=f) + date_end = timezone.now().strftime('%Y-%m-%d %H:%M:%S') + # f.write("\r\n{} Task finish\r\n".format(date_end)) history.is_finished = True if summary.get('dark'): history.is_success = False @@ -227,6 +237,7 @@ class AdHoc(models.Model): except Exception as e: return {}, {"dark": {"all": str(e)}, "contacted": []} finally: + # f.close() history.date_finished = timezone.now() history.timedelta = time.time() - time_start history.save() diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index 41f60f20c..5a2738cd9 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -1,6 +1,7 @@ # coding: utf-8 from celery import shared_task, subtask + from common.utils import get_logger, get_object_or_none from .models import Task @@ -12,14 +13,13 @@ def rerun_task(): @shared_task -def run_ansible_task(task_id, callback=None, **kwargs): +def run_ansible_task(tid, callback=None, **kwargs): """ - :param task_id: is the tasks serialized data + :param tid: is the tasks serialized data :param callback: callback function name :return: """ - - task = get_object_or_none(Task, id=task_id) + task = get_object_or_none(Task, id=tid) if task: result = task.run() if callback is not None: diff --git a/apps/ops/templates/ops/adhoc_history.html b/apps/ops/templates/ops/adhoc_history.html index 802e8e8f6..cab76f692 100644 --- a/apps/ops/templates/ops/adhoc_history.html +++ b/apps/ops/templates/ops/adhoc_history.html @@ -82,7 +82,8 @@ function initTable() { select: [], columnDefs: [ {targets: 1, createdCell: function (td, cellData, rowData) { - $(td).html(cellData); + var d = new Date(cellData); + $(td).html(d); }}, {targets: 2, createdCell: function (td, cellData) { var total = "" + cellData.total + ""; diff --git a/apps/ops/templates/ops/adhoc_history_detail.html b/apps/ops/templates/ops/adhoc_history_detail.html index f3d18f8ba..15c26376f 100644 --- a/apps/ops/templates/ops/adhoc_history_detail.html +++ b/apps/ops/templates/ops/adhoc_history_detail.html @@ -18,6 +18,9 @@
  • {% trans 'Run history detail' %}
  • +
  • + {% trans 'History output' %} +
  • diff --git a/apps/ops/templates/ops/adhoc_history_output.html b/apps/ops/templates/ops/adhoc_history_output.html index 34f8078bf..781c2adf5 100644 --- a/apps/ops/templates/ops/adhoc_history_output.html +++ b/apps/ops/templates/ops/adhoc_history_output.html @@ -1,93 +1,37 @@ +{% extends 'base.html' %} {% load static %} - - - - term.js - - +{% load i18n %} + +{% block custom_head_css_js %} + + + + - -
    -
    +{% endblock %} +{% block content %} + -
    - +{% endblock %} - - - - diff --git a/apps/ops/templates/ops/celery_task_output.html b/apps/ops/templates/ops/celery_task_output.html new file mode 100644 index 000000000..d48b35dcf --- /dev/null +++ b/apps/ops/templates/ops/celery_task_output.html @@ -0,0 +1,94 @@ +{% load static %} + + term.js + + + +
    +
    +
    +
    + + + + diff --git a/apps/ops/templates/ops/task_adhoc.html b/apps/ops/templates/ops/task_adhoc.html index 2d2c4de5c..381048602 100644 --- a/apps/ops/templates/ops/task_adhoc.html +++ b/apps/ops/templates/ops/task_adhoc.html @@ -105,6 +105,10 @@ $(td).html(cellData.user) } }}, + {targets: 6, createdCell: function (td, cellData) { + var d = new Date(cellData); + $(td).html(d.toLocaleString()) + }}, {targets: 7, createdCell: function (td, cellData, rowData) { var detail_btn = '{% trans "Detail" %}'.replace('{{ DEFAULT_PK }}', cellData); if (cellData) { diff --git a/apps/ops/templates/ops/task_detail.html b/apps/ops/templates/ops/task_detail.html index 4da7e0f58..4aba9998a 100644 --- a/apps/ops/templates/ops/task_detail.html +++ b/apps/ops/templates/ops/task_detail.html @@ -24,6 +24,9 @@
  • {% trans 'Run history' %}
  • +
  • + {% trans 'Last run output' %} +
  • @@ -160,6 +163,5 @@
    - {% include 'users/_user_update_pk_modal.html' %} {% endblock %} diff --git a/apps/ops/templates/ops/task_history.html b/apps/ops/templates/ops/task_history.html index cc620237d..92401a0f9 100644 --- a/apps/ops/templates/ops/task_history.html +++ b/apps/ops/templates/ops/task_history.html @@ -30,7 +30,7 @@
    - {% trans 'History of ' %} {{ object.task.name }}:{{ object.short_id }} + {% trans 'History of ' %} {{ object.name }}:{{ object.short_id }}
    @@ -85,7 +85,8 @@ function initTable() { select: [], columnDefs: [ {targets: 1, createdCell: function (td, cellData, rowData) { - $(td).html(cellData); + var d = new Date(cellData); + $(td).html(d.toLocaleString()); }}, {targets: 2, createdCell: function (td, cellData) { var total = "" + cellData.total + ""; diff --git a/apps/ops/templates/ops/task_list.html b/apps/ops/templates/ops/task_list.html index f098a4d78..37a73b4c0 100644 --- a/apps/ops/templates/ops/task_list.html +++ b/apps/ops/templates/ops/task_list.html @@ -111,9 +111,9 @@ $(document).ready(function() { var error = function (data) { alert(data) }; - var success = function () { - alert("任务开始执行,重定向到任务详情页面,多刷新几次查看结果") - window.location = "{% url 'ops:task-detail' pk=DEFAULT_PK %}".replace('{{ DEFAULT_PK }}', uid); + var success = function(data) { + var task_id = data.task; + window.location = "{% url 'ops:adhoc-history-output' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", task_id); }; APIUpdateAttr({ url: the_url, diff --git a/apps/ops/urls/api_urls.py b/apps/ops/urls/api_urls.py index 26c1c89a1..a5f94da29 100644 --- a/apps/ops/urls/api_urls.py +++ b/apps/ops/urls/api_urls.py @@ -15,7 +15,7 @@ router.register(r'v1/history', api.AdHocRunHistorySet, 'history') urlpatterns = [ url(r'^v1/tasks/(?P[0-9a-zA-Z\-]{36})/run/$', api.TaskRun.as_view(), name='task-run'), - url(r'^v1/history/(?P[0-9a-zA-Z\-]{36})/output/$', api.AdHocHistoryOutputAPI.as_view(), name='history-output'), + # url(r'^v1/history/(?P[0-9a-zA-Z\-]{36})/output/$', api.CeleryTaskOutputApi.as_view(), name='history-output'), ] urlpatterns += router.urls diff --git a/apps/ops/urls/view_urls.py b/apps/ops/urls/view_urls.py index 980f9d070..e6979bbf3 100644 --- a/apps/ops/urls/view_urls.py +++ b/apps/ops/urls/view_urls.py @@ -18,5 +18,6 @@ urlpatterns = [ url(r'^adhoc/(?P[0-9a-zA-Z\-]{36})/$', views.AdHocDetailView.as_view(), name='adhoc-detail'), url(r'^adhoc/(?P[0-9a-zA-Z\-]{36})/history/$', views.AdHocHistoryView.as_view(), name='adhoc-history'), url(r'^adhoc/history/(?P[0-9a-zA-Z\-]{36})/$', views.AdHocHistoryDetailView.as_view(), name='adhoc-history-detail'), + url(r'^adhoc/history/(?P[0-9a-zA-Z\-]{36})/_output/$', views.CeleryTaskOutputView.as_view(), name='adhoc-history-output-alone'), url(r'^adhoc/history/(?P[0-9a-zA-Z\-]{36})/output/$', views.AdHocHistoryOutputView.as_view(), name='adhoc-history-output'), ] diff --git a/apps/ops/views.py b/apps/ops/views.py index fbc942b56..5a294e7c7 100644 --- a/apps/ops/views.py +++ b/apps/ops/views.py @@ -2,7 +2,7 @@ from django.utils.translation import ugettext as _ from django.conf import settings -from django.views.generic import ListView, DetailView +from django.views.generic import ListView, DetailView, TemplateView from common.mixins import DatetimeSearchMixin from .models import Task, AdHoc, AdHocRunHistory @@ -121,6 +121,11 @@ class AdHocHistoryDetailView(AdminUserRequiredMixin, DetailView): return super().get_context_data(**kwargs) +class CeleryTaskOutputView(AdminUserRequiredMixin, TemplateView): + model = AdHocRunHistory + template_name = 'ops/celery_task_output.html' + + class AdHocHistoryOutputView(AdminUserRequiredMixin, DetailView): model = AdHocRunHistory template_name = 'ops/adhoc_history_output.html' From a4c843ff13265d4ce9d280df62732fdbd8e11169 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 2 Apr 2018 13:19:31 +0800 Subject: [PATCH 4/8] =?UTF-8?q?[Update]=20=E8=BF=81=E7=A7=BBcelery?= =?UTF-8?q?=E5=88=B0ops?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/tasks.py | 6 +- apps/common/__init__.py | 2 +- apps/common/api.py | 26 ++-- apps/common/const.py | 3 +- apps/common/models.py | 9 ++ apps/common/tasks.py | 4 +- .../{tail_file.html => celery_task_log.html} | 4 +- apps/common/urls/api_urls.py | 2 +- apps/common/urls/view_urls.py | 3 +- apps/common/utils.py | 18 +++ apps/common/views.py | 32 ++--- apps/ops/__init__.py | 2 +- apps/ops/celery/__init__.py | 18 +++ apps/ops/celery/const.py | 3 + apps/ops/celery/signal_handler.py | 89 ++++++++++++++ .../{common/celery.py => ops/celery/utils.py} | 115 +++++++----------- apps/ops/models.py | 12 +- apps/ops/templates/ops/task_list.html | 3 +- apps/terminal/signals_handler.py | 2 - apps/terminal/tasks.py | 2 +- jms | 4 +- 21 files changed, 224 insertions(+), 135 deletions(-) rename apps/common/templates/common/{tail_file.html => celery_task_log.html} (95%) create mode 100644 apps/ops/celery/__init__.py create mode 100644 apps/ops/celery/const.py create mode 100644 apps/ops/celery/signal_handler.py rename apps/{common/celery.py => ops/celery/utils.py} (64%) diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py index e47d15d41..bfdafe734 100644 --- a/apps/assets/tasks.py +++ b/apps/assets/tasks.py @@ -3,15 +3,15 @@ import json import re import os -import paramiko from celery import shared_task from django.core.cache import cache from django.utils.translation import ugettext as _ from common.utils import get_object_or_none, capacity_convert, \ sum_capacity, encrypt_password, get_logger -from common.celery import register_as_period_task, after_app_shutdown_clean, \ - after_app_ready_start, app as celery_app +from ops.celery.utils import register_as_period_task, after_app_shutdown_clean, \ + after_app_ready_start +from ops.celery import app as celery_app from .models import SystemUser, AdminUser, Asset from . import const diff --git a/apps/common/__init__.py b/apps/common/__init__.py index b64e43e83..fdb34b225 100644 --- a/apps/common/__init__.py +++ b/apps/common/__init__.py @@ -2,4 +2,4 @@ from __future__ import absolute_import # This will make sure the app is always imported when # Django starts so that shared_task will use this app. -from .celery import app as celery_app + diff --git a/apps/common/api.py b/apps/common/api.py index 0e626cacd..11828a81e 100644 --- a/apps/common/api.py +++ b/apps/common/api.py @@ -5,8 +5,8 @@ import json import uuid from django.core.cache import cache -from rest_framework.views import APIView -from rest_framework.views import Response +from rest_framework.generics import RetrieveAPIView +from rest_framework.views import Response, APIView from ldap3 import Server, Connection from django.core.mail import get_connection, send_mail from django.utils.translation import ugettext_lazy as _ @@ -14,7 +14,8 @@ from django.conf import settings from .permissions import IsSuperUser, IsAppUser from .serializers import MailTestSerializer, LDAPTestSerializer -from .const import FILE_END_GUARD +from .celery import FINISHED +from .const import FILE_END_GUARD, celery_task_pre_key class MailTestingAPI(APIView): @@ -111,28 +112,27 @@ class DjangoSettingsAPI(APIView): return Response(configs) -class FileTailApi(APIView): +class CeleryTaskLogApi(APIView): permission_classes = (IsSuperUser,) - default_buff_size = 1024 * 10 + buff_size = 1024 * 10 end = False - buff_size = None def get(self, request, *args, **kwargs): - file_path = request.query_params.get("file") - self.buff_size = request.query_params.get('buffer') or self.default_buff_size + task_id = kwargs.get('pk') + info = cache.get(celery_task_pre_key + task_id, {}) + log_path = info.get("log_path") mark = request.query_params.get("mark") or str(uuid.uuid4()) - if not os.path.isfile(file_path): + if not log_path or not os.path.isfile(log_path): return Response({"data": _("Waiting ...")}, status=203) - with open(file_path, 'r') as f: + with open(log_path, 'r') as f: offset = cache.get(mark, 0) f.seek(offset) data = f.read(self.buff_size).replace('\n', '\r\n') mark = str(uuid.uuid4()) cache.set(mark, f.tell(), 5) - if FILE_END_GUARD in data: - data = data.replace(FILE_END_GUARD, '') + if data == '' and info["status"] == FINISHED: self.end = True - return Response({"data": data, 'end': self.end, 'mark': mark}) \ No newline at end of file + return Response({"data": data, 'end': self.end, 'mark': mark}) diff --git a/apps/common/const.py b/apps/common/const.py index f3669c49d..6652593cb 100644 --- a/apps/common/const.py +++ b/apps/common/const.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- # -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext_lazy as _ create_success_msg = _("%(name)s was created successfully") update_success_msg = _("%(name)s was updated successfully") FILE_END_GUARD = ">>> Content End <<<" +celery_task_pre_key = "CELERY_" diff --git a/apps/common/models.py b/apps/common/models.py index 1f634bce2..24922300f 100644 --- a/apps/common/models.py +++ b/apps/common/models.py @@ -79,3 +79,12 @@ class Setting(models.Model): class Meta: db_table = "settings" + + +class CeleryTask(models.Model): + id = models.UUIDField() + name = models.CharField(max_length=1024) + status = models.CharField(max_length=128) + date_published = models.DateTimeField(auto_now_add=True) + date_start = models.DateTimeField(null=True) + date_finished = models.DateTimeField(null=True) \ No newline at end of file diff --git a/apps/common/tasks.py b/apps/common/tasks.py index e8d6ba8b0..dec738921 100644 --- a/apps/common/tasks.py +++ b/apps/common/tasks.py @@ -1,13 +1,13 @@ from django.core.mail import send_mail from django.conf import settings -from .celery import app +from celery import shared_task from .utils import get_logger logger = get_logger(__file__) -@app.task +@shared_task def send_mail_async(*args, **kwargs): """ Using celery to send email async diff --git a/apps/common/templates/common/tail_file.html b/apps/common/templates/common/celery_task_log.html similarity index 95% rename from apps/common/templates/common/tail_file.html rename to apps/common/templates/common/celery_task_log.html index 8c24fa707..23b676dd2 100644 --- a/apps/common/templates/common/tail_file.html +++ b/apps/common/templates/common/celery_task_log.html @@ -35,7 +35,7 @@ var rowHeight = 1; var colWidth = 1; var mark = ''; - var url = "{% url 'api-common:tail-file' %}?file={{ file_path }}"; + var url = "{% url 'api-common:celery-task-log' pk=task_id %}"; var term; var end = false; var error = false; @@ -54,7 +54,7 @@ function requestAndWrite() { if (!end) { $.ajax({ - url: url + '&mark=' + mark, + url: url + '?mark=' + mark, method: "GET", contentType: "application/json; charset=utf-8" }).done(function(data, textStatue, jqXHR) { diff --git a/apps/common/urls/api_urls.py b/apps/common/urls/api_urls.py index e1e6c19ee..37629b801 100644 --- a/apps/common/urls/api_urls.py +++ b/apps/common/urls/api_urls.py @@ -10,5 +10,5 @@ urlpatterns = [ url(r'^v1/mail/testing/$', api.MailTestingAPI.as_view(), name='mail-testing'), url(r'^v1/ldap/testing/$', api.LDAPTestingAPI.as_view(), name='ldap-testing'), url(r'^v1/django-settings/$', api.DjangoSettingsAPI.as_view(), name='django-settings'), - url(r'^v1/tail-file/$', api.FileTailApi.as_view(), name='tail-file'), + url(r'^v1/celery/task/(?P[0-9a-zA-Z\-]{36})/log/$', api.CeleryTaskLogApi.as_view(), name='celery-task-log'), ] diff --git a/apps/common/urls/view_urls.py b/apps/common/urls/view_urls.py index d2135f2d4..0483d4dbb 100644 --- a/apps/common/urls/view_urls.py +++ b/apps/common/urls/view_urls.py @@ -12,6 +12,5 @@ urlpatterns = [ url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-setting'), url(r'^terminal/$', views.TerminalSettingView.as_view(), name='terminal-setting'), - url(r'^tail-file/$', views.TailFileView.as_view(), name='tail-file'), - url(r'^celery/task/log/$', views.CeleryTaskLogView.as_view(), name='celery-task-log'), + url(r'^celery/task/(?P[0-9a-zA-Z\-]{36})/log/$', views.CeleryTaskLogView.as_view(), name='celery-task-log'), ] diff --git a/apps/common/utils.py b/apps/common/utils.py index 7e7aa9aae..b4dd0aef8 100644 --- a/apps/common/utils.py +++ b/apps/common/utils.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # import re +import sys from collections import OrderedDict from six import string_types import base64 @@ -360,3 +361,20 @@ def get_signer(): signer = Signer(settings.SECRET_KEY) return signer + +class TeeObj: + origin_stdout = sys.stdout + + def __init__(self, file_obj): + self.file_obj = file_obj + + def write(self, msg): + self.origin_stdout.write(msg) + self.file_obj.write(msg.replace('*', '')) + + def flush(self): + self.origin_stdout.flush() + self.file_obj.flush() + + def close(self): + self.file_obj.close() diff --git a/apps/common/views.py b/apps/common/views.py index a57834a77..e51400d12 100644 --- a/apps/common/views.py +++ b/apps/common/views.py @@ -1,6 +1,6 @@ from django.core.cache import cache -from django.views.generic import TemplateView, View +from django.views.generic import TemplateView, View, DetailView from django.shortcuts import render, redirect, Http404, reverse from django.contrib import messages from django.utils.translation import ugettext as _ @@ -8,7 +8,6 @@ from django.conf import settings from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \ TerminalSettingForm -from .models import Setting from .mixins import AdminUserRequiredMixin from .signals import ldap_auth_enable @@ -123,33 +122,18 @@ class TerminalSettingView(AdminUserRequiredMixin, TemplateView): return render(request, self.template_name, context) -class TailFileView(AdminUserRequiredMixin, TemplateView): - template_name = 'common/tail_file.html' - - def get_context_data(self, **kwargs): - file_path = self.request.GET.get("file") - context = super().get_context_data(**kwargs) - context.update({"file_path": file_path}) - return context - - class CeleryTaskLogView(AdminUserRequiredMixin, TemplateView): - template_name = 'common/tail_file.html' + template_name = 'common/celery_task_log.html' task_log_path = None - def get(self, request, *args, **kwargs): - task = self.request.GET.get('task') - if not task: - raise Http404("Not found task") - - self.task_log_path = cache.get(task) - if not self.task_log_path: - raise Http404("Not found task log file") - return super().get(request, *args, **kwargs) - def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) + task_id = self.kwargs.get("pk") + + if cache.get(celery_task_pre_key+task_id) is None: + raise Http404() + context.update({ - 'file_path': self.task_log_path + "task_id": self.kwargs.get("pk") }) return context diff --git a/apps/ops/__init__.py b/apps/ops/__init__.py index 8b1378917..cf2e85f6d 100644 --- a/apps/ops/__init__.py +++ b/apps/ops/__init__.py @@ -1 +1 @@ - +from .celery import app as celery_app diff --git a/apps/ops/celery/__init__.py b/apps/ops/celery/__init__.py new file mode 100644 index 000000000..9cdcb3063 --- /dev/null +++ b/apps/ops/celery/__init__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +import os + +from celery import Celery + +# set the default Django settings module for the 'celery' program. +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'jumpserver.settings') + +from django.conf import settings + +app = Celery('jumpserver') + +# Using a string here means the worker will not have to +# pickle the object when using Windows. +app.config_from_object('django.conf:settings', namespace='CELERY') +app.autodiscover_tasks(lambda: [app_config.split('.')[0] for app_config in settings.INSTALLED_APPS]) + diff --git a/apps/ops/celery/const.py b/apps/ops/celery/const.py new file mode 100644 index 000000000..3d98261b1 --- /dev/null +++ b/apps/ops/celery/const.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# + diff --git a/apps/ops/celery/signal_handler.py b/apps/ops/celery/signal_handler.py new file mode 100644 index 000000000..6ce6c5d9d --- /dev/null +++ b/apps/ops/celery/signal_handler.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +# +import os +import datetime +import sys + +from django.conf import settings +from django.core.cache import cache +from celery import subtask +from celery.signals import worker_ready, worker_shutdown, task_prerun, \ + task_postrun, after_task_publish + +from django_celery_beat.models import PeriodicTask + +from common.utils import get_logger, TeeObj +from common.const import celery_task_pre_key + +from .utils import get_after_app_ready_tasks, get_after_app_shutdown_clean_tasks + + +logger = get_logger(__file__) + +WAITING = "waiting" +RUNNING = "running" +FINISHED = "finished" + +EXPIRE_TIME = 3600 + + +@worker_ready.connect +def on_app_ready(sender=None, headers=None, body=None, **kwargs): + if cache.get("CELERY_APP_READY", 0) == 1: + return + cache.set("CELERY_APP_READY", 1, 10) + logger.debug("App ready signal recv") + tasks = get_after_app_ready_tasks() + logger.debug("Start need start task: [{}]".format( + ", ".join(tasks)) + ) + for task in tasks: + subtask(task).delay() + + +@worker_shutdown.connect +def after_app_shutdown(sender=None, headers=None, body=None, **kwargs): + if cache.get("CELERY_APP_SHUTDOWN", 0) == 1: + return + cache.set("CELERY_APP_SHUTDOWN", 1, 10) + tasks = get_after_app_shutdown_clean_tasks() + logger.debug("App shutdown signal recv") + logger.debug("Clean need cleaned period tasks: [{}]".format( + ', '.join(tasks)) + ) + PeriodicTask.objects.filter(name__in=tasks).delete() + + +@task_prerun.connect +def pre_run_task_signal_handler(sender, task_id=None, task=None, **kwargs): + task_key = celery_task_pre_key + task_id + info = cache.get(task_key, {}) + now = datetime.datetime.now().strftime("%Y-%m-%d") + log_dir = os.path.join(settings.PROJECT_DIR, "data", "celery", now) + if not os.path.exists(log_dir): + os.makedirs(log_dir) + log_path = os.path.join(log_dir, task_id + '.log') + info.update({"status": RUNNING, "log_path": log_path}) + cache.set(task_key, info, EXPIRE_TIME) + f = open(log_path, 'w') + tee = TeeObj(f) + sys.stdout = tee + task.log_f = tee + + +@task_postrun.connect +def post_run_task_signal_handler(sender, task_id=None, task=None, **kwargs): + task_key = celery_task_pre_key + task_id + info = cache.get(task_key, {}) + info.update({"status": FINISHED}) + cache.set(task_key, info, EXPIRE_TIME) + task.log_f.flush() + sys.stdout = task.log_f.origin_stdout + task.log_f.close() + + +@after_task_publish.connect +def after_task_publish_signal_handler(sender, headers=None, **kwargs): + task_id = headers["id"] + key = celery_task_pre_key + task_id + cache.set(key, {"status": WAITING}, EXPIRE_TIME) \ No newline at end of file diff --git a/apps/common/celery.py b/apps/ops/celery/utils.py similarity index 64% rename from apps/common/celery.py rename to apps/ops/celery/utils.py index 352f1ff45..b4f5a80db 100644 --- a/apps/common/celery.py +++ b/apps/ops/celery/utils.py @@ -1,33 +1,50 @@ -# ~*~ coding: utf-8 ~*~ - -import os +# -*- coding: utf-8 -*- +# import json from functools import wraps -from celery import Celery, subtask -from celery.signals import worker_ready, worker_shutdown, task_prerun, task_postrun from django.db.utils import ProgrammingError, OperationalError - -from .utils import get_logger - -logger = get_logger(__file__) - -# set the default Django settings module for the 'celery' program. -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'jumpserver.settings') - -from django.conf import settings from django.core.cache import cache +from django_celery_beat.models import PeriodicTask, IntervalSchedule, CrontabSchedule -app = Celery('jumpserver') -# Using a string here means the worker will not have to -# pickle the object when using Windows. -app.config_from_object('django.conf:settings', namespace='CELERY') -app.autodiscover_tasks(lambda: [app_config.split('.')[0] for app_config in settings.INSTALLED_APPS]) +def add_register_period_task(name): + key = "__REGISTER_PERIODIC_TASKS" + value = cache.get(key, []) + value.append(name) + cache.set(key, value) + + +def get_register_period_tasks(): + key = "__REGISTER_PERIODIC_TASKS" + return cache.get(key, []) + + +def add_after_app_shutdown_clean_task(name): + key = "__AFTER_APP_SHUTDOWN_CLEAN_TASKS" + value = cache.get(key, []) + value.append(name) + cache.set(key, value) + + +def get_after_app_shutdown_clean_tasks(): + key = "__AFTER_APP_SHUTDOWN_CLEAN_TASKS" + return cache.get(key, []) + + +def add_after_app_ready_task(name): + key = "__AFTER_APP_READY_RUN_TASKS" + value = cache.get(key, []) + value.append(name) + cache.set(key, value) + + +def get_after_app_ready_tasks(): + key = "__AFTER_APP_READY_RUN_TASKS" + return cache.get(key, []) def create_or_update_celery_periodic_tasks(tasks): - from django_celery_beat.models import PeriodicTask, IntervalSchedule, CrontabSchedule """ :param tasks: { 'add-every-monday-morning': { @@ -106,11 +123,6 @@ def delete_celery_periodic_task(task_name): PeriodicTask.objects.filter(name=task_name).delete() -__REGISTER_PERIODIC_TASKS = [] -__AFTER_APP_SHUTDOWN_CLEAN_TASKS = [] -__AFTER_APP_READY_RUN_TASKS = [] - - def register_as_period_task(crontab=None, interval=None): """ Warning: Task must be have not any args and kwargs @@ -128,7 +140,7 @@ def register_as_period_task(crontab=None, interval=None): # Because when this decorator run, the task was not created, # So we can't use func.name name = '{func.__module__}.{func.__name__}'.format(func=func) - if name not in __REGISTER_PERIODIC_TASKS: + if name not in get_register_period_tasks(): create_or_update_celery_periodic_tasks({ name: { 'task': name, @@ -138,7 +150,7 @@ def register_as_period_task(crontab=None, interval=None): 'enabled': True, } }) - __REGISTER_PERIODIC_TASKS.append(name) + add_register_period_task(name) @wraps(func) def wrapper(*args, **kwargs): @@ -151,13 +163,12 @@ def after_app_ready_start(func): # Because when this decorator run, the task was not created, # So we can't use func.name name = '{func.__module__}.{func.__name__}'.format(func=func) - if name not in __AFTER_APP_READY_RUN_TASKS: - __AFTER_APP_READY_RUN_TASKS.append(name) + if name not in get_after_app_ready_tasks(): + add_after_app_ready_task(name) @wraps(func) def decorate(*args, **kwargs): return func(*args, **kwargs) - return decorate @@ -165,50 +176,10 @@ def after_app_shutdown_clean(func): # Because when this decorator run, the task was not created, # So we can't use func.name name = '{func.__module__}.{func.__name__}'.format(func=func) - if name not in __AFTER_APP_READY_RUN_TASKS: - __AFTER_APP_SHUTDOWN_CLEAN_TASKS.append(name) + if name not in get_after_app_shutdown_clean_tasks(): + add_after_app_shutdown_clean_task(name) @wraps(func) def decorate(*args, **kwargs): return func(*args, **kwargs) - return decorate - - -@worker_ready.connect -def on_app_ready(sender=None, headers=None, body=None, **kwargs): - if cache.get("CELERY_APP_READY", 0) == 1: - return - cache.set("CELERY_APP_READY", 1, 10) - logger.debug("App ready signal recv") - logger.debug("Start need start task: [{}]".format( - ", ".join(__AFTER_APP_READY_RUN_TASKS)) - ) - for task in __AFTER_APP_READY_RUN_TASKS: - subtask(task).delay() - - -@worker_shutdown.connect -def after_app_shutdown(sender=None, headers=None, body=None, **kwargs): - if cache.get("CELERY_APP_SHUTDOWN", 0) == 1: - return - cache.set("CELERY_APP_SHUTDOWN", 1, 10) - from django_celery_beat.models import PeriodicTask - logger.debug("App shutdown signal recv") - logger.debug("Clean need cleaned period tasks: [{}]".format( - ', '.join(__AFTER_APP_SHUTDOWN_CLEAN_TASKS)) - ) - PeriodicTask.objects.filter(name__in=__AFTER_APP_SHUTDOWN_CLEAN_TASKS).delete() - - -@task_prerun.connect -def pre_run_task_signal_handler(sender, task, *args, **kwargs): - - print("Sender: {}".format(sender)) - print("Task: {}".format(task)) - - -@task_postrun.connect -def post_run_task_signal_handler(sender, task, *args, **kwargs): - print("Sender: {}".format(sender)) - print("Task: {}".format(task)) \ No newline at end of file diff --git a/apps/ops/models.py b/apps/ops/models.py index 62bd7d084..636d27b0c 100644 --- a/apps/ops/models.py +++ b/apps/ops/models.py @@ -15,7 +15,7 @@ from django_celery_beat.models import CrontabSchedule, IntervalSchedule, \ PeriodicTask from common.utils import get_signer, get_logger -from common.celery import delete_celery_periodic_task, \ +from .celery.utils import delete_celery_periodic_task, \ create_or_update_celery_periodic_tasks, \ disable_celery_periodic_task from .ansible import AdHocRunner, AnsibleError @@ -218,14 +218,12 @@ class AdHoc(models.Model): hid = str(uuid.uuid4()) history = AdHocRunHistory(id=hid, adhoc=self, task=self.task) time_start = time.time() - # f = open(history.log_path, 'w') try: - date_start = timezone.now().strftime('%Y-%m-%d %H:%M:%S') - # f.write("{} {}\r\n\r\n".format(date_start, self.task.name)) + date_start = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + print("{} Start task: {}\r\n".format(date_start, self.task.name)) raw, summary = self._run_only() - # raw, summary = self._run_only(file_obj=f) - date_end = timezone.now().strftime('%Y-%m-%d %H:%M:%S') - # f.write("\r\n{} Task finish\r\n".format(date_end)) + date_end = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + print("\r\n{} Task finished".format(date_end)) history.is_finished = True if summary.get('dark'): history.is_success = False diff --git a/apps/ops/templates/ops/task_list.html b/apps/ops/templates/ops/task_list.html index 37a73b4c0..af4c8ea8f 100644 --- a/apps/ops/templates/ops/task_list.html +++ b/apps/ops/templates/ops/task_list.html @@ -113,7 +113,8 @@ $(document).ready(function() { }; var success = function(data) { var task_id = data.task; - window.location = "{% url 'ops:adhoc-history-output' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", task_id); + var url = '{% url "common:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); + window.open(url, '', 'width=800,height=800') }; APIUpdateAttr({ url: the_url, diff --git a/apps/terminal/signals_handler.py b/apps/terminal/signals_handler.py index 3926a5751..883dd51f2 100644 --- a/apps/terminal/signals_handler.py +++ b/apps/terminal/signals_handler.py @@ -5,8 +5,6 @@ from django.core.cache import cache from django.db.utils import ProgrammingError, OperationalError from common.utils import get_logger -from common.celery import after_app_ready_start, register_as_period_task, \ - after_app_shutdown_clean from .const import ASSETS_CACHE_KEY, USERS_CACHE_KEY, SYSTEM_USER_CACHE_KEY RUNNING = False diff --git a/apps/terminal/tasks.py b/apps/terminal/tasks.py index e267b30b7..4e57c5f5e 100644 --- a/apps/terminal/tasks.py +++ b/apps/terminal/tasks.py @@ -6,7 +6,7 @@ import datetime from celery import shared_task from django.utils import timezone -from common.celery import register_as_period_task, after_app_ready_start, \ +from ops.celery.utils import register_as_period_task, after_app_ready_start, \ after_app_shutdown_clean from .models import Status, Session diff --git a/jms b/jms index ea48ab6a7..0f07f0760 100755 --- a/jms +++ b/jms @@ -155,7 +155,7 @@ def start_celery(): cmd = [ 'celery', 'worker', - '-A', 'common', + '-A', 'ops', '-l', LOG_LEVEL.lower(), '--pidfile', pid_file, '-c', str(WORKERS), @@ -182,7 +182,7 @@ def start_beat(): scheduler = "django_celery_beat.schedulers:DatabaseScheduler" cmd = [ 'celery', 'beat', - '-A', 'common', + '-A', 'ops', '--pidfile', pid_file, '-l', LOG_LEVEL, '--scheduler', scheduler, From d247e49b709d2f8027ecae300495a7ae4e3d2836 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 2 Apr 2018 15:54:49 +0800 Subject: [PATCH 5/8] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9celery?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/api.py | 31 +-- apps/common/models.py | 8 - apps/common/urls/api_urls.py | 1 - apps/common/urls/view_urls.py | 2 - apps/common/views.py | 15 - apps/i18n/zh/LC_MESSAGES/django.mo | Bin 30239 -> 30286 bytes apps/i18n/zh/LC_MESSAGES/django.po | 263 +++++++++--------- apps/ops/api.py | 27 +- apps/ops/apps.py | 4 + apps/ops/celery/__init__.py | 1 - apps/ops/celery/signal_handler.py | 63 +++-- apps/ops/models/__init__.py | 5 + apps/ops/{models.py => models/adhoc.py} | 11 +- apps/ops/models/celery.py | 36 +++ apps/ops/tasks.py | 1 - .../templates/ops/adhoc_history_detail.html | 2 +- .../templates/ops/adhoc_history_output.html | 37 --- .../templates/ops}/celery_task_log.html | 2 +- .../ops/templates/ops/celery_task_output.html | 94 ------- apps/ops/templates/ops/task_adhoc.html | 3 + apps/ops/templates/ops/task_detail.html | 2 +- apps/ops/templates/ops/task_history.html | 3 + apps/ops/templates/ops/task_list.html | 4 +- apps/ops/urls/api_urls.py | 2 +- apps/ops/urls/view_urls.py | 3 +- apps/ops/views.py | 21 +- 26 files changed, 250 insertions(+), 391 deletions(-) create mode 100644 apps/ops/models/__init__.py rename apps/ops/{models.py => models/adhoc.py} (97%) create mode 100644 apps/ops/models/celery.py delete mode 100644 apps/ops/templates/ops/adhoc_history_output.html rename apps/{common/templates/common => ops/templates/ops}/celery_task_log.html (97%) delete mode 100644 apps/ops/templates/ops/celery_task_output.html diff --git a/apps/common/api.py b/apps/common/api.py index 11828a81e..4b74b6b0d 100644 --- a/apps/common/api.py +++ b/apps/common/api.py @@ -1,21 +1,15 @@ # -*- coding: utf-8 -*- # -import os import json -import uuid -from django.core.cache import cache -from rest_framework.generics import RetrieveAPIView from rest_framework.views import Response, APIView from ldap3 import Server, Connection from django.core.mail import get_connection, send_mail from django.utils.translation import ugettext_lazy as _ from django.conf import settings -from .permissions import IsSuperUser, IsAppUser +from .permissions import IsSuperUser from .serializers import MailTestSerializer, LDAPTestSerializer -from .celery import FINISHED -from .const import FILE_END_GUARD, celery_task_pre_key class MailTestingAPI(APIView): @@ -112,27 +106,4 @@ class DjangoSettingsAPI(APIView): return Response(configs) -class CeleryTaskLogApi(APIView): - permission_classes = (IsSuperUser,) - buff_size = 1024 * 10 - end = False - def get(self, request, *args, **kwargs): - task_id = kwargs.get('pk') - info = cache.get(celery_task_pre_key + task_id, {}) - log_path = info.get("log_path") - mark = request.query_params.get("mark") or str(uuid.uuid4()) - - if not log_path or not os.path.isfile(log_path): - return Response({"data": _("Waiting ...")}, status=203) - - with open(log_path, 'r') as f: - offset = cache.get(mark, 0) - f.seek(offset) - data = f.read(self.buff_size).replace('\n', '\r\n') - mark = str(uuid.uuid4()) - cache.set(mark, f.tell(), 5) - - if data == '' and info["status"] == FINISHED: - self.end = True - return Response({"data": data, 'end': self.end, 'mark': mark}) diff --git a/apps/common/models.py b/apps/common/models.py index 24922300f..c90458985 100644 --- a/apps/common/models.py +++ b/apps/common/models.py @@ -80,11 +80,3 @@ class Setting(models.Model): class Meta: db_table = "settings" - -class CeleryTask(models.Model): - id = models.UUIDField() - name = models.CharField(max_length=1024) - status = models.CharField(max_length=128) - date_published = models.DateTimeField(auto_now_add=True) - date_start = models.DateTimeField(null=True) - date_finished = models.DateTimeField(null=True) \ No newline at end of file diff --git a/apps/common/urls/api_urls.py b/apps/common/urls/api_urls.py index 37629b801..a9075e66e 100644 --- a/apps/common/urls/api_urls.py +++ b/apps/common/urls/api_urls.py @@ -10,5 +10,4 @@ urlpatterns = [ url(r'^v1/mail/testing/$', api.MailTestingAPI.as_view(), name='mail-testing'), url(r'^v1/ldap/testing/$', api.LDAPTestingAPI.as_view(), name='ldap-testing'), url(r'^v1/django-settings/$', api.DjangoSettingsAPI.as_view(), name='django-settings'), - url(r'^v1/celery/task/(?P[0-9a-zA-Z\-]{36})/log/$', api.CeleryTaskLogApi.as_view(), name='celery-task-log'), ] diff --git a/apps/common/urls/view_urls.py b/apps/common/urls/view_urls.py index 0483d4dbb..466f7c49c 100644 --- a/apps/common/urls/view_urls.py +++ b/apps/common/urls/view_urls.py @@ -11,6 +11,4 @@ urlpatterns = [ url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'), url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-setting'), url(r'^terminal/$', views.TerminalSettingView.as_view(), name='terminal-setting'), - - url(r'^celery/task/(?P[0-9a-zA-Z\-]{36})/log/$', views.CeleryTaskLogView.as_view(), name='celery-task-log'), ] diff --git a/apps/common/views.py b/apps/common/views.py index e51400d12..ee7a2225f 100644 --- a/apps/common/views.py +++ b/apps/common/views.py @@ -122,18 +122,3 @@ class TerminalSettingView(AdminUserRequiredMixin, TemplateView): return render(request, self.template_name, context) -class CeleryTaskLogView(AdminUserRequiredMixin, TemplateView): - template_name = 'common/celery_task_log.html' - task_log_path = None - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - task_id = self.kwargs.get("pk") - - if cache.get(celery_task_pre_key+task_id) is None: - raise Http404() - - context.update({ - "task_id": self.kwargs.get("pk") - }) - return context diff --git a/apps/i18n/zh/LC_MESSAGES/django.mo b/apps/i18n/zh/LC_MESSAGES/django.mo index da9fcf171523f8a8a892810b7222767dd1e8c87c..0282d293f7b5e37fba6923114945dc7cdff3444e 100644 GIT binary patch delta 10678 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 diff --git a/apps/i18n/zh/LC_MESSAGES/django.po b/apps/i18n/zh/LC_MESSAGES/django.po index 78691d4e7..6409056f8 100644 --- a/apps/i18n/zh/LC_MESSAGES/django.po +++ b/apps/i18n/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-03-23 19:50+0800\n" +"POT-Creation-Date: 2018-04-02 15:49+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -51,7 +51,7 @@ msgid "Labels" msgstr "标签管理" #: assets/forms/asset.py:34 assets/forms/asset.py:70 assets/models/asset.py:52 -#: assets/models/domain.py:34 +#: assets/models/domain.py:46 msgid "Domain" msgstr "网域" @@ -74,12 +74,12 @@ msgstr "" #: assets/forms/asset.py:90 assets/forms/asset.py:94 assets/forms/domain.py:16 #: assets/forms/label.py:15 -#: perms/templates/perms/asset_permission_asset.html:88 users/forms.py:270 +#: perms/templates/perms/asset_permission_asset.html:88 users/forms.py:272 msgid "Select assets" msgstr "选择资产" #: assets/forms/asset.py:99 assets/models/asset.py:51 -#: assets/models/domain.py:32 assets/templates/assets/admin_user_assets.html:53 +#: assets/models/domain.py:44 assets/templates/assets/admin_user_assets.html:53 #: assets/templates/assets/asset_detail.html:69 #: assets/templates/assets/domain_gateway_list.html:58 #: assets/templates/assets/system_user_asset.html:51 @@ -96,7 +96,7 @@ msgid "Select nodes" msgstr "选择节点" #: assets/forms/domain.py:14 assets/forms/label.py:13 -#: assets/models/asset.py:156 assets/templates/assets/admin_user_list.html:25 +#: assets/models/asset.py:165 assets/templates/assets/admin_user_list.html:25 #: assets/templates/assets/domain_detail.html:60 #: assets/templates/assets/domain_list.html:15 #: assets/templates/assets/label_list.html:16 @@ -111,7 +111,7 @@ msgstr "资产" #: assets/forms/domain.py:54 assets/forms/user.py:79 assets/forms/user.py:120 #: assets/models/base.py:20 assets/models/cluster.py:18 -#: assets/models/domain.py:15 assets/models/group.py:20 +#: assets/models/domain.py:17 assets/models/group.py:20 #: assets/models/label.py:17 assets/templates/assets/admin_user_detail.html:56 #: assets/templates/assets/admin_user_list.html:23 #: assets/templates/assets/domain_detail.html:56 @@ -121,8 +121,8 @@ msgstr "资产" #: assets/templates/assets/system_user_detail.html:58 #: assets/templates/assets/system_user_list.html:26 common/models.py:26 #: common/templates/common/terminal_setting.html:67 -#: common/templates/common/terminal_setting.html:85 ops/models.py:31 -#: ops/templates/ops/task_detail.html:56 ops/templates/ops/task_list.html:34 +#: common/templates/common/terminal_setting.html:85 ops/models/adhoc.py:36 +#: ops/templates/ops/task_detail.html:59 ops/templates/ops/task_list.html:34 #: perms/models.py:14 perms/templates/perms/asset_permission_detail.html:62 #: perms/templates/perms/asset_permission_user.html:54 terminal/models.py:16 #: terminal/models.py:149 terminal/templates/terminal/terminal_detail.html:43 @@ -190,7 +190,7 @@ msgid "" "than 2 system user" msgstr "高优先级的系统用户将会作为默认登录用户" -#: assets/models/asset.py:49 assets/models/domain.py:31 +#: assets/models/asset.py:49 assets/models/domain.py:43 #: assets/templates/assets/_asset_list_modal.html:21 #: assets/templates/assets/admin_user_assets.html:52 #: assets/templates/assets/asset_detail.html:61 @@ -217,7 +217,7 @@ msgstr "IP" msgid "Hostname" msgstr "主机名" -#: assets/models/asset.py:54 assets/models/domain.py:36 +#: assets/models/asset.py:54 assets/models/domain.py:48 #: assets/models/label.py:20 assets/templates/assets/asset_detail.html:105 #: perms/templates/perms/asset_permission_list.html:70 msgid "Is active" @@ -300,11 +300,11 @@ msgid "Created by" msgstr "创建者" #: assets/models/asset.py:83 assets/models/cluster.py:26 -#: assets/models/domain.py:18 assets/models/group.py:22 +#: assets/models/domain.py:20 assets/models/group.py:22 #: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:64 #: assets/templates/assets/domain_detail.html:68 #: assets/templates/assets/system_user_detail.html:92 -#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:60 +#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:63 #: perms/models.py:23 perms/models.py:80 #: perms/templates/perms/asset_permission_detail.html:90 #: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17 @@ -313,8 +313,8 @@ msgid "Date created" msgstr "创建日期" #: assets/models/asset.py:84 assets/models/base.py:25 -#: assets/models/cluster.py:29 assets/models/domain.py:16 -#: assets/models/domain.py:35 assets/models/group.py:23 +#: assets/models/cluster.py:29 assets/models/domain.py:18 +#: assets/models/domain.py:47 assets/models/group.py:23 #: assets/models/label.py:21 assets/templates/assets/admin_user_detail.html:72 #: assets/templates/assets/admin_user_list.html:29 #: assets/templates/assets/asset_detail.html:125 @@ -323,7 +323,7 @@ msgstr "创建日期" #: assets/templates/assets/domain_list.html:17 #: assets/templates/assets/system_user_detail.html:100 #: assets/templates/assets/system_user_list.html:33 common/models.py:30 -#: ops/models.py:37 perms/models.py:24 perms/models.py:81 +#: ops/models/adhoc.py:42 perms/models.py:24 perms/models.py:81 #: perms/templates/perms/asset_permission_detail.html:98 terminal/models.py:26 #: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:15 #: users/models/user.py:47 users/templates/users/user_detail.html:111 @@ -387,7 +387,7 @@ msgstr "默认Cluster" msgid "Cluster" msgstr "集群" -#: assets/models/domain.py:33 assets/models/user.py:104 +#: assets/models/domain.py:45 assets/models/user.py:104 #: assets/templates/assets/domain_gateway_list.html:59 #: assets/templates/assets/system_user_detail.html:66 #: assets/templates/assets/system_user_list.html:28 @@ -407,7 +407,7 @@ msgstr "默认资产组" #: terminal/templates/terminal/command_list.html:32 #: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/session_list.html:33 -#: terminal/templates/terminal/session_list.html:71 users/forms.py:218 +#: terminal/templates/terminal/session_list.html:71 users/forms.py:220 #: users/models/user.py:30 users/models/user.py:254 #: users/templates/users/user_group_detail.html:78 #: users/templates/users/user_group_list.html:13 users/views/user.py:334 @@ -493,7 +493,7 @@ msgstr "" msgid "推送系统用户到节点资产: {} => {}" msgstr "" -#: assets/tasks.py:431 +#: assets/tasks.py:432 msgid "推送节点系统用户到新加入资产中: {}" msgstr "" @@ -509,9 +509,9 @@ msgstr "仅修改你需要更新的字段" #: assets/templates/assets/system_user_asset.html:21 #: assets/views/admin_user.py:29 assets/views/admin_user.py:47 #: assets/views/admin_user.py:63 assets/views/admin_user.py:78 -#: assets/views/admin_user.py:102 assets/views/asset.py:48 -#: assets/views/asset.py:94 assets/views/asset.py:154 assets/views/asset.py:171 -#: assets/views/asset.py:195 assets/views/domain.py:29 +#: assets/views/admin_user.py:102 assets/views/asset.py:49 +#: assets/views/asset.py:95 assets/views/asset.py:155 assets/views/asset.py:172 +#: assets/views/asset.py:196 assets/views/domain.py:29 #: assets/views/domain.py:45 assets/views/domain.py:61 #: assets/views/domain.py:74 assets/views/domain.py:98 #: assets/views/domain.py:126 assets/views/domain.py:150 @@ -602,8 +602,8 @@ msgstr "可连接" #: assets/templates/assets/domain_list.html:18 #: assets/templates/assets/label_list.html:17 #: assets/templates/assets/system_user_list.html:34 -#: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:61 -#: ops/templates/ops/task_history.html:62 ops/templates/ops/task_list.html:41 +#: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:64 +#: ops/templates/ops/task_history.html:65 ops/templates/ops/task_list.html:41 #: perms/templates/perms/asset_permission_list.html:72 #: terminal/templates/terminal/session_list.html:80 #: terminal/templates/terminal/terminal_list.html:36 @@ -774,9 +774,9 @@ msgstr "重置" #: assets/templates/assets/domain_gateway_list.html:18 #: assets/templates/assets/system_user_asset.html:17 #: assets/templates/assets/system_user_detail.html:18 -#: ops/templates/ops/adhoc_history.html:129 -#: ops/templates/ops/task_adhoc.html:109 -#: ops/templates/ops/task_history.html:132 +#: ops/templates/ops/adhoc_history.html:130 +#: ops/templates/ops/task_adhoc.html:116 +#: ops/templates/ops/task_history.html:136 #: perms/templates/perms/asset_permission_asset.html:18 #: perms/templates/perms/asset_permission_detail.html:18 #: perms/templates/perms/asset_permission_user.html:18 @@ -822,7 +822,7 @@ msgstr "替换资产的管理员" #: assets/templates/assets/admin_user_detail.html:100 #: assets/templates/assets/asset_detail.html:200 -#: assets/templates/assets/asset_list.html:586 +#: assets/templates/assets/asset_list.html:594 #: assets/templates/assets/system_user_detail.html:183 #: assets/templates/assets/system_user_list.html:138 templates/_modal.html:16 #: terminal/templates/terminal/session_detail.html:108 @@ -849,7 +849,7 @@ msgstr "不可达" #: assets/templates/assets/admin_user_list.html:28 #: assets/templates/assets/system_user_list.html:32 #: ops/templates/ops/adhoc_history.html:54 -#: ops/templates/ops/task_history.html:57 +#: ops/templates/ops/task_history.html:60 msgid "Ratio" msgstr "比例" @@ -866,7 +866,7 @@ msgstr "节点" msgid "Label" msgstr "标签" -#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:196 +#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:197 msgid "Asset detail" msgstr "资产详情" @@ -905,7 +905,7 @@ msgid "Update successfully!" msgstr "更新成功" #: assets/templates/assets/asset_list.html:63 -#: assets/templates/assets/asset_list.html:120 assets/views/asset.py:95 +#: assets/templates/assets/asset_list.html:120 assets/views/asset.py:96 msgid "Create asset" msgstr "创建资产" @@ -955,7 +955,7 @@ msgstr "创建节点失败" msgid "Have child node, cancel" msgstr "存在子节点,不能删除" -#: assets/templates/assets/asset_list.html:581 +#: assets/templates/assets/asset_list.html:589 #: assets/templates/assets/system_user_list.html:133 #: users/templates/users/user_detail.html:334 #: users/templates/users/user_detail.html:359 @@ -964,20 +964,20 @@ msgstr "存在子节点,不能删除" msgid "Are you sure?" msgstr "你确认吗?" -#: assets/templates/assets/asset_list.html:582 +#: assets/templates/assets/asset_list.html:590 msgid "This will delete the selected assets !!!" msgstr "删除选择资产" -#: assets/templates/assets/asset_list.html:590 +#: assets/templates/assets/asset_list.html:598 msgid "Asset Deleted." msgstr "已被删除" -#: assets/templates/assets/asset_list.html:591 -#: assets/templates/assets/asset_list.html:596 +#: assets/templates/assets/asset_list.html:599 +#: assets/templates/assets/asset_list.html:604 msgid "Asset Delete" msgstr "删除" -#: assets/templates/assets/asset_list.html:595 +#: assets/templates/assets/asset_list.html:603 msgid "Asset Deleting failed." msgstr "删除失败" @@ -1104,23 +1104,23 @@ msgstr "更新管理用户" msgid "Admin user detail" msgstr "管理用户详情" -#: assets/views/asset.py:49 templates/_nav.html:23 +#: assets/views/asset.py:50 templates/_nav.html:23 msgid "Asset list" msgstr "资产列表" -#: assets/views/asset.py:61 templates/_nav_user.html:4 +#: assets/views/asset.py:62 templates/_nav_user.html:4 msgid "My assets" msgstr "我的资产" -#: assets/views/asset.py:155 +#: assets/views/asset.py:156 msgid "Bulk update asset" msgstr "批量更新资产" -#: assets/views/asset.py:172 +#: assets/views/asset.py:173 msgid "Update asset" msgstr "更新资产" -#: assets/views/asset.py:296 +#: assets/views/asset.py:299 msgid "already exists" msgstr "已经存在" @@ -1172,15 +1172,15 @@ msgstr "资产管理" msgid "System user asset" msgstr "系统用户集群资产" -#: common/api.py:19 +#: common/api.py:18 msgid "Test mail sent to {}, please check" msgstr "邮件已经发送{}, 请检查" -#: common/api.py:53 +#: common/api.py:52 msgid "Test ldap success" msgstr "连接LDAP成功" -#: common/api.py:91 +#: common/api.py:90 msgid "Match {} s users" msgstr "匹配 {} 个用户" @@ -1299,7 +1299,7 @@ msgstr "资产列表排序" msgid "Heartbeat interval" msgstr "心跳间隔" -#: common/forms.py:150 ops/models.py:32 +#: common/forms.py:150 ops/models/adhoc.py:37 msgid "Units: seconds" msgstr "单位: 秒" @@ -1349,28 +1349,28 @@ msgstr "启用" #: common/templates/common/email_setting.html:15 #: common/templates/common/ldap_setting.html:15 #: common/templates/common/terminal_setting.html:16 -#: common/templates/common/terminal_setting.html:42 common/views.py:21 +#: common/templates/common/terminal_setting.html:42 common/views.py:22 msgid "Basic setting" msgstr "基本设置" #: common/templates/common/basic_setting.html:18 #: common/templates/common/email_setting.html:18 #: common/templates/common/ldap_setting.html:18 -#: common/templates/common/terminal_setting.html:20 common/views.py:47 +#: common/templates/common/terminal_setting.html:20 common/views.py:48 msgid "Email setting" msgstr "邮件设置" #: common/templates/common/basic_setting.html:21 #: common/templates/common/email_setting.html:21 #: common/templates/common/ldap_setting.html:21 -#: common/templates/common/terminal_setting.html:24 common/views.py:73 +#: common/templates/common/terminal_setting.html:24 common/views.py:74 msgid "LDAP setting" msgstr "LDAP设置" #: common/templates/common/basic_setting.html:24 #: common/templates/common/email_setting.html:24 #: common/templates/common/ldap_setting.html:24 -#: common/templates/common/terminal_setting.html:28 common/views.py:103 +#: common/templates/common/terminal_setting.html:28 common/views.py:104 msgid "Terminal setting" msgstr "终端设置" @@ -1380,97 +1380,101 @@ msgstr "终端设置" msgid "Type" msgstr "类型" -#: common/views.py:20 common/views.py:46 common/views.py:72 common/views.py:102 +#: common/views.py:21 common/views.py:47 common/views.py:73 common/views.py:103 #: templates/_nav.html:73 msgid "Settings" msgstr "系统设置" -#: common/views.py:31 common/views.py:57 common/views.py:85 common/views.py:115 +#: common/views.py:32 common/views.py:58 common/views.py:86 common/views.py:116 msgid "Update setting successfully, please restart program" msgstr "更新设置成功, 请手动重启程序" -#: ops/models.py:32 +#: ops/api.py:79 +msgid "Waiting ..." +msgstr "" + +#: ops/models/adhoc.py:37 msgid "Interval" msgstr "间隔" -#: ops/models.py:33 +#: ops/models/adhoc.py:38 msgid "Crontab" msgstr "Crontab" -#: ops/models.py:33 +#: ops/models/adhoc.py:38 msgid "5 * * * *" msgstr "" -#: ops/models.py:35 +#: ops/models/adhoc.py:40 msgid "Callback" msgstr "回调" -#: ops/models.py:149 ops/templates/ops/adhoc_detail.html:114 +#: ops/models/adhoc.py:154 ops/templates/ops/adhoc_detail.html:114 msgid "Tasks" msgstr "任务" -#: ops/models.py:150 ops/templates/ops/adhoc_detail.html:57 -#: ops/templates/ops/task_adhoc.html:57 +#: ops/models/adhoc.py:155 ops/templates/ops/adhoc_detail.html:57 +#: ops/templates/ops/task_adhoc.html:60 msgid "Pattern" msgstr "" -#: ops/models.py:151 ops/templates/ops/adhoc_detail.html:61 +#: ops/models/adhoc.py:156 ops/templates/ops/adhoc_detail.html:61 msgid "Options" msgstr "选项" -#: ops/models.py:152 ops/templates/ops/adhoc_detail.html:53 -#: ops/templates/ops/task_adhoc.html:56 ops/templates/ops/task_list.html:37 +#: ops/models/adhoc.py:157 ops/templates/ops/adhoc_detail.html:53 +#: ops/templates/ops/task_adhoc.html:59 ops/templates/ops/task_list.html:37 msgid "Hosts" msgstr "主机" -#: ops/models.py:153 +#: ops/models/adhoc.py:158 msgid "Run as admin" msgstr "再次执行" -#: ops/models.py:154 ops/templates/ops/adhoc_detail.html:72 -#: ops/templates/ops/adhoc_detail.html:77 ops/templates/ops/task_adhoc.html:58 +#: ops/models/adhoc.py:159 ops/templates/ops/adhoc_detail.html:72 +#: ops/templates/ops/adhoc_detail.html:77 ops/templates/ops/task_adhoc.html:61 msgid "Run as" msgstr "用户" -#: ops/models.py:155 ops/templates/ops/adhoc_detail.html:82 -#: ops/templates/ops/task_adhoc.html:59 +#: ops/models/adhoc.py:160 ops/templates/ops/adhoc_detail.html:82 +#: ops/templates/ops/task_adhoc.html:62 msgid "Become" msgstr "Become" -#: ops/models.py:156 users/templates/users/user_group_detail.html:59 +#: ops/models/adhoc.py:161 users/templates/users/user_group_detail.html:59 msgid "Create by" msgstr "创建者" -#: ops/models.py:307 +#: ops/models/adhoc.py:323 msgid "Start time" msgstr "开始时间" -#: ops/models.py:308 +#: ops/models/adhoc.py:324 msgid "End time" msgstr "完成时间" -#: ops/models.py:309 ops/templates/ops/adhoc_history.html:57 -#: ops/templates/ops/task_history.html:60 ops/templates/ops/task_list.html:40 +#: ops/models/adhoc.py:325 ops/templates/ops/adhoc_history.html:57 +#: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:40 msgid "Time" msgstr "时间" -#: ops/models.py:310 ops/templates/ops/adhoc_detail.html:106 +#: ops/models/adhoc.py:326 ops/templates/ops/adhoc_detail.html:106 #: ops/templates/ops/adhoc_history.html:55 -#: ops/templates/ops/adhoc_history_detail.html:66 -#: ops/templates/ops/task_detail.html:80 ops/templates/ops/task_history.html:58 +#: ops/templates/ops/adhoc_history_detail.html:69 +#: ops/templates/ops/task_detail.html:83 ops/templates/ops/task_history.html:61 msgid "Is finished" msgstr "是否完成" -#: ops/models.py:311 ops/templates/ops/adhoc_history.html:56 -#: ops/templates/ops/task_history.html:59 +#: ops/models/adhoc.py:327 ops/templates/ops/adhoc_history.html:56 +#: ops/templates/ops/task_history.html:62 msgid "Is success" msgstr "是否成功" -#: ops/models.py:312 +#: ops/models/adhoc.py:328 msgid "Adhoc raw result" msgstr "结果" -#: ops/models.py:313 +#: ops/models/adhoc.py:329 msgid "Adhoc result summary" msgstr "汇总" @@ -1485,8 +1489,8 @@ msgid "Version run history" msgstr "执行历史" #: ops/templates/ops/adhoc_detail.html:49 -#: ops/templates/ops/adhoc_history_detail.html:46 -#: ops/templates/ops/task_detail.html:52 +#: ops/templates/ops/adhoc_history_detail.html:49 +#: ops/templates/ops/task_detail.html:55 #: terminal/templates/terminal/session_list.html:70 #: users/templates/users/login_log_list.html:48 msgid "ID" @@ -1496,59 +1500,59 @@ msgstr "ID" msgid "Run times" msgstr "执行次数" -#: ops/templates/ops/adhoc_detail.html:98 ops/templates/ops/task_detail.html:72 +#: ops/templates/ops/adhoc_detail.html:98 ops/templates/ops/task_detail.html:75 msgid "Last run" msgstr "最后运行" #: ops/templates/ops/adhoc_detail.html:102 -#: ops/templates/ops/adhoc_history_detail.html:62 -#: ops/templates/ops/task_detail.html:76 +#: ops/templates/ops/adhoc_history_detail.html:65 +#: ops/templates/ops/task_detail.html:79 msgid "Time delta" msgstr "运行时间" #: ops/templates/ops/adhoc_detail.html:110 -#: ops/templates/ops/adhoc_history_detail.html:70 -#: ops/templates/ops/task_detail.html:84 +#: ops/templates/ops/adhoc_history_detail.html:73 +#: ops/templates/ops/task_detail.html:87 msgid "Is success " msgstr "成功" #: ops/templates/ops/adhoc_detail.html:131 -#: ops/templates/ops/task_detail.html:105 +#: ops/templates/ops/task_detail.html:108 msgid "Last run failed hosts" msgstr "最后运行失败主机" #: ops/templates/ops/adhoc_detail.html:151 #: ops/templates/ops/adhoc_detail.html:176 -#: ops/templates/ops/task_detail.html:125 -#: ops/templates/ops/task_detail.html:150 +#: ops/templates/ops/task_detail.html:128 +#: ops/templates/ops/task_detail.html:153 msgid "No hosts" msgstr "没有主机" #: ops/templates/ops/adhoc_detail.html:161 -#: ops/templates/ops/task_detail.html:135 +#: ops/templates/ops/task_detail.html:138 msgid "Last run success hosts" msgstr "最后运行成功主机" #: ops/templates/ops/adhoc_history.html:30 -#: ops/templates/ops/task_history.html:33 +#: ops/templates/ops/task_history.html:36 msgid "History of " msgstr "执行历史" #: ops/templates/ops/adhoc_history.html:52 -#: ops/templates/ops/adhoc_history_detail.html:58 -#: ops/templates/ops/task_history.html:55 terminal/models.py:132 +#: ops/templates/ops/adhoc_history_detail.html:61 +#: ops/templates/ops/task_history.html:58 terminal/models.py:132 #: terminal/templates/terminal/session_list.html:77 msgid "Date start" msgstr "开始日期" #: ops/templates/ops/adhoc_history.html:53 -#: ops/templates/ops/task_history.html:56 +#: ops/templates/ops/task_history.html:59 msgid "F/S/T" msgstr "失败/成功/总" #: ops/templates/ops/adhoc_history.html:58 -#: ops/templates/ops/adhoc_history_detail.html:54 -#: ops/templates/ops/task_adhoc.html:55 ops/templates/ops/task_history.html:61 +#: ops/templates/ops/adhoc_history_detail.html:57 +#: ops/templates/ops/task_adhoc.html:58 ops/templates/ops/task_history.html:64 msgid "Version" msgstr "版本" @@ -1556,24 +1560,29 @@ msgstr "版本" msgid "Run history detail" msgstr "执行历史详情" -#: ops/templates/ops/adhoc_history_detail.html:27 +#: ops/templates/ops/adhoc_history_detail.html:22 +#: terminal/backends/command/models.py:14 +msgid "Output" +msgstr "输出" + +#: ops/templates/ops/adhoc_history_detail.html:30 msgid "History detail of" msgstr "执行历史详情" -#: ops/templates/ops/adhoc_history_detail.html:50 +#: ops/templates/ops/adhoc_history_detail.html:53 msgid "Task name" msgstr "任务名称" -#: ops/templates/ops/adhoc_history_detail.html:81 +#: ops/templates/ops/adhoc_history_detail.html:84 msgid "Failed assets" msgstr "失败资产" -#: ops/templates/ops/adhoc_history_detail.html:101 -#: ops/templates/ops/adhoc_history_detail.html:126 +#: ops/templates/ops/adhoc_history_detail.html:104 +#: ops/templates/ops/adhoc_history_detail.html:129 msgid "No assets" msgstr "没有资产" -#: ops/templates/ops/adhoc_history_detail.html:111 +#: ops/templates/ops/adhoc_history_detail.html:114 msgid "Success assets" msgstr "成功资产" @@ -1592,25 +1601,30 @@ msgstr "任务各版本" msgid "Run history" msgstr "执行历史" -#: ops/templates/ops/task_adhoc.html:33 +#: ops/templates/ops/task_adhoc.html:28 ops/templates/ops/task_detail.html:28 +#: ops/templates/ops/task_history.html:28 +msgid "Last run output" +msgstr "输出" + +#: ops/templates/ops/task_adhoc.html:36 msgid "Versions of " msgstr "版本" -#: ops/templates/ops/task_adhoc.html:60 +#: ops/templates/ops/task_adhoc.html:63 #: terminal/templates/terminal/command_list.html:76 #: terminal/templates/terminal/session_detail.html:50 msgid "Datetime" msgstr "日期" -#: ops/templates/ops/task_detail.html:64 +#: ops/templates/ops/task_detail.html:67 msgid "Total versions" msgstr "版本数量" -#: ops/templates/ops/task_detail.html:68 +#: ops/templates/ops/task_detail.html:71 msgid "Latest version" msgstr "最新版本" -#: ops/templates/ops/task_detail.html:88 +#: ops/templates/ops/task_detail.html:91 msgid "Contents" msgstr "内容" @@ -1639,7 +1653,7 @@ msgstr "日期" msgid "Run" msgstr "执行" -#: ops/templates/ops/task_list.html:123 +#: ops/templates/ops/task_list.html:124 msgid "Task start: " msgstr "任务开始: " @@ -1705,7 +1719,7 @@ msgstr "添加" msgid "Add asset group to this permission" msgstr "添加资产组" -#: perms/templates/perms/asset_permission_asset.html:116 users/forms.py:273 +#: perms/templates/perms/asset_permission_asset.html:116 users/forms.py:275 msgid "Select asset groups" msgstr "选择资产组" @@ -1734,7 +1748,7 @@ msgstr "资产组数量" msgid "System user count" msgstr "系统用户数量" -#: perms/templates/perms/asset_permission_detail.html:144 users/forms.py:276 +#: perms/templates/perms/asset_permission_detail.html:144 users/forms.py:278 msgid "Select system users" msgstr "选择系统用户" @@ -1787,7 +1801,7 @@ msgstr "商业支持" msgid "Docs" msgstr "文档" -#: templates/_header_bar.html:37 templates/_nav_user.html:9 users/forms.py:92 +#: templates/_header_bar.html:37 templates/_nav_user.html:9 users/forms.py:94 #: users/templates/users/_user.html:36 #: users/templates/users/user_password_update.html:37 #: users/templates/users/user_profile.html:17 @@ -1912,10 +1926,6 @@ msgstr "过滤" msgid "Input" msgstr "输入" -#: terminal/backends/command/models.py:14 -msgid "Output" -msgstr "输出" - #: terminal/backends/command/models.py:15 #: terminal/templates/terminal/command_list.html:75 #: terminal/templates/terminal/terminal_list.html:33 @@ -2179,11 +2189,11 @@ msgstr "" msgid "Role" msgstr "角色" -#: users/forms.py:30 users/forms.py:138 +#: users/forms.py:30 users/forms.py:140 msgid "ssh public key" msgstr "ssh公钥" -#: users/forms.py:31 users/forms.py:139 +#: users/forms.py:31 users/forms.py:141 msgid "ssh-rsa AAAA..." msgstr "" @@ -2195,39 +2205,39 @@ msgstr "复制用户公钥到这里" msgid "Join user groups" msgstr "添加到用户组" -#: users/forms.py:58 users/forms.py:153 +#: users/forms.py:60 users/forms.py:155 msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" -#: users/forms.py:62 users/forms.py:157 users/serializers.py:42 +#: users/forms.py:64 users/forms.py:159 users/serializers.py:42 msgid "Not a valid ssh public key" msgstr "ssh密钥不合法" -#: users/forms.py:98 +#: users/forms.py:100 msgid "Old password" msgstr "原来密码" -#: users/forms.py:103 +#: users/forms.py:105 msgid "New password" msgstr "新密码" -#: users/forms.py:108 +#: users/forms.py:110 msgid "Confirm password" msgstr "确认密码" -#: users/forms.py:118 +#: users/forms.py:120 msgid "Old password error" msgstr "原来密码错误" -#: users/forms.py:126 +#: users/forms.py:128 msgid "Password does not match" msgstr "密码不一致" -#: users/forms.py:140 +#: users/forms.py:142 msgid "Paste your id_rsa.pub here." msgstr "复制你的公钥到这里" -#: users/forms.py:168 users/models/user.py:46 +#: users/forms.py:170 users/models/user.py:46 #: users/templates/users/user_password_update.html:43 #: users/templates/users/user_profile.html:71 #: users/templates/users/user_profile_update.html:43 @@ -2235,7 +2245,7 @@ msgstr "复制你的公钥到这里" msgid "Public key" msgstr "ssh公钥" -#: users/forms.py:175 users/forms.py:180 users/forms.py:192 users/forms.py:222 +#: users/forms.py:177 users/forms.py:182 users/forms.py:194 users/forms.py:224 msgid "Select users" msgstr "选择用户" @@ -2777,4 +2787,3 @@ msgstr "密码更新" #: users/views/user.py:375 msgid "Public key update" msgstr "密钥更新" - diff --git a/apps/ops/api.py b/apps/ops/api.py index db27653cb..0134fbd4a 100644 --- a/apps/ops/api.py +++ b/apps/ops/api.py @@ -6,18 +6,15 @@ from django.core.cache import cache from django.shortcuts import get_object_or_404 from django.utils.translation import ugettext as _ from rest_framework import viewsets, generics -from rest_framework.views import APIView from rest_framework.views import Response from .hands import IsSuperUser -from common.const import FILE_END_GUARD -from .models import Task, AdHoc, AdHocRunHistory -from .serializers import TaskSerializer, AdHocSerializer, AdHocRunHistorySerializer +from .models import Task, AdHoc, AdHocRunHistory, CeleryTask +from .serializers import TaskSerializer, AdHocSerializer, \ + AdHocRunHistorySerializer from .tasks import run_ansible_task - - class TaskViewSet(viewsets.ModelViewSet): queryset = Task.objects.all() serializer_class = TaskSerializer @@ -67,28 +64,28 @@ class AdHocRunHistorySet(viewsets.ModelViewSet): return self.queryset -class LogFileViewApi(APIView): +class CeleryTaskLogApi(generics.RetrieveAPIView): permission_classes = (IsSuperUser,) buff_size = 1024 * 10 end = False + queryset = CeleryTask.objects.all() def get(self, request, *args, **kwargs): - file_path = request.query_params.get("file") mark = request.query_params.get("mark") or str(uuid.uuid4()) + task = super().get_object() + log_path = task.full_log_path - if not os.path.isfile(file_path): - print(file_path) - return Response({"error": _("Log file not found")}, status=204) + if not log_path or not os.path.isfile(log_path): + return Response({"data": _("Waiting ...")}, status=203) - with open(file_path, 'r') as f: + with open(log_path, 'r') as f: offset = cache.get(mark, 0) f.seek(offset) data = f.read(self.buff_size).replace('\n', '\r\n') mark = str(uuid.uuid4()) cache.set(mark, f.tell(), 5) - if FILE_END_GUARD in data: - data.replace(FILE_END_GUARD, '') + if data == '' and task.is_finished(): self.end = True - return Response({"data": data, 'end': self.end, 'mark': mark}) + diff --git a/apps/ops/apps.py b/apps/ops/apps.py index 35f09c4b7..d5d6879b1 100644 --- a/apps/ops/apps.py +++ b/apps/ops/apps.py @@ -5,3 +5,7 @@ from django.apps import AppConfig class OpsConfig(AppConfig): name = 'ops' + + def ready(self): + super().ready() + from .celery import signal_handler diff --git a/apps/ops/celery/__init__.py b/apps/ops/celery/__init__.py index 9cdcb3063..04f48299e 100644 --- a/apps/ops/celery/__init__.py +++ b/apps/ops/celery/__init__.py @@ -15,4 +15,3 @@ app = Celery('jumpserver') # pickle the object when using Windows. app.config_from_object('django.conf:settings', namespace='CELERY') app.autodiscover_tasks(lambda: [app_config.split('.')[0] for app_config in settings.INSTALLED_APPS]) - diff --git a/apps/ops/celery/signal_handler.py b/apps/ops/celery/signal_handler.py index 6ce6c5d9d..8309c0246 100644 --- a/apps/ops/celery/signal_handler.py +++ b/apps/ops/celery/signal_handler.py @@ -5,27 +5,21 @@ import datetime import sys from django.conf import settings +from django.utils import timezone from django.core.cache import cache +from django.db import transaction from celery import subtask from celery.signals import worker_ready, worker_shutdown, task_prerun, \ task_postrun, after_task_publish - from django_celery_beat.models import PeriodicTask -from common.utils import get_logger, TeeObj +from common.utils import get_logger, TeeObj, get_object_or_none from common.const import celery_task_pre_key - from .utils import get_after_app_ready_tasks, get_after_app_shutdown_clean_tasks - +from ..models import CeleryTask logger = get_logger(__file__) -WAITING = "waiting" -RUNNING = "running" -FINISHED = "finished" - -EXPIRE_TIME = 3600 - @worker_ready.connect def on_app_ready(sender=None, headers=None, body=None, **kwargs): @@ -54,18 +48,31 @@ def after_app_shutdown(sender=None, headers=None, body=None, **kwargs): PeriodicTask.objects.filter(name__in=tasks).delete() +@after_task_publish.connect +def after_task_publish_signal_handler(sender, headers=None, **kwargs): + CeleryTask.objects.create( + id=headers["id"], status=CeleryTask.WAITING, name=headers["task"] + ) + + @task_prerun.connect def pre_run_task_signal_handler(sender, task_id=None, task=None, **kwargs): - task_key = celery_task_pre_key + task_id - info = cache.get(task_key, {}) + t = get_object_or_none(CeleryTask, id=task_id) + if t is None: + logger.warn("Not get the task: {}".format(task_id)) + return now = datetime.datetime.now().strftime("%Y-%m-%d") - log_dir = os.path.join(settings.PROJECT_DIR, "data", "celery", now) - if not os.path.exists(log_dir): - os.makedirs(log_dir) - log_path = os.path.join(log_dir, task_id + '.log') - info.update({"status": RUNNING, "log_path": log_path}) - cache.set(task_key, info, EXPIRE_TIME) - f = open(log_path, 'w') + log_path = os.path.join(now, task_id + '.log') + full_path = os.path.join(CeleryTask.LOG_DIR, log_path) + + if not os.path.exists(os.path.dirname(full_path)): + os.makedirs(os.path.dirname(full_path)) + with transaction.atomic(): + t.date_start = timezone.now() + t.status = CeleryTask.RUNNING + t.log_path = log_path + t.save() + f = open(full_path, 'w') tee = TeeObj(f) sys.stdout = tee task.log_f = tee @@ -73,17 +80,15 @@ def pre_run_task_signal_handler(sender, task_id=None, task=None, **kwargs): @task_postrun.connect def post_run_task_signal_handler(sender, task_id=None, task=None, **kwargs): - task_key = celery_task_pre_key + task_id - info = cache.get(task_key, {}) - info.update({"status": FINISHED}) - cache.set(task_key, info, EXPIRE_TIME) + t = get_object_or_none(CeleryTask, id=task_id) + if t is None: + logger.warn("Not get the task: {}".format(task_id)) + return + with transaction.atomic(): + t.status = CeleryTask.FINISHED + t.date_finished = timezone.now() + t.save() task.log_f.flush() sys.stdout = task.log_f.origin_stdout task.log_f.close() - -@after_task_publish.connect -def after_task_publish_signal_handler(sender, headers=None, **kwargs): - task_id = headers["id"] - key = celery_task_pre_key + task_id - cache.set(key, {"status": WAITING}, EXPIRE_TIME) \ No newline at end of file diff --git a/apps/ops/models/__init__.py b/apps/ops/models/__init__.py new file mode 100644 index 000000000..68920eb42 --- /dev/null +++ b/apps/ops/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# + +from .adhoc import * +from .celery import * \ No newline at end of file diff --git a/apps/ops/models.py b/apps/ops/models/adhoc.py similarity index 97% rename from apps/ops/models.py rename to apps/ops/models/adhoc.py index 636d27b0c..beba34fe6 100644 --- a/apps/ops/models.py +++ b/apps/ops/models/adhoc.py @@ -11,15 +11,14 @@ from django.db import models from django.conf import settings from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from django_celery_beat.models import CrontabSchedule, IntervalSchedule, \ - PeriodicTask +from django_celery_beat.models import PeriodicTask from common.utils import get_signer, get_logger -from .celery.utils import delete_celery_periodic_task, \ +from ..celery.utils import delete_celery_periodic_task, \ create_or_update_celery_periodic_tasks, \ disable_celery_periodic_task -from .ansible import AdHocRunner, AnsibleError -from .inventory import JMSInventory +from ..ansible import AdHocRunner, AnsibleError +from ..inventory import JMSInventory __all__ = ["Task", "AdHoc", "AdHocRunHistory"] @@ -91,7 +90,7 @@ class Task(models.Model): def save(self, force_insert=False, force_update=False, using=None, update_fields=None): - from .tasks import run_ansible_task + from ..tasks import run_ansible_task super().save( force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields, diff --git a/apps/ops/models/celery.py b/apps/ops/models/celery.py new file mode 100644 index 000000000..0e3b99d19 --- /dev/null +++ b/apps/ops/models/celery.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# +import uuid +import os +from django.conf import settings +from django.db import models + + +class CeleryTask(models.Model): + WAITING = "waiting" + RUNNING = "running" + FINISHED = "finished" + LOG_DIR = os.path.join(settings.PROJECT_DIR, 'data', 'celery') + + STATUS_CHOICES = ( + (WAITING, WAITING), + (RUNNING, RUNNING), + (FINISHED, FINISHED), + ) + id = models.UUIDField(primary_key=True, default=uuid.uuid4) + name = models.CharField(max_length=1024) + status = models.CharField(max_length=128, choices=STATUS_CHOICES) + log_path = models.CharField(max_length=256, blank=True, null=True) + date_published = models.DateTimeField(auto_now_add=True) + date_start = models.DateTimeField(null=True) + date_finished = models.DateTimeField(null=True) + + def __str__(self): + return "{}: {}".format(self.name, self.id) + + def is_finished(self): + return self.status == self.FINISHED + + @property + def full_log_path(self): + return os.path.join(self.LOG_DIR, self.log_path) diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index 5a2738cd9..8df803207 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -1,7 +1,6 @@ # coding: utf-8 from celery import shared_task, subtask - from common.utils import get_logger, get_object_or_none from .models import Task diff --git a/apps/ops/templates/ops/adhoc_history_detail.html b/apps/ops/templates/ops/adhoc_history_detail.html index 15c26376f..16adbc4e3 100644 --- a/apps/ops/templates/ops/adhoc_history_detail.html +++ b/apps/ops/templates/ops/adhoc_history_detail.html @@ -19,7 +19,7 @@ {% trans 'Run history detail' %}
  • - {% trans 'History output' %} + {% trans 'Output' %}
  • diff --git a/apps/ops/templates/ops/adhoc_history_output.html b/apps/ops/templates/ops/adhoc_history_output.html deleted file mode 100644 index 781c2adf5..000000000 --- a/apps/ops/templates/ops/adhoc_history_output.html +++ /dev/null @@ -1,37 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - - - - -{% endblock %} -{% block content %} -
    - -
    -{% endblock %} - diff --git a/apps/common/templates/common/celery_task_log.html b/apps/ops/templates/ops/celery_task_log.html similarity index 97% rename from apps/common/templates/common/celery_task_log.html rename to apps/ops/templates/ops/celery_task_log.html index 23b676dd2..9b0826949 100644 --- a/apps/common/templates/common/celery_task_log.html +++ b/apps/ops/templates/ops/celery_task_log.html @@ -35,7 +35,7 @@ var rowHeight = 1; var colWidth = 1; var mark = ''; - var url = "{% url 'api-common:celery-task-log' pk=task_id %}"; + var url = "{% url 'api-ops:celery-task-log' pk=object.id %}"; var term; var end = false; var error = false; diff --git a/apps/ops/templates/ops/celery_task_output.html b/apps/ops/templates/ops/celery_task_output.html deleted file mode 100644 index d48b35dcf..000000000 --- a/apps/ops/templates/ops/celery_task_output.html +++ /dev/null @@ -1,94 +0,0 @@ -{% load static %} - - term.js - - - -
    -
    -
    -
    - - - - diff --git a/apps/ops/templates/ops/task_adhoc.html b/apps/ops/templates/ops/task_adhoc.html index 381048602..2762e3f8c 100644 --- a/apps/ops/templates/ops/task_adhoc.html +++ b/apps/ops/templates/ops/task_adhoc.html @@ -24,6 +24,9 @@
  • {% trans 'Run history' %}
  • +
  • + {% trans 'Last run output' %} +
  • diff --git a/apps/ops/templates/ops/task_detail.html b/apps/ops/templates/ops/task_detail.html index 4aba9998a..f6e832bd7 100644 --- a/apps/ops/templates/ops/task_detail.html +++ b/apps/ops/templates/ops/task_detail.html @@ -25,7 +25,7 @@ {% trans 'Run history' %}
  • - {% trans 'Last run output' %} + {% trans 'Last run output' %}
  • diff --git a/apps/ops/templates/ops/task_history.html b/apps/ops/templates/ops/task_history.html index 92401a0f9..9604b3402 100644 --- a/apps/ops/templates/ops/task_history.html +++ b/apps/ops/templates/ops/task_history.html @@ -24,6 +24,9 @@
  • {% trans 'Run history' %}
  • +
  • + {% trans 'Last run output' %} +
  • diff --git a/apps/ops/templates/ops/task_list.html b/apps/ops/templates/ops/task_list.html index af4c8ea8f..4dc24e7ba 100644 --- a/apps/ops/templates/ops/task_list.html +++ b/apps/ops/templates/ops/task_list.html @@ -113,8 +113,8 @@ $(document).ready(function() { }; var success = function(data) { var task_id = data.task; - var url = '{% url "common:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); - window.open(url, '', 'width=800,height=800') + var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); + window.open(url, '', 'width=800,height=600') }; APIUpdateAttr({ url: the_url, diff --git a/apps/ops/urls/api_urls.py b/apps/ops/urls/api_urls.py index a5f94da29..d6390fed9 100644 --- a/apps/ops/urls/api_urls.py +++ b/apps/ops/urls/api_urls.py @@ -15,7 +15,7 @@ router.register(r'v1/history', api.AdHocRunHistorySet, 'history') urlpatterns = [ url(r'^v1/tasks/(?P[0-9a-zA-Z\-]{36})/run/$', api.TaskRun.as_view(), name='task-run'), - # url(r'^v1/history/(?P[0-9a-zA-Z\-]{36})/output/$', api.CeleryTaskOutputApi.as_view(), name='history-output'), + url(r'^v1/celery/task/(?P[0-9a-zA-Z\-]{36})/log/$', api.CeleryTaskLogApi.as_view(), name='celery-task-log'), ] urlpatterns += router.urls diff --git a/apps/ops/urls/view_urls.py b/apps/ops/urls/view_urls.py index e6979bbf3..470d6f06d 100644 --- a/apps/ops/urls/view_urls.py +++ b/apps/ops/urls/view_urls.py @@ -18,6 +18,5 @@ urlpatterns = [ url(r'^adhoc/(?P[0-9a-zA-Z\-]{36})/$', views.AdHocDetailView.as_view(), name='adhoc-detail'), url(r'^adhoc/(?P[0-9a-zA-Z\-]{36})/history/$', views.AdHocHistoryView.as_view(), name='adhoc-history'), url(r'^adhoc/history/(?P[0-9a-zA-Z\-]{36})/$', views.AdHocHistoryDetailView.as_view(), name='adhoc-history-detail'), - url(r'^adhoc/history/(?P[0-9a-zA-Z\-]{36})/_output/$', views.CeleryTaskOutputView.as_view(), name='adhoc-history-output-alone'), - url(r'^adhoc/history/(?P[0-9a-zA-Z\-]{36})/output/$', views.AdHocHistoryOutputView.as_view(), name='adhoc-history-output'), + url(r'^celery/task/(?P[0-9a-zA-Z\-]{36})/log/$', views.CeleryTaskLogView.as_view(), name='celery-task-log'), ] diff --git a/apps/ops/views.py b/apps/ops/views.py index 5a294e7c7..e3ba2789a 100644 --- a/apps/ops/views.py +++ b/apps/ops/views.py @@ -5,7 +5,7 @@ from django.conf import settings from django.views.generic import ListView, DetailView, TemplateView from common.mixins import DatetimeSearchMixin -from .models import Task, AdHoc, AdHocRunHistory +from .models import Task, AdHoc, AdHocRunHistory, CeleryTask from .hands import AdminUserRequiredMixin @@ -121,19 +121,6 @@ class AdHocHistoryDetailView(AdminUserRequiredMixin, DetailView): return super().get_context_data(**kwargs) -class CeleryTaskOutputView(AdminUserRequiredMixin, TemplateView): - model = AdHocRunHistory - template_name = 'ops/celery_task_output.html' - - -class AdHocHistoryOutputView(AdminUserRequiredMixin, DetailView): - model = AdHocRunHistory - template_name = 'ops/adhoc_history_output.html' - - def get_context_data(self, **kwargs): - context = { - 'app': _('Ops'), - 'action': _('Run history detail'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) \ No newline at end of file +class CeleryTaskLogView(AdminUserRequiredMixin, DetailView): + template_name = 'ops/celery_task_log.html' + model = CeleryTask From 24c4e1df504b3741e118bd20f774bf0495ae3fe2 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 2 Apr 2018 16:55:39 +0800 Subject: [PATCH 6/8] =?UTF-8?q?[Update]=20=E8=B5=84=E4=BA=A7=EF=BC=8C?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E7=94=A8=E6=88=B7=EF=BC=8C=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=AD=89=E6=94=AF=E6=8C=81=E6=9F=A5=E7=9C=8B?= =?UTF-8?q?=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/admin_user.py | 4 ++-- apps/assets/api/asset.py | 15 ++++-------- apps/assets/api/node.py | 9 ++++--- apps/assets/api/system_user.py | 8 +++---- apps/assets/tasks.py | 5 ++-- .../templates/assets/admin_user_assets.html | 10 ++++---- .../assets/templates/assets/asset_detail.html | 24 +++++++++---------- apps/assets/templates/assets/asset_list.html | 18 +++++++++----- .../templates/assets/system_user_detail.html | 20 +++++++++------- apps/ops/templates/ops/task_list.html | 3 ++- apps/static/js/jumpserver.js | 6 +++-- 11 files changed, 65 insertions(+), 57 deletions(-) diff --git a/apps/assets/api/admin_user.py b/apps/assets/api/admin_user.py index e32bbe02a..968cd6594 100644 --- a/apps/assets/api/admin_user.py +++ b/apps/assets/api/admin_user.py @@ -79,5 +79,5 @@ class AdminUserTestConnectiveApi(generics.RetrieveAPIView): def retrieve(self, request, *args, **kwargs): admin_user = self.get_object() - test_admin_user_connectability_manual.delay(admin_user) - return Response({"msg": "Task created"}) \ No newline at end of file + task = test_admin_user_connectability_manual.delay(admin_user) + return Response({"task": task.id}) diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 405beaf53..9520ff120 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -87,12 +87,8 @@ class AssetRefreshHardwareApi(generics.RetrieveAPIView): def retrieve(self, request, *args, **kwargs): asset_id = kwargs.get('pk') asset = get_object_or_404(Asset, pk=asset_id) - summary = update_asset_hardware_info_manual(asset)[1] - logger.debug("Refresh summary: {}".format(summary)) - if summary.get('dark'): - return Response(summary['dark'].values(), status=501) - else: - return Response({"msg": "ok"}) + task = update_asset_hardware_info_manual.delay(asset) + return Response({"task": task.id}) class AssetAdminUserTestApi(generics.RetrieveAPIView): @@ -105,8 +101,5 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView): def retrieve(self, request, *args, **kwargs): asset_id = kwargs.get('pk') asset = get_object_or_404(Asset, pk=asset_id) - ok, msg = test_asset_connectability_manual(asset) - if ok: - return Response({"msg": "pong"}) - else: - return Response({"error": msg}, status=502) \ No newline at end of file + task = test_asset_connectability_manual.delay(asset) + return Response({"task": task.id}) diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index 9c002b271..3b61ceb51 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -130,10 +130,9 @@ class RefreshNodeHardwareInfoApi(APIView): node_id = kwargs.get('pk') node = get_object_or_404(self.model, id=node_id) assets = node.assets.all() - # task_name = _("Refresh node assets hardware info: {}".format(node.name)) task_name = _("更新节点资产硬件信息: {}".format(node.name)) - update_assets_hardware_info_util.delay(assets, task_name=task_name) - return Response({"msg": "Task created"}) + task = update_assets_hardware_info_util.delay(assets, task_name=task_name) + return Response({"task": task.id}) class TestNodeConnectiveApi(APIView): @@ -145,6 +144,6 @@ class TestNodeConnectiveApi(APIView): node = get_object_or_404(self.model, id=node_id) assets = node.assets.all() task_name = _("测试节点下资产是否可连接: {}".format(node.name)) - test_asset_connectability_util.delay(assets, task_name=task_name) - return Response({"msg": "Task created"}) + task = test_asset_connectability_util.delay(assets, task_name=task_name) + return Response({"task": task.id}) diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index ca2a6b7f0..b4e46cc78 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -58,8 +58,8 @@ class SystemUserPushApi(generics.RetrieveAPIView): def retrieve(self, request, *args, **kwargs): system_user = self.get_object() - push_system_user_to_assets_manual.delay(system_user) - return Response({"msg": "Task created"}) + task = push_system_user_to_assets_manual.delay(system_user) + return Response({"task": task.id}) class SystemUserTestConnectiveApi(generics.RetrieveAPIView): @@ -71,5 +71,5 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView): def retrieve(self, request, *args, **kwargs): system_user = self.get_object() - test_system_user_connectability_manual.delay(system_user) - return Response({"msg": "Task created"}) \ No newline at end of file + task = test_system_user_connectability_manual.delay(system_user) + return Response({"task": task.id}) diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py index bfdafe734..53a54764d 100644 --- a/apps/assets/tasks.py +++ b/apps/assets/tasks.py @@ -215,7 +215,7 @@ def test_admin_user_connectability_period(): def test_admin_user_connectability_manual(admin_user): # task_name = _("Test admin user connectability: {}").format(admin_user.name) task_name = _("测试管理行号可连接性: {}").format(admin_user.name) - return test_admin_user_connectability_util.delay(admin_user, task_name) + return test_admin_user_connectability_util(admin_user, task_name) @shared_task @@ -395,11 +395,12 @@ def get_node_push_system_user_task_name(system_user, node): ) +@shared_task def push_system_user_to_node(system_user, node): logger.info("Start push system user node: {} => {}".format(system_user.name, node.value)) assets = node.get_all_assets() task_name = get_node_push_system_user_task_name(system_user, node) - push_system_user_util.delay([system_user], assets, task_name) + push_system_user_util([system_user], assets, task_name) @shared_task diff --git a/apps/assets/templates/assets/admin_user_assets.html b/apps/assets/templates/assets/admin_user_assets.html index fa72abaa5..80ff0cd5f 100644 --- a/apps/assets/templates/assets/admin_user_assets.html +++ b/apps/assets/templates/assets/admin_user_assets.html @@ -121,14 +121,16 @@ $(document).ready(function () { }) .on('click', '.btn-test-connective', function () { var the_url = "{% url 'api-assets:admin-user-connective' pk=admin_user.id %}"; - var error = function (data) { - alert(data) + var success = function (data) { + var task_id = data.task; + var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); + window.open(url, '', 'width=800,height=600') }; APIUpdateAttr({ url: the_url, - error: error, method: 'GET', - success_message: "{% trans 'Task has been send, seen left asset status' %}" + success: success, + flash_message: false }); }) diff --git a/apps/assets/templates/assets/asset_detail.html b/apps/assets/templates/assets/asset_detail.html index 9767320a3..fd57d9643 100644 --- a/apps/assets/templates/assets/asset_detail.html +++ b/apps/assets/templates/assets/asset_detail.html @@ -269,16 +269,15 @@ function updateAssetNodes(nodes) { function refreshAssetHardware() { var the_url = "{% url 'api-assets:asset-refresh' pk=asset.id %}"; - var success = function (data) { - location.reload(); - }; - var error = function (data) { - alert(data) + var success = function(data) { + console.log(data); + var task_id = data.task; + var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); + window.open(url, '', 'width=800,height=600') }; APIUpdateAttr({ url: the_url, success: success, - error: error, method: 'GET' }); } @@ -344,19 +343,20 @@ $(document).ready(function () { var redirect_url = "{% url 'assets:asset-list' %}"; objectDelete($this, name, the_url, redirect_url); }).on('click', '#btn_refresh_asset', function () { - alert('关闭alert, 等待完成, 自动刷新页面'); refreshAssetHardware() }).on('click', '#btn-test-is-alive', function () { var the_url = "{% url 'api-assets:asset-alive-test' pk=asset.id %}"; - var error = function (data) { - alert(data) + + var success = function(data) { + var task_id = data.task; + var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); + window.open(url, '', 'width=800,height=600') }; - alert('关闭alert, 等待完成'); + APIUpdateAttr({ url: the_url, - error: error, method: 'GET', - success_message: "{% trans "Reachable" %}" + success: success }); }) diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index ec905b119..25e206c0c 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -497,14 +497,17 @@ $(document).ready(function(){ } var the_url = url.replace("{{ DEFAULT_PK }}", current_node.id); - function success() { + function success(data) { rMenu.css({"visibility" : "hidden"}); + var task_id = data.task; + var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); + window.open(url, '', 'width=800,height=600') } APIUpdateAttr({ url: the_url, method: "GET", - success_message: "更新硬件信息任务下发成功", - success: success + success: success, + flash_message: false }); }) @@ -519,14 +522,17 @@ $(document).ready(function(){ } var the_url = url.replace("{{ DEFAULT_PK }}", current_node.id); - function success() { + function success(data) { rMenu.css({"visibility" : "hidden"}); + var task_id = data.task; + var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); + window.open(url, '', 'width=800,height=600') } APIUpdateAttr({ url: the_url, method: "GET", - success_message: "测试可连接性任务下发成功", - success: success + success: success, + flash_message: false }); }) .on('click', '.btn_asset_delete', function () { diff --git a/apps/assets/templates/assets/system_user_detail.html b/apps/assets/templates/assets/system_user_detail.html index b74b402cf..3572cfd3d 100644 --- a/apps/assets/templates/assets/system_user_detail.html +++ b/apps/assets/templates/assets/system_user_detail.html @@ -293,26 +293,30 @@ $(document).ready(function () { }) .on('click', '.btn-push', function () { var the_url = "{% url 'api-assets:system-user-push' pk=system_user.id %}"; - var error = function (data) { - alert(data) + var success = function (data) { + var task_id = data.task; + var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); + window.open(url, '', 'width=800,height=600') }; APIUpdateAttr({ url: the_url, - error: error, method: 'GET', - success_message: "{% trans "Task has been send, Go to ops task list seen result" %}" + success: success, + flash_message: false }); }) .on('click', '.btn-test-connective', function () { var the_url = "{% url 'api-assets:system-user-connective' pk=system_user.id %}"; - var error = function (data) { - alert(data) + var success = function (data) { + var task_id = data.task; + var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id); + window.open(url, '', 'width=800,height=600') }; APIUpdateAttr({ url: the_url, - error: error, method: 'GET', - success_message: "{% trans "Task has been send, seen left assets status" %}" + success: success, + flash_message: false }); }) diff --git a/apps/ops/templates/ops/task_list.html b/apps/ops/templates/ops/task_list.html index 4dc24e7ba..bd0748695 100644 --- a/apps/ops/templates/ops/task_list.html +++ b/apps/ops/templates/ops/task_list.html @@ -1,8 +1,9 @@ {% extends '_base_list.html' %} {% load i18n %} {% load static %} + {% block content_left_head %} - + {% endblock %} diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index c4d18815b..984bdfe9a 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -157,7 +157,7 @@ function APIUpdateAttr(props) { props = props || {}; var success_message = props.success_message || '更新成功!'; var fail_message = props.fail_message || '更新时发生未知错误.'; - var flash_message = true; + var flash_message = props.flash_message || true; if (props.flash_message === false){ flash_message = false; } @@ -170,7 +170,9 @@ function APIUpdateAttr(props) { dataType: props.data_type || "json" }).done(function(data, textStatue, jqXHR) { if (flash_message) { - toastr.success(success_message); + if (send_message) { + toastr.success(success_message); + } } if (typeof props.success === 'function') { return props.success(data); From 0b812a03c6cb98f2386a539e9f947c2606a0a075 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 3 Apr 2018 10:36:11 +0800 Subject: [PATCH 7/8] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8D=20ApiUpdateAt?= =?UTF-8?q?tr=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/static/js/jumpserver.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 984bdfe9a..1083f3520 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -170,9 +170,7 @@ function APIUpdateAttr(props) { dataType: props.data_type || "json" }).done(function(data, textStatue, jqXHR) { if (flash_message) { - if (send_message) { - toastr.success(success_message); - } + toastr.success(success_message); } if (typeof props.success === 'function') { return props.success(data); From 168335a3814b288b91e0ec8279aa345716a162fb Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 3 Apr 2018 12:14:58 +0800 Subject: [PATCH 8/8] =?UTF-8?q?[Update]=20=E6=9B=B4=E6=96=B0=E5=86=85?= =?UTF-8?q?=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/templates/assets/domain_gateway_list.html | 4 ++-- apps/assets/templates/assets/domain_list.html | 6 +++++- apps/ops/templates/ops/task_list.html | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/assets/templates/assets/domain_gateway_list.html b/apps/assets/templates/assets/domain_gateway_list.html index 1f25e6ac1..3dc82652f 100644 --- a/apps/assets/templates/assets/domain_gateway_list.html +++ b/apps/assets/templates/assets/domain_gateway_list.html @@ -95,8 +95,8 @@ function initTable() { ], ajax_url: '{% url "api-assets:gateway-list" %}?domain={{ object.id }}', columns: [ - {data: "id"}, {data: "name" }, {data: 'ip'}, {data: 'port'}, {data: "username" }, - {data: "protocol"}, {data: "comment" }, {data: "id"} + {data: "id"}, {data: "name" }, {data: 'ip'}, {data: 'port'}, + {data: "protocol"}, {data: "username" }, {data: "comment" }, {data: "id"} ], op_html: $('#actions').html() }; diff --git a/apps/assets/templates/assets/domain_list.html b/apps/assets/templates/assets/domain_list.html index 03b671bf3..926c4bbc3 100644 --- a/apps/assets/templates/assets/domain_list.html +++ b/apps/assets/templates/assets/domain_list.html @@ -33,7 +33,11 @@ function initTable() { var detail_btn = '' + cellData + ''; $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); }}, - + {targets: 3, createdCell: function (td, cellData, rowData) { + var gateway_list_btn = '' + cellData + ''; + gateway_list_btn = gateway_list_btn.replace("{{ DEFAULT_PK }}", rowData.id); + $(td).html(gateway_list_btn); + }}, {targets: 5, createdCell: function (td, cellData, rowData) { var update_btn = '{% trans "Update" %}'.replace('{{ DEFAULT_PK }}', cellData); var del_btn = '{% trans "Delete" %}'.replace('{{ DEFAULT_PK }}', cellData); diff --git a/apps/ops/templates/ops/task_list.html b/apps/ops/templates/ops/task_list.html index bd0748695..2b65c9e3e 100644 --- a/apps/ops/templates/ops/task_list.html +++ b/apps/ops/templates/ops/task_list.html @@ -3,7 +3,7 @@ {% load static %} {% block content_left_head %} - +{# #} {% endblock %}