diff --git a/apps/assets/migrations/0044_platform.py b/apps/assets/migrations/0044_platform.py
index f66d00642..8d45a8ee3 100644
--- a/apps/assets/migrations/0044_platform.py
+++ b/apps/assets/migrations/0044_platform.py
@@ -40,6 +40,9 @@ class Migration(migrations.Migration):
                 ('internal', models.BooleanField(default=False, verbose_name='Internal')),
                 ('comment', models.TextField(blank=True, null=True, verbose_name='Comment')),
             ],
+            options={
+                'verbose_name': 'Platform'
+            }
         ),
         migrations.RunPython(create_internal_platform)
     ]
diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py
index b49813c27..895e0134d 100644
--- a/apps/assets/models/base.py
+++ b/apps/assets/models/base.py
@@ -11,14 +11,13 @@ from django.utils.translation import ugettext_lazy as _
 from django.conf import settings
 
 from common.utils import (
-    get_signer, ssh_key_string_to_obj, ssh_key_gen, get_logger
+    signer, ssh_key_string_to_obj, ssh_key_gen, get_logger
 )
 from common.validators import alphanumeric
 from common import fields
 from orgs.mixins.models import OrgModelMixin
 from .utils import private_key_validator, Connectivity
 
-signer = get_signer()
 
 logger = get_logger(__file__)
 
diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py
index 443c32981..dd2504f00 100644
--- a/apps/assets/models/user.py
+++ b/apps/assets/models/user.py
@@ -10,14 +10,13 @@ from django.db.models import Q
 from django.utils.translation import ugettext_lazy as _
 from django.core.validators import MinValueValidator, MaxValueValidator
 
-from common.utils import get_signer
+from common.utils import signer
 from .base import AssetUser
 from .asset import Asset
 
 
 __all__ = ['AdminUser', 'SystemUser']
 logger = logging.getLogger(__name__)
-signer = get_signer()
 
 
 class AdminUser(AssetUser):
diff --git a/apps/audits/signals_handler.py b/apps/audits/signals_handler.py
index 772a7fcd5..e7c61313d 100644
--- a/apps/audits/signals_handler.py
+++ b/apps/audits/signals_handler.py
@@ -27,6 +27,7 @@ MODELS_NEED_RECORD = (
     'User', 'UserGroup', 'Asset', 'Node', 'AdminUser', 'SystemUser',
     'Domain', 'Gateway', 'Organization', 'AssetPermission', 'CommandFilter',
     'CommandFilterRule', 'License', 'Setting', 'Account', 'SyncInstanceTask',
+    'Platform', 'RemoteAppPermission', 'ChangeAuthPlan', 'GatherUserTask',
 )
 
 
diff --git a/apps/common/fields/form.py b/apps/common/fields/form.py
index 156e331dd..c4cdc78ad 100644
--- a/apps/common/fields/form.py
+++ b/apps/common/fields/form.py
@@ -6,9 +6,8 @@ from django import forms
 from django.utils import six
 from django.core.exceptions import ValidationError
 from django.utils.translation import ugettext as _
-from ..utils import get_signer
+from ..utils import signer
 
-signer = get_signer()
 
 __all__ = [
     'FormDictField', 'FormEncryptCharField', 'FormEncryptDictField',
diff --git a/apps/common/fields/model.py b/apps/common/fields/model.py
index e1bd4e1e7..ddb61f7c0 100644
--- a/apps/common/fields/model.py
+++ b/apps/common/fields/model.py
@@ -4,7 +4,7 @@ import json
 from django.db import models
 from django.utils.translation import ugettext_lazy as _
 
-from ..utils import get_signer
+from ..utils import signer
 
 
 __all__ = [
@@ -12,8 +12,8 @@ __all__ = [
     'JsonCharField', 'JsonTextField', 'JsonListCharField', 'JsonListTextField',
     'JsonDictCharField', 'JsonDictTextField', 'EncryptCharField',
     'EncryptTextField', 'EncryptMixin', 'EncryptJsonDictTextField',
+    'EncryptJsonDictCharField',
 ]
-signer = get_signer()
 
 
 class JsonMixin:
@@ -108,14 +108,24 @@ class JsonTextField(JsonMixin, models.TextField):
 
 
 class EncryptMixin:
+    """
+    EncryptMixin要放在最前面
+    """
     def from_db_value(self, value, expression, connection, context):
-        if value is not None:
-            return signer.unsign(value)
-        return None
+        if value is None:
+            return value
+        value = signer.unsign(value)
+        sp = super()
+        if hasattr(sp, 'from_db_value'):
+            return sp.from_db_value(value, expression, connection, context)
+        return value
 
     def get_prep_value(self, value):
         if value is None:
             return value
+        sp = super()
+        if hasattr(sp, 'get_prep_value'):
+            value = sp.get_prep_value(value)
         return signer.sign(value)
 
 
@@ -150,3 +160,6 @@ class EncryptJsonDictTextField(EncryptMixin, JsonDictTextField):
     pass
 
 
+class EncryptJsonDictCharField(EncryptMixin, JsonDictCharField):
+    pass
+
diff --git a/apps/common/tests.py b/apps/common/tests.py
index a9edb8f69..5fd1a7ddb 100644
--- a/apps/common/tests.py
+++ b/apps/common/tests.py
@@ -2,11 +2,10 @@ from django.test import TestCase
 
 # Create your tests here.
 
-from .utils import random_string, get_signer
+from .utils import random_string, signer
 
 
 def test_signer_len():
-    signer = get_signer()
     results = {}
     for i in range(1, 4096):
         s = random_string(i)
diff --git a/apps/common/utils/encode.py b/apps/common/utils/encode.py
index 6da79ff48..097e28292 100644
--- a/apps/common/utils/encode.py
+++ b/apps/common/utils/encode.py
@@ -184,8 +184,11 @@ def encrypt_password(password, salt=None):
 
 
 def get_signer():
-    signer = Signer(settings.SECRET_KEY)
-    return signer
+    s = Signer(settings.SECRET_KEY)
+    return s
+
+
+signer = get_signer()
 
 
 def ensure_last_char_is_ascii(data):
diff --git a/apps/jumpserver/settings/libs.py b/apps/jumpserver/settings/libs.py
index 35de39b33..69b580d66 100644
--- a/apps/jumpserver/settings/libs.py
+++ b/apps/jumpserver/settings/libs.py
@@ -105,7 +105,7 @@ CELERY_TASK_SERIALIZER = 'pickle'
 CELERY_RESULT_SERIALIZER = 'pickle'
 CELERY_RESULT_BACKEND = CELERY_BROKER_URL
 CELERY_ACCEPT_CONTENT = ['json', 'pickle']
-CELERY_RESULT_EXPIRES = 3600
+CELERY_RESULT_EXPIRES = 600
 # CELERY_WORKER_LOG_FORMAT = '%(asctime)s [%(module)s %(levelname)s] %(message)s'
 # CELERY_WORKER_LOG_FORMAT = '%(message)s'
 # CELERY_WORKER_TASK_LOG_FORMAT = '%(task_id)s %(task_name)s %(message)s'
diff --git a/apps/ops/api/adhoc.py b/apps/ops/api/adhoc.py
index 1bf2b4901..38c59d701 100644
--- a/apps/ops/api/adhoc.py
+++ b/apps/ops/api/adhoc.py
@@ -4,6 +4,7 @@
 from django.shortcuts import get_object_or_404
 from rest_framework import viewsets, generics
 from rest_framework.views import Response
+from django.db.models import Count, Q
 
 from common.permissions import IsOrgAdmin
 from common.serializers import CeleryTaskSerializer
@@ -31,6 +32,7 @@ class TaskViewSet(viewsets.ModelViewSet):
             queryset = queryset.filter(created_by=current_org.id)
         else:
             queryset = queryset.filter(created_by='')
+        queryset = queryset.select_related('latest_history')
         return queryset
 
 
diff --git a/apps/ops/celery/decorator.py b/apps/ops/celery/decorator.py
index c60c02b6a..971d5b863 100644
--- a/apps/ops/celery/decorator.py
+++ b/apps/ops/celery/decorator.py
@@ -33,11 +33,14 @@ def get_after_app_ready_tasks():
 
 def register_as_period_task(
         crontab=None, interval=None, name=None,
+        args=(), kwargs=None,
         description=''):
     """
     Warning: Task must be have not any args and kwargs
     :param crontab:  "* * * * *"
     :param interval:  60*60*60
+    :param args: ()
+    :param kwargs: {}
     :param description: "
     :param name: ""
     :return:
@@ -58,7 +61,8 @@ def register_as_period_task(
                'task': task,
                'interval': interval,
                'crontab': crontab,
-               'args': (),
+               'args': args,
+               'kwargs': kwargs if kwargs else {},
                'enabled': True,
                'description': description
            }
diff --git a/apps/ops/celery/utils.py b/apps/ops/celery/utils.py
index 2c46f225f..55ce4a9d1 100644
--- a/apps/ops/celery/utils.py
+++ b/apps/ops/celery/utils.py
@@ -74,8 +74,6 @@ def create_or_update_celery_periodic_tasks(tasks):
             kwargs=json.dumps(detail.get('kwargs', {})),
             description=detail.get('description') or ''
         )
-        print(defaults)
-
         task = PeriodicTask.objects.update_or_create(
             defaults=defaults, name=name,
         )
@@ -101,4 +99,3 @@ def get_celery_task_log_path(task_id):
     path = os.path.join(settings.CELERY_LOG_DIR, rel_path)
     os.makedirs(os.path.dirname(path), exist_ok=True)
     return path
-
diff --git a/apps/ops/migrations/0009_auto_20191217_1713.py b/apps/ops/migrations/0009_auto_20191217_1713.py
new file mode 100644
index 000000000..75ba632d8
--- /dev/null
+++ b/apps/ops/migrations/0009_auto_20191217_1713.py
@@ -0,0 +1,72 @@
+# Generated by Django 2.2.7 on 2019-12-17 09:13
+
+from django.db import migrations, models
+import django.db.models.deletion
+from django.core.exceptions import ObjectDoesNotExist
+
+
+def migrate_task_data(apps, schema_editor):
+    task_model = apps.get_model("ops", "Task")
+    db_alias = schema_editor.connection.alias
+    tasks = task_model.objects.using(db_alias).all()
+    for task in tasks:
+        try:
+            latest_history = task.history.latest()
+        except ObjectDoesNotExist:
+            latest_history = None
+        try:
+            latest_adhoc = task.adhoc.latest()
+        except ObjectDoesNotExist:
+            latest_adhoc = None
+        if latest_history and latest_history.adhoc:
+            latest_history.hosts_amount = latest_history.adhoc.hosts.count()
+            latest_history.save()
+        total_run_amount = task.history.all().count()
+        success_run_amount = task.history.filter(is_success=True).count()
+        task.latest_history = latest_history
+        task.latest_adhoc = latest_adhoc
+        task.total_run_amount = total_run_amount
+        task.success_run_amount = success_run_amount
+        task.save()
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('ops', '0008_auto_20190919_2100'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='task',
+            name='latest_adhoc',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='task_latest', to='ops.AdHoc'),
+        ),
+        migrations.AddField(
+            model_name='task',
+            name='latest_history',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='task_latest', to='ops.AdHocRunHistory'),
+        ),
+        migrations.AddField(
+            model_name='task',
+            name='success_run_amount',
+            field=models.IntegerField(default=0),
+        ),
+        migrations.AddField(
+            model_name='task',
+            name='total_run_amount',
+            field=models.IntegerField(default=0),
+        ),
+        migrations.AddField(
+            model_name='adhocrunhistory',
+            name='hosts_amount',
+            field=models.IntegerField(default=0, verbose_name='Host amount'),
+        ),
+        migrations.AddField(
+            model_name='adhocrunhistory',
+            name='task_display',
+            field=models.CharField(blank=True, default='', max_length=128,
+                                   verbose_name='Task display'),
+        ),
+        migrations.RunPython(migrate_task_data),
+    ]
diff --git a/apps/ops/migrations/0010_auto_20191217_1758.py b/apps/ops/migrations/0010_auto_20191217_1758.py
new file mode 100644
index 000000000..7e999a33b
--- /dev/null
+++ b/apps/ops/migrations/0010_auto_20191217_1758.py
@@ -0,0 +1,68 @@
+# Generated by Django 2.2.7 on 2019-12-17 09:58
+
+import common.fields.model
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('ops', '0009_auto_20191217_1713'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='adhoc',
+            name='_hosts',
+        ),
+        migrations.AlterField(
+            model_name='adhoc',
+            name='_become',
+            field=common.fields.model.EncryptJsonDictCharField(blank=True, default='', max_length=1024, verbose_name='Become'),
+        ),
+        migrations.AlterField(
+            model_name='adhoc',
+            name='_options',
+            field=common.fields.model.JsonDictCharField(default='', max_length=1024, verbose_name='Options'),
+        ),
+        migrations.AlterField(
+            model_name='adhoc',
+            name='_tasks',
+            field=common.fields.model.JsonListTextField(verbose_name='Tasks'),
+        ),
+        migrations.RenameField(
+            model_name='adhoc',
+            old_name='_become',
+            new_name='become',
+        ),
+        migrations.RenameField(
+            model_name='adhoc',
+            old_name='_options',
+            new_name='options',
+        ),
+        migrations.RenameField(
+            model_name='adhoc',
+            old_name='_tasks',
+            new_name='tasks',
+        ),
+        migrations.AlterField(
+            model_name='adhocrunhistory',
+            name='_result',
+            field=common.fields.model.JsonDictTextField(blank=True, null=True, verbose_name='Adhoc raw result'),
+        ),
+        migrations.AlterField(
+            model_name='adhocrunhistory',
+            name='_summary',
+            field=common.fields.model.JsonDictTextField(blank=True, null=True, verbose_name='Adhoc result summary'),
+        ),
+        migrations.RenameField(
+            model_name='adhocrunhistory',
+            old_name='_result',
+            new_name='result',
+        ),
+        migrations.RenameField(
+            model_name='adhocrunhistory',
+            old_name='_summary',
+            new_name='summary',
+        ),
+    ]
diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py
index 3eebb6636..3e0220b68 100644
--- a/apps/ops/models/adhoc.py
+++ b/apps/ops/models/adhoc.py
@@ -1,6 +1,5 @@
 # ~*~ coding: utf-8 ~*~
 
-import json
 import uuid
 import os
 import time
@@ -13,11 +12,16 @@ from django.utils import timezone
 from django.utils.translation import ugettext_lazy as _
 from django_celery_beat.models import PeriodicTask
 
-from common.utils import get_signer, get_logger, lazyproperty
-from orgs.utils import set_to_root_org
-from ..celery.utils import delete_celery_periodic_task, \
-    create_or_update_celery_periodic_tasks, \
+from common.utils import get_logger, lazyproperty
+from common.fields.model import (
+    JsonListTextField, JsonDictCharField, EncryptJsonDictCharField,
+    JsonDictTextField,
+)
+from orgs.utils import set_to_root_org, get_current_org, set_current_org
+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
 
@@ -25,7 +29,6 @@ __all__ = ["Task", "AdHoc", "AdHocRunHistory"]
 
 
 logger = get_logger(__file__)
-signer = get_signer()
 
 
 class Task(models.Model):
@@ -44,14 +47,17 @@ class Task(models.Model):
     created_by = models.CharField(max_length=128, blank=True, default='')
     date_created = models.DateTimeField(auto_now_add=True, db_index=True, verbose_name=_("Date created"))
     date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
-    __latest_adhoc = None
+    latest_adhoc = models.ForeignKey('ops.AdHoc', on_delete=models.SET_NULL, null=True, related_name='task_latest')
+    latest_history = models.ForeignKey('ops.AdHocRunHistory', on_delete=models.SET_NULL, null=True, related_name='task_latest')
+    total_run_amount = models.IntegerField(default=0)
+    success_run_amount = models.IntegerField(default=0)
     _ignore_auto_created_by = True
 
     @property
     def short_id(self):
         return str(self.id).split('-')[-1]
 
-    @property
+    @lazyproperty
     def versions(self):
         return self.adhoc.all().count()
 
@@ -78,73 +84,67 @@ class Task(models.Model):
 
     @property
     def assets_amount(self):
-        return self.latest_adhoc.hosts.count()
-
-    @lazyproperty
-    def latest_adhoc(self):
-        return self.get_latest_adhoc()
-
-    @lazyproperty
-    def latest_history(self):
-        try:
-            return self.history.all().latest()
-        except AdHocRunHistory.DoesNotExist:
-            return None
+        if self.latest_history:
+            return self.latest_history.hosts_amount
+        return 0
 
     def get_latest_adhoc(self):
+        if self.latest_adhoc:
+            return self.latest_adhoc
         try:
-            return self.adhoc.all().latest()
+            adhoc = self.adhoc.all().latest()
+            self.latest_adhoc = adhoc
+            self.save()
+            return adhoc
         except AdHoc.DoesNotExist:
             return None
 
     @property
     def history_summary(self):
-        history = self.get_run_history()
-        total = len(history)
-        success = len([history for history in history if history.is_success])
-        failed = len([history for history in history if not history.is_success])
+        total = self.total_run_amount
+        success = self.success_run_amount
+        failed = total - success
         return {'total': total, 'success': success, 'failed': failed}
 
     def get_run_history(self):
         return self.history.all()
 
-    def run(self, record=True):
-        set_to_root_org()
-        if self.latest_adhoc:
-            return self.latest_adhoc.run(record=record)
+    def run(self):
+        latest_adhoc = self.get_latest_adhoc()
+        if latest_adhoc:
+            return latest_adhoc.run()
         else:
             return {'error': 'No adhoc'}
 
-    def save(self, force_insert=False, force_update=False, using=None,
-             update_fields=None):
+    def register_as_period_task(self):
         from ..tasks import run_ansible_task
-        super().save(
-            force_insert=force_insert, force_update=force_update,
-            using=using, update_fields=update_fields,
-        )
+        interval = None
+        crontab = None
 
-        if self.is_periodic:
-            interval = None
-            crontab = None
+        if self.interval:
+            interval = self.interval
+        elif self.crontab:
+            crontab = self.crontab
 
-            if self.interval:
-                interval = self.interval
-            elif self.crontab:
-                crontab = self.crontab
-
-            tasks = {
-                self.__str__(): {
-                    "task": run_ansible_task.name,
-                    "interval": interval,
-                    "crontab": crontab,
-                    "args": (str(self.id),),
-                    "kwargs": {"callback": self.callback},
-                    "enabled": True,
-                }
+        tasks = {
+            self.__str__(): {
+                "task": run_ansible_task.name,
+                "interval": interval,
+                "crontab": crontab,
+                "args": (str(self.id),),
+                "kwargs": {"callback": self.callback},
+                "enabled": True,
             }
-            create_or_update_celery_periodic_tasks(tasks)
+        }
+        create_or_update_celery_periodic_tasks(tasks)
+
+    def save(self, **kwargs):
+        instance = super().save(**kwargs)
+        if self.is_periodic:
+            self.register_as_period_task()
         else:
             disable_celery_periodic_task(self.__str__())
+        return instance
 
     def delete(self, using=None, keep_parents=False):
         super().delete(using=using, keep_parents=keep_parents)
@@ -153,7 +153,7 @@ class Task(models.Model):
     @property
     def schedule(self):
         try:
-            return PeriodicTask.objects.get(name=self.name)
+            return PeriodicTask.objects.get(name=str(self))
         except PeriodicTask.DoesNotExist:
             return None
 
@@ -172,7 +172,6 @@ class AdHoc(models.Model):
     task: A task reference
     _tasks: [{'name': 'task_name', 'action': {'module': '', 'args': ''}, 'other..': ''}, ]
     _options: ansible options, more see ops.ansible.runner.Options
-    _hosts: ["hostname1", "hostname2"], hostname must be unique key of cmdb
     run_as_admin: if true, then need get every host admin user run it, because every host may be have different admin user, so we choise host level
     run_as: username(Add the uniform AssetUserManager <AssetUserManager> and change it to username)
     _become: May be using become [sudo, su] options. {method: "sudo", user: "user", pass: "pass"]
@@ -180,31 +179,16 @@ class AdHoc(models.Model):
     """
     id = models.UUIDField(default=uuid.uuid4, primary_key=True)
     task = models.ForeignKey(Task, related_name='adhoc', on_delete=models.CASCADE)
-    _tasks = models.TextField(verbose_name=_('Tasks'))
+    tasks = JsonListTextField(verbose_name=_('Tasks'))
     pattern = models.CharField(max_length=64, default='{}', verbose_name=_('Pattern'))
-    _options = models.CharField(max_length=1024, default='', verbose_name=_('Options'))
-    _hosts = models.TextField(blank=True, verbose_name=_('Hosts'))  # ['hostname1', 'hostname2']
+    options = JsonDictCharField(max_length=1024, default='', verbose_name=_('Options'))
     hosts = models.ManyToManyField('assets.Asset', verbose_name=_("Host"))
     run_as_admin = models.BooleanField(default=False, verbose_name=_('Run as admin'))
     run_as = models.CharField(max_length=64, default='', blank=True, null=True, verbose_name=_('Username'))
-    _become = models.CharField(max_length=1024, default='', blank=True, verbose_name=_("Become"))
+    become = EncryptJsonDictCharField(max_length=1024, default='', blank=True, verbose_name=_("Become"))
     created_by = models.CharField(max_length=64, default='', blank=True, null=True, verbose_name=_('Create by'))
     date_created = models.DateTimeField(auto_now_add=True, db_index=True)
 
-    @property
-    def tasks(self):
-        try:
-            return json.loads(self._tasks)
-        except:
-            return []
-
-    @tasks.setter
-    def tasks(self, item):
-        if item and isinstance(item, list):
-            self._tasks = json.dumps(item)
-        else:
-            raise SyntaxError('Tasks should be a list: {}'.format(item))
-
     @property
     def inventory(self):
         if self.become:
@@ -223,97 +207,22 @@ class AdHoc(models.Model):
         return inventory
 
     @property
-    def become(self):
-        if self._become:
-            return json.loads(signer.unsign(self._become))
-        else:
-            return {}
+    def become_display(self):
+        if self.become:
+            return self.become.get("user", "")
+        return ""
 
-    def run(self, record=True):
-        set_to_root_org()
-        if record:
-            return self._run_and_record()
-        else:
-            return self._run_only()
-
-    def _run_and_record(self):
+    def run(self):
         try:
             hid = current_task.request.id
         except AttributeError:
             hid = str(uuid.uuid4())
-        history = AdHocRunHistory(id=hid, adhoc=self, task=self.task)
+        history = AdHocRunHistory(
+            id=hid, adhoc=self, task=self.task,
+            task_display=str(self.task)
+        )
         history.save()
-        time_start = time.time()
-        date_start = timezone.now()
-        is_success = False
-        summary = {}
-        raw = ''
-
-        try:
-            date_start_s = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
-            print(_("{} Start task: {}").format(date_start_s, self.task.name))
-            raw, summary = self._run_only()
-            is_success = summary.get('success', False)
-        except Exception as e:
-            logger.error(e, exc_info=True)
-            raw = {"dark": {"all": str(e)}, "contacted": []}
-        finally:
-            date_end = timezone.now()
-            date_end_s = date_end.strftime('%Y-%m-%d %H:%M:%S')
-            print(_("{} Task finish").format(date_end_s))
-            print('.\n\n.')
-            try:
-                summary_text = json.dumps(summary)
-            except json.JSONDecodeError:
-                summary_text = '{}'
-            AdHocRunHistory.objects.filter(id=history.id).update(
-                date_start=date_start,
-                is_finished=True,
-                is_success=is_success,
-                date_finished=timezone.now(),
-                timedelta=time.time() - time_start,
-                _summary=summary_text
-            )
-            return raw, summary
-
-    def _run_only(self):
-        Task.objects.filter(id=self.task.id).update(date_updated=timezone.now())
-        runner = AdHocRunner(self.inventory, options=self.options)
-        try:
-            result = runner.run(
-                self.tasks,
-                self.pattern,
-                self.task.name,
-            )
-            return result.results_raw, result.results_summary
-        except AnsibleError as e:
-            logger.warn("Failed run adhoc {}, {}".format(self.task.name, e))
-            pass
-
-    @become.setter
-    def become(self, item):
-        """
-        :param item:  {
-            method: "sudo",
-            user: "user",
-            pass: "pass",
-        }
-        :return:
-        """
-        # self._become = signer.sign(json.dumps(item)).decode('utf-8')
-        self._become = signer.sign(json.dumps(item))
-
-    @property
-    def options(self):
-        if self._options:
-            _options = json.loads(self._options)
-            if isinstance(_options, dict):
-                return _options
-        return {}
-
-    @options.setter
-    def options(self, item):
-        self._options = json.dumps(item)
+        return history.start()
 
     @property
     def short_id(self):
@@ -328,6 +237,8 @@ class AdHoc(models.Model):
 
     def save(self, **kwargs):
         instance = super().save(**kwargs)
+        self.task.latest_adhoc = instance
+        self.task.save()
         return instance
 
     def __str__(self):
@@ -356,19 +267,25 @@ class AdHocRunHistory(models.Model):
     """
     id = models.UUIDField(default=uuid.uuid4, primary_key=True)
     task = models.ForeignKey(Task, related_name='history', on_delete=models.SET_NULL, null=True)
+    task_display = models.CharField(max_length=128, blank=True, default='', verbose_name=_("Task display"))
+    hosts_amount = models.IntegerField(default=0, verbose_name=_("Host amount"))
     adhoc = models.ForeignKey(AdHoc, related_name='history', on_delete=models.SET_NULL, null=True)
     date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Start time'))
     date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('End time'))
     timedelta = models.FloatField(default=0.0, verbose_name=_('Time'), null=True)
     is_finished = models.BooleanField(default=False, verbose_name=_('Is finished'))
     is_success = models.BooleanField(default=False, verbose_name=_('Is success'))
-    _result = models.TextField(blank=True, null=True, verbose_name=_('Adhoc raw result'))
-    _summary = models.TextField(blank=True, null=True, verbose_name=_('Adhoc result summary'))
+    result = JsonDictTextField(blank=True, null=True, verbose_name=_('Adhoc raw result'))
+    summary = JsonDictTextField(blank=True, null=True, verbose_name=_('Adhoc result summary'))
 
     @property
     def short_id(self):
         return str(self.id).split('-')[-1]
 
+    @property
+    def adhoc_short_id(self):
+        return str(self.adhoc_id).split('-')[-1]
+
     @property
     def log_path(self):
         dt = datetime.datetime.now().strftime('%Y-%m-%d')
@@ -377,30 +294,58 @@ class AdHocRunHistory(models.Model):
             os.makedirs(log_dir)
         return os.path.join(log_dir, str(self.id) + '.log')
 
-    @property
-    def result(self):
-        if self._result:
-            return json.loads(self._result)
-        else:
-            return {}
-
-    @result.setter
-    def result(self, item):
-        self._result = json.dumps(item)
-
-    @property
-    def summary(self):
-        if self._summary:
-            return json.loads(self._summary)
-        else:
-            return {"ok": {}, "dark": {}}
-
-    @summary.setter
-    def summary(self, item):
+    def start_runner(self):
+        runner = AdHocRunner(self.adhoc.inventory, options=self.adhoc.options)
         try:
-            self._summary = json.dumps(item)
-        except json.JSONDecodeError:
-            self._summary = json.dumps({})
+            result = runner.run(
+                self.adhoc.tasks,
+                self.adhoc.pattern,
+                self.task.name,
+            )
+            return result.results_raw, result.results_summary
+        except AnsibleError as e:
+            logger.warn("Failed run adhoc {}, {}".format(self.task.name, e))
+            return {}, {}
+
+    def start(self):
+        self.task.latest_history = self
+        self.task.save()
+        current_org = get_current_org()
+        set_to_root_org()
+        time_start = time.time()
+        date_start = timezone.now()
+        is_success = False
+        summary = {}
+        raw = ''
+
+        try:
+            date_start_s = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+            print(_("{} Start task: {}").format(date_start_s, self.task.name))
+            raw, summary = self.start_runner()
+            is_success = summary.get('success', False)
+        except Exception as e:
+            logger.error(e, exc_info=True)
+            raw = {"dark": {"all": str(e)}, "contacted": []}
+        finally:
+            date_end = timezone.now()
+            date_end_s = date_end.strftime('%Y-%m-%d %H:%M:%S')
+            print(_("{} Task finish").format(date_end_s))
+            print('.\n\n.')
+            task = Task.objects.get(id=self.task_id)
+            task.total_run_amount = models.F('total_run_amount') + 1
+            if is_success:
+                task.success_run_amount = models.F('success_run_amount') + 1
+            task.save()
+            AdHocRunHistory.objects.filter(id=self.id).update(
+                date_start=date_start,
+                is_finished=True,
+                is_success=is_success,
+                date_finished=timezone.now(),
+                timedelta=time.time() - time_start,
+                summary=summary
+            )
+            set_current_org(current_org)
+            return raw, summary
 
     @property
     def success_hosts(self):
diff --git a/apps/ops/serializers/adhoc.py b/apps/ops/serializers/adhoc.py
index a737af1c4..6a3464654 100644
--- a/apps/ops/serializers/adhoc.py
+++ b/apps/ops/serializers/adhoc.py
@@ -6,59 +6,74 @@ from django.shortcuts import reverse
 from ..models import Task, AdHoc, AdHocRunHistory, CommandExecution
 
 
-class TaskSerializer(serializers.ModelSerializer):
-    class Meta:
-        model = Task
-        fields = [
-            'id', 'name', 'interval', 'crontab', 'is_periodic',
-            'is_deleted', 'comment', 'created_by', 'date_created',
-            'versions', 'is_success', 'timedelta', 'assets_amount',
-            'date_updated', 'history_summary',
-        ]
-
-
-class AdHocSerializer(serializers.ModelSerializer):
-    class Meta:
-        model = AdHoc
-        exclude = ('_tasks', '_options', '_hosts', '_become')
-
-    def get_field_names(self, declared_fields, info):
-        fields = super().get_field_names(declared_fields, info)
-        fields.extend(['tasks', 'options', 'hosts', 'become', 'short_id'])
-        return fields
-
-
 class AdHocRunHistorySerializer(serializers.ModelSerializer):
-    task = serializers.SerializerMethodField()
-    adhoc_short_id = serializers.SerializerMethodField()
     stat = serializers.SerializerMethodField()
 
     class Meta:
         model = AdHocRunHistory
-        exclude = ('_result', '_summary')
-
-    @staticmethod
-    def get_adhoc_short_id(obj):
-        return obj.adhoc.short_id
+        fields = '__all__'
 
     @staticmethod
     def get_task(obj):
-        return obj.adhoc.task.id
+        return obj.task.id
 
     @staticmethod
     def get_stat(obj):
         return {
-            "total": obj.adhoc.hosts.count(),
+            "total": obj.hosts_amount,
             "success": len(obj.summary.get("contacted", [])),
             "failed": len(obj.summary.get("dark", [])),
         }
 
     def get_field_names(self, declared_fields, info):
         fields = super().get_field_names(declared_fields, info)
-        fields.extend(['summary', 'short_id'])
+        fields.extend(['short_id', 'adhoc_short_id'])
         return fields
 
 
+class AdHocRunHistoryExcludeResultSerializer(AdHocRunHistorySerializer):
+    def get_field_names(self, declared_fields, info):
+        fields = super().get_field_names(declared_fields, info)
+        fields = [i for i in fields if i not in ['result', 'summary']]
+        return fields
+
+
+class TaskSerializer(serializers.ModelSerializer):
+    latest_history = AdHocRunHistoryExcludeResultSerializer(read_only=True)
+
+    class Meta:
+        model = Task
+        fields = [
+            'id', 'name', 'interval', 'crontab', 'is_periodic',
+            'is_deleted', 'comment', 'created_by', 'date_created',
+            'date_updated', 'latest_history',
+        ]
+        read_only_fields = [
+            'is_deleted', 'created_by', 'date_created', 'date_updated',
+            'latest_adhoc', 'latest_history', 'total_run_amount',
+            'success_run_amount',
+        ]
+
+
+class AdHocSerializer(serializers.ModelSerializer):
+    become_display = serializers.ReadOnlyField()
+
+    class Meta:
+        model = AdHoc
+        fields = [
+            "id", "task", 'tasks', "pattern", "options",
+            "hosts", "run_as_admin", "run_as", "become",
+            "created_by", "date_created", "short_id",
+            "become_display",
+        ]
+        read_only_fields = [
+            'created_by', 'date_created'
+        ]
+        extra_kwargs = {
+            "become": {'write_only': True}
+        }
+
+
 class CommandExecutionSerializer(serializers.ModelSerializer):
     result = serializers.JSONField(read_only=True)
     log_url = serializers.SerializerMethodField()
diff --git a/apps/ops/templates/ops/adhoc_detail.html b/apps/ops/templates/ops/adhoc_detail.html
index 55c0960a2..777207da4 100644
--- a/apps/ops/templates/ops/adhoc_detail.html
+++ b/apps/ops/templates/ops/adhoc_detail.html
@@ -78,7 +78,7 @@
                                         {% endif %}
                                         <tr>
                                             <td>{% trans 'Become' %}</td>
-                                            <td><b>{{ object.become.user }}</b></td>
+                                            <td><b>{{ object.become_display }}</b></td>
                                         </tr>
                                         <tr>
                                             <td>{% trans 'Created by' %}</td>
diff --git a/apps/ops/templates/ops/command_execution_create.html b/apps/ops/templates/ops/command_execution_create.html
index f89dc98f6..95ce617ca 100644
--- a/apps/ops/templates/ops/command_execution_create.html
+++ b/apps/ops/templates/ops/command_execution_create.html
@@ -4,17 +4,12 @@
 {% load bootstrap3 %}
 
 {% block custom_head_css_js %}
-    <link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}"
-          rel="stylesheet">
+    <link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
     <link rel="stylesheet" href="{% static 'js/plugins/xterm/xterm.css' %}"/>
-    <link href="{% static 'css/plugins/codemirror/codemirror.css' %}"
-          rel="stylesheet">
-    <link href="{% static 'css/plugins/codemirror/ambiance.css' %}"
-          rel="stylesheet">
-    <script type="text/javascript"
-            src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
-    <script type="text/javascript"
-            src="{% static 'js/plugins/ztree/jquery.ztree.exhide.min.js' %}"></script>
+    <link href="{% static 'css/plugins/codemirror/codemirror.css' %}" rel="stylesheet">
+    <link href="{% static 'css/plugins/codemirror/ambiance.css' %}" rel="stylesheet">
+    <script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
+    <script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.exhide.min.js' %}"></script>
     <script src="{% static 'js/jquery.form.min.js' %}"></script>
     <script src="{% static 'js/plugins/xterm/xterm.js' %}"></script>
     <script src="{% static 'js/plugins/xterm/addons/fit/fit.js' %}"></script>
@@ -37,18 +32,18 @@
             overflow: auto;
         }
 
-        body ::-webkit-scrollbar-track {
+        #term ::-webkit-scrollbar-track {
             -webkit-box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.3);
             background-color: #272323;
             border-radius: 6px;
         }
 
-        body ::-webkit-scrollbar {
+        #term ::-webkit-scrollbar {
             width: 8px;
             height: 8px;
         }
 
-        body ::-webkit-scrollbar-thumb {
+        #term ::-webkit-scrollbar-thumb {
             background-color: #494141;
             border-radius: 6px;
         }
@@ -58,8 +53,8 @@
 {% block content %}
     <div class="wrapper wrapper-content">
         <div class="row">
-            <div class="col-sm-3" id="split-left" style="padding-left: 3px">
-                <div class="ibox float-e-margins">
+            <div class="col-sm-3" id="split-left" style="padding-left: 3px;overflow:auto">
+                <div class="ibox treebox float-e-margins">
                     <div class="ibox-content mailbox-content"
                          style="padding-top: 0;padding-left: 1px">
                         <div class="file-manager ">
@@ -73,37 +68,30 @@
             </div>
             <div class="col-sm-9 animated fadeInRight" id="split-right">
                 <div class="tree-toggle">
-                    <div class="btn btn-sm btn-primary tree-toggle-btn"
-                         onclick="toggle()">
+                    <div class="btn btn-sm btn-primary tree-toggle-btn" onclick="toggle()">
                         <i class="fa fa-angle-left fa-x" id="toggle-icon"></i>
                     </div>
                 </div>
                 <div class="mail-box-header" style="padding-top: 5px;">
-                    <form enctype="multipart/form-data" method="post"
-                          class="form-horizontal" action=""
-                          onsubmit="return execute()">
+                    <form enctype="multipart/form-data" method="post" class="form-horizontal" action="" onsubmit="return execute()">
                         <div class="form-group">
-                            <div id="term"
-                                 style="height: 100%;width: 100%"></div>
+                            <div id="term" style="height: 100%;width: 100%"></div>
                         </div>
                         <div class="row">
                             <div class="col-sm-10">
-                                <div class="input-group"
-                                     style="height: 100%; width: 100%">
-                                    <textarea class="form-control"
-                                              id="command-text"></textarea>
+                                <div class="input-group" style="height: 100%; width: 100%">
+                                    <textarea class="form-control" id="command-text"></textarea>
                                 </div>
                             </div>
                             <div class="col-sm-2">
-                                <select class="select2 form-control"
-                                        id="system-users-select">
+                                <select class="select2 form-control" id="system-users-select">
                                     {% for s in system_users %}
                                         <option value="{{ s.id }}" {% if s.protocol != 'ssh' or s.login_mode != 'auto' %}disabled{% endif %}>{{ s }}</option>
                                     {% endfor %}
                                 </select>
-                                <button type="button"
-                                        class="btn btn-primary btn-execute"
-                                        style="margin-top: 30px; width: 100%">{% trans 'Go' %}</button>
+                                <button type="button" class="btn btn-primary btn-execute" style="margin-top: 30px; width: 100%">
+                                    {% trans 'Go' %}
+                                </button>
                             </div>
                         </div>
                     </form>
@@ -114,230 +102,236 @@
 {% endblock %}
 
 {% block custom_foot_js %}
-    <script>
-        var zTree, show = 0;
-        var systemUserId = null;
-        var url = null;
-        var treeUrl = "{% url 'api-perms:my-nodes-with-assets-as-tree' %}?cache_policy=1";
+<script>
+    var zTree, show = 0;
+    var systemUserId = null;
+    var url = null;
+    var treeUrl = "{% url 'api-perms:my-nodes-with-assets-as-tree' %}?cache_policy=1";
 
-        function initTree() {
-            $('#assetTree').html("{% trans 'Loading' %}" + '..');
-            if (systemUserId) {
-                url = treeUrl + '&system_user=' + systemUserId
-            } else {
-                url = treeUrl
-            }
-            var setting = {
-                check: {
+    function initTree() {
+        $('#assetTree').html("{% trans 'Loading' %}" + '..');
+        if (systemUserId) {
+            url = treeUrl + '&system_user=' + systemUserId
+        } else {
+            url = treeUrl
+        }
+        var setting = {
+            check: {
+                enable: true
+            },
+            async: {
+                enable: true,
+                url: url,
+                autoParam: ["id=key", "name=n", "level=lv"],
+                type: 'get'
+            },
+            view: {
+                dblClickExpand: false,
+                showLine: true
+            },
+            data: {
+                simpleData: {
                     enable: true
-                },
-                view: {
-                    dblClickExpand: false,
-                    showLine: true
-                },
-                data: {
-                    simpleData: {
-                        enable: true
-                    }
-                },
-                edit: {
-                    enable: true,
-                    showRemoveBtn: false,
-                    showRenameBtn: false,
-                    drag: {
-                        isCopy: true,
-                        isMove: true
-                    }
-                },
-                callback: {
-                    onCheck: onCheck
                 }
-            };
-
-
-            $.get(url, function (data, status) {
-                $.fn.zTree.init($("#assetTree"), setting, data);
-                zTree = $.fn.zTree.getZTreeObj("assetTree");
-                rootNodeAddDom(zTree, function () {
-                    treeUrl = treeUrl.replace('cache_policy=1', 'cache_policy=2');
-                    initTree();
-                });
-            });
-        }
-
-        function getSelectedAssetsNode() {
-            var nodes = zTree.getCheckedNodes(true);
-            var assetsNodeId = [];
-            var assetsNode = [];
-            nodes.forEach(function (node) {
-                if (node.meta.type === 'asset' && !node.isHidden) {
-                    var protocols = node.meta.asset.protocols;
-                    protocols.forEach(function (val) {
-                        if (assetsNodeId.indexOf(node.id) === -1 && val.indexOf("ssh") > -1) {
-                            assetsNodeId.push(node.id);
-                            assetsNode.push(node)
-                        }
-                    });
+            },
+            edit: {
+                enable: true,
+                showRemoveBtn: false,
+                showRenameBtn: false,
+                drag: {
+                    isCopy: true,
+                    isMove: true
                 }
-            });
-            return assetsNode;
-        }
-
-        function onCheck(e, treeId, treeNode) {
-            var nodes = getSelectedAssetsNode();
-            var nodes_names = nodes.map(function (node) {
-                return node.name;
-            });
-            var message = "{% trans 'Selected assets' %}" + ': ';
-            message += nodes_names.join(", ");
-            message += "\r\n";
-            message += "{% trans 'In total' %}" + ': ' + nodes_names.length + "个\r\n";
-            term.clear();
-            term.write(message)
-        }
-
-        function toggle() {
-            if (show === 0) {
-                $("#split-left").hide(500, function () {
-                    $("#split-right").attr("class", "col-sm-12");
-                    $("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
-                    show = 1;
-                });
-            } else {
-                $("#split-right").attr("class", "col-sm-9");
-                $("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
-                $("#split-left").show(500);
-                show = 0;
+            },
+            callback: {
+                onCheck: onCheck
             }
-        }
+        };
 
-        var term = null;
-        var ws = null;
 
-        function initResultTerminal() {
-            term = new Terminal({
-                cursorBlink: false,
-                screenKeys: false,
-                fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
-                fontSize: 13,
-                rightClickSelectsWord: true,
-                disableStdin: true,
-                lineHeight: 1.2,
-                theme: {
-                    background: '#1f1b1b'
-                }
+        $.get(url, function (data, status) {
+            $.fn.zTree.init($("#assetTree"), setting, data);
+            zTree = $.fn.zTree.getZTreeObj("assetTree");
+            rootNodeAddDom(zTree, function () {
+                treeUrl = treeUrl.replace('cache_policy=1', 'cache_policy=2');
+                initTree();
             });
-            term.open(document.getElementById('term'));
-            var msg = "{% trans 'Select the left asset, select the running system user, execute command in batch' %}" + "\r\n";
-            window.fit.fit(term);
-            {#fit(term);#}
-            term.write(msg);
+        });
+    }
 
-            var scheme = document.location.protocol === "https:" ? "wss" : "ws";
-            var port = document.location.port ? ":" + document.location.port : "";
-            var url = "/ws/ops/tasks/log/";
-            var wsURL = scheme + "://" + document.location.hostname + port + url;
-            var failOverPort = "{{ ws_port }}";
-            var failOverWsURL = scheme + "://" + document.location.hostname + ':' + failOverPort + url;
-            ws = new WebSocket(wsURL);
-            ws.onerror = function (e) {
-                ws = new WebSocket(failOverWsURL);
-                ws.onmessage = function(e) {
-                    var data = JSON.parse(e.data);
-                    term.write(data.message);
-                };
-                ws.onerror = function (e) {
-                    term.write("Connect websocket server error")
-                }
-            };
+    function getSelectedAssetsNode() {
+        var nodes = zTree.getCheckedNodes(true);
+        var assetsNodeId = [];
+        var assetsNode = [];
+        nodes.forEach(function (node) {
+            if (node.meta.type === 'asset' && !node.isHidden) {
+                var protocols = node.meta.asset.protocols;
+                protocols.forEach(function (val) {
+                    if (assetsNodeId.indexOf(node.id) === -1 && val.indexOf("ssh") > -1) {
+                        assetsNodeId.push(node.id);
+                        assetsNode.push(node)
+                    }
+                });
+            }
+        });
+        return assetsNode;
+    }
+
+    function onCheck(e, treeId, treeNode) {
+        var nodes = getSelectedAssetsNode();
+        var nodes_names = nodes.map(function (node) {
+            return node.name;
+        });
+        var message = "{% trans 'Selected assets' %}" + ': ';
+        message += nodes_names.join(", ");
+        message += "\r\n";
+        message += "{% trans 'In total' %}" + ': ' + nodes_names.length + "个\r\n";
+        term.clear();
+        term.write(message)
+    }
+
+    function toggle() {
+        if (show === 0) {
+            $("#split-left").hide(500, function () {
+                $("#split-right").attr("class", "col-sm-12");
+                $("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
+                show = 1;
+            });
+        } else {
+            $("#split-right").attr("class", "col-sm-9");
+            $("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
+            $("#split-left").show(500);
+            show = 0;
+        }
+    }
+
+    var term = null;
+    var ws = null;
+
+    function initResultTerminal() {
+        term = new Terminal({
+            cursorBlink: false,
+            screenKeys: false,
+            fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
+            fontSize: 13,
+            rightClickSelectsWord: true,
+            disableStdin: true,
+            lineHeight: 1.2,
+            theme: {
+                background: '#1f1b1b'
+            }
+        });
+        term.open(document.getElementById('term'));
+        var msg = "{% trans 'Select the left asset, select the running system user, execute command in batch' %}" + "\r\n";
+        window.fit.fit(term);
+        {#fit(term);#}
+        term.write(msg);
+
+        var scheme = document.location.protocol === "https:" ? "wss" : "ws";
+        var port = document.location.port ? ":" + document.location.port : "";
+        var url = "/ws/ops/tasks/log/";
+        var wsURL = scheme + "://" + document.location.hostname + port + url;
+        var failOverPort = "{{ ws_port }}";
+        var failOverWsURL = scheme + "://" + document.location.hostname + ':' + failOverPort + url;
+        ws = new WebSocket(wsURL);
+        ws.onerror = function (e) {
+            ws = new WebSocket(failOverWsURL);
             ws.onmessage = function(e) {
                 var data = JSON.parse(e.data);
                 term.write(data.message);
             };
+            ws.onerror = function (e) {
+                term.write("Connect websocket server error")
+            }
+        };
+        ws.onmessage = function(e) {
+            var data = JSON.parse(e.data);
+            term.write(data.message);
+        };
+    }
+
+    function wrapperError(msg) {
+        return '\033[31m' + msg + '\033[0m' + '\r\n';
+    }
+
+    function execute() {
+        if (!term) {
+            initResultTerminal()
+        }
+        var size = 'rows=' + term.rows + '&cols=' + term.cols;
+        var url = '{% url "api-ops:command-execution-list" %}?' + size;
+        var run_as = systemUserId;
+        var command = editor.getValue();
+        var hosts = getSelectedAssetsNode().map(function (node) {
+            return node.id;
+        });
+        if (hosts.length === 0) {
+            term.write(wrapperError("{% trans 'Unselected assets' %}"));
+            return
+        }
+        if (!command) {
+            term.write(wrapperError("{% trans 'No input command' %}"));
+            return
+        }
+        if (!run_as) {
+            term.write(wrapperError("{% trans 'No system user was selected' %}"));
+            return
+        }
+        var data = {
+            hosts: hosts,
+            run_as: run_as,
+            command: command
+        };
+
+        function writeExecutionOutput(taskId) {
+            var msg = "{% trans 'Pending' %} ";
+            term.write(msg);
+            msg = JSON.stringify({task: taskId});
+            ws.send(msg);
         }
 
-        function wrapperError(msg) {
-            return '\033[31m' + msg + '\033[0m' + '\r\n';
-        }
-
-        function execute() {
-            if (!term) {
-                initResultTerminal()
+        requestApi({
+            url: url,
+            body: JSON.stringify(data),
+            method: 'POST',
+            flash_message: false,
+            success: function (resp) {
+                {#log_url = resp.log_url;#}
+                writeExecutionOutput(resp.id)
             }
-            var size = 'rows=' + term.rows + '&cols=' + term.cols;
-            var url = '{% url "api-ops:command-execution-list" %}?' + size;
-            var run_as = systemUserId;
-            var command = editor.getValue();
-            var hosts = getSelectedAssetsNode().map(function (node) {
-                return node.id;
-            });
-            if (hosts.length === 0) {
-                term.write(wrapperError("{% trans 'Unselected assets' %}"));
-                return
-            }
-            if (!command) {
-                term.write(wrapperError("{% trans 'No input command' %}"));
-                return
-            }
-            if (!run_as) {
-                term.write(wrapperError("{% trans 'No system user was selected' %}"));
-                return
-            }
-            var data = {
-                hosts: hosts,
-                run_as: run_as,
-                command: command
-            };
+        });
+        return false;
+    }
 
-            function writeExecutionOutput(taskId) {
-                var msg = "{% trans 'Pending' %} ";
-                term.write(msg);
-                msg = JSON.stringify({task: taskId});
-                ws.send(msg);
-            }
+    var editor;
+    $(document).ready(function () {
+        $('.treebox').css('height', window.innerHeight - 60);
+        systemUserId = $('#system-users-select').val();
 
-            requestApi({
-                url: url,
-                body: JSON.stringify(data),
-                method: 'POST',
-                flash_message: false,
-                success: function (resp) {
-                    {#log_url = resp.log_url;#}
-                    writeExecutionOutput(resp.id)
-                }
-            });
-            return false;
-        }
-
-        var editor;
-        $(document).ready(function () {
-            systemUserId = $('#system-users-select').val();
-
-
-            $(".select2").select2({
-                dropdownAutoWidth: true,
-            }).on('select2:select', function (evt) {
-                var data = evt.params.data;
-                systemUserId = data.id;
-                initTree();
-            });
-            editor = CodeMirror.fromTextArea(document.getElementById("command-text"), {
-                lineNumbers: true,
-                lineWrapping: true,
-                mode: "shell"
-            });
-            editor.setSize(600, 100);
-            var charWidth = editor.defaultCharWidth(), basePadding = 4;
-            editor.on("renderLine", function (cm, line, elt) {
-                var off = CodeMirror.countColumn(line.text, null, cm.getOption("tabSize")) * charWidth;
-                elt.style.textIndent = "-" + off + "px";
-                elt.style.paddingLeft = (basePadding + off) + "px";
-            });
-            editor.refresh();
+        $(".select2").select2({
+            dropdownAutoWidth: true,
+        }).on('select2:select', function (evt) {
+            var data = evt.params.data;
+            systemUserId = data.id;
             initTree();
-            initResultTerminal();
-        }).on('click', '.btn-execute', function () {
-            execute()
-        })
-    </script>
+        });
+        editor = CodeMirror.fromTextArea(document.getElementById("command-text"), {
+            lineNumbers: true,
+            lineWrapping: true,
+            mode: "shell"
+        });
+        editor.setSize(600, 100);
+        var charWidth = editor.defaultCharWidth(), basePadding = 4;
+        editor.on("renderLine", function (cm, line, elt) {
+            var off = CodeMirror.countColumn(line.text, null, cm.getOption("tabSize")) * charWidth;
+            elt.style.textIndent = "-" + off + "px";
+            elt.style.paddingLeft = (basePadding + off) + "px";
+        });
+        editor.refresh();
+        initTree();
+        initResultTerminal();
+    }).on('click', '.btn-execute', function () {
+        execute()
+    })
+</script>
 {% endblock %}
diff --git a/apps/ops/templates/ops/task_adhoc.html b/apps/ops/templates/ops/task_adhoc.html
index 9e544d6c5..49035c725 100644
--- a/apps/ops/templates/ops/task_adhoc.html
+++ b/apps/ops/templates/ops/task_adhoc.html
@@ -103,7 +103,7 @@ $(document).ready(function () {
                 if (!cellData) {
                     $(td).html("")
                 } else {
-                    $(td).html(cellData.user)
+                    $(td).html(cellData)
                 }
             }},
             {targets: 6, createdCell: function (td, cellData) {
@@ -118,8 +118,12 @@ $(document).ready(function () {
             }}
         ],
         ajax_url: '{% url "api-ops:adhoc-list" %}?task={{ object.pk }}',
-        columns: [{data: function(){return ""}}, {data: "short_id" }, {data: "hosts", orderable:false}, {data: "pattern", orderable:false},
-                  {data: "run_as"}, {data: "become", orderable:false}, {data: "date_created"}, {data: "id", orderable:false}]
+        columns: [
+            {data: function(){return ""}}, {data: "short_id"},
+            {data: "hosts", orderable:false}, {data: "pattern", orderable:false},
+            {data: "run_as"}, {data: "become_display", orderable:false},
+            {data: "date_created"}, {data: "id", orderable:false}
+        ]
     };
     jumpserver.initDataTable(options);
 }).on('click', '.celery-task-log', function () {
diff --git a/apps/ops/templates/ops/task_detail.html b/apps/ops/templates/ops/task_detail.html
index 07036b58a..d2e46cc35 100644
--- a/apps/ops/templates/ops/task_detail.html
+++ b/apps/ops/templates/ops/task_detail.html
@@ -80,11 +80,23 @@
                                         </tr>
                                         <tr>
                                             <td>{% trans 'Is finished' %}:</td>
-                                            <td><b>{{ object.latest_history.is_finished|yesno:"Yes,No,Unkown" }}</b></td>
+                                            <td><b>
+                                                {% if object.latest_history.is_finished %}
+                                                    {% trans 'Yes' %}
+                                                {% else %}
+                                                    {% trans 'No' %}
+                                                {% endif %}
+                                            </b></td>
                                         </tr>
                                         <tr>
                                             <td>{% trans 'Is success ' %}:</td>
-                                            <td><b>{{ object.latest_history.is_success|yesno:"Yes,No,Unkown" }}</b></td>
+                                            <td><b>
+                                                {% if object.latest_history.is_success %}
+                                                    {% trans 'Yes' %}
+                                                {% else %}
+                                                    {% trans 'No' %}
+                                                {% endif %}
+                                            </b></td>
                                         </tr>
                                         <tr>
                                             <td>{% trans 'Contents' %}:</td>
diff --git a/apps/ops/templates/ops/task_list.html b/apps/ops/templates/ops/task_list.html
index 83da62071..0d2095d78 100644
--- a/apps/ops/templates/ops/task_list.html
+++ b/apps/ops/templates/ops/task_list.html
@@ -10,7 +10,6 @@
             </th>
             <th class="text-left">{% trans 'Name' %}</th>
             <th class="text-center">{% trans 'Run times' %}</th>
-            <th class="text-center">{% trans 'Versions' %}</th>
             <th class="text-center">{% trans 'Hosts' %}</th>
             <th class="text-center">{% trans 'Success' %}</th>
             <th class="text-center">{% trans 'Date' %}</th>
@@ -36,34 +35,40 @@ $(document).ready(function () {
                 $(td).html(innerHtml);
             }},
             {targets: 2, createdCell: function (td, cellData) {
+                var summary = cellData ? cellData.stat : {failed: 0, success: 0, total: 0};
                 var innerHtml = '<span class="text-danger">failed</span>/<span class="text-navy">success</span>/total';
-                if (cellData) {
-                    innerHtml = innerHtml.replace('failed', cellData.failed)
-                        .replace('success', cellData.success)
-                        .replace('total', cellData.total);
-                    $(td).html(innerHtml);
-                } else {
-                    $(td).html('')
-                }
+                innerHtml = innerHtml.replace('failed', summary.failed)
+                    .replace('success', summary.success)
+                    .replace('total', summary.total);
+                $(td).html(innerHtml);
             }},
-            {targets: 5, createdCell: function (td, cellData) {
+            {targets: 3, createdCell: function (td, cellData) {
+                var hostsAmount = cellData ? cellData.hosts_amount : 0;
+                 $(td).html(hostsAmount)
+            }},
+            {targets: 4, createdCell: function (td, cellData) {
                 var successBtn = '<i class="fa fa-check text-navy"></i>';
                 var failedBtn = '<i class="fa fa-times text-danger"></i>';
-                if (cellData) {
+                if (cellData && cellData.is_success) {
                     $(td).html(successBtn)
                 } else {
                     $(td).html(failedBtn)
                 }
             }},
-            {targets: 6, createdCell: function (td, cellData) {
-                $(td).html(toSafeLocalDateStr(cellData));
+            {targets: 5, createdCell: function (td, cellData) {
+                if (cellData) {
+                    $(td).html(toSafeLocalDateStr(cellData.date_start));
+                } else {
+                    $(td).html('');
+                }
             }},
-            {targets: 7, createdCell: function (td, cellData) {
+            {targets: 6, createdCell: function (td, cellData) {
+                cellData = cellData ? cellData.timedelta : 0;
                 var delta = readableSecond(cellData);
                 $(td).html(delta);
             }},
             {
-                targets: 8,
+                targets: 7,
                 createdCell: function (td, cellData, rowData) {
                     var runBtn = '<a data-uid="ID" class="btn btn-xs btn-primary btn-run">{% trans "Run" %}</a> '.replace('ID', cellData);
                     var delBtn = '<a data-uid="ID" class="btn btn-xs btn-danger btn-del">{% trans "Delete" %}</a>'.replace('ID', cellData);
@@ -73,10 +78,11 @@ $(document).ready(function () {
         ],
         ajax_url: '{% url "api-ops:task-list" %}',
         columns: [
-            {data: "id"}, {data: "name", className: "text-left"}, {data: "history_summary", orderable: false},
-            {data: "versions", orderable: false}, {data: "assets_amount", orderable: false},
-            {data: "is_success", orderable: false}, {data: "date_updated"},
-            {data: "timedelta", orderable:false}, {data: "id", orderable: false},
+            {data: "id"}, {data: "name", className: "text-left"},
+            {data: "latest_history", orderable: false},
+            {data: "latest_history", orderable: false},
+            {data: "latest_history", orderable: false}, {data: "latest_history"},
+            {data: "latest_history", orderable:false}, {data: "id", orderable: false},
         ],
         order: [],
         op_html: $('#actions').html()
diff --git a/apps/ops/urls/view_urls.py b/apps/ops/urls/view_urls.py
index f8428a667..13759c3f2 100644
--- a/apps/ops/urls/view_urls.py
+++ b/apps/ops/urls/view_urls.py
@@ -20,5 +20,5 @@ urlpatterns = [
     path('celery/task/<uuid:pk>/log/', views.CeleryTaskLogView.as_view(), name='celery-task-log'),
 
     path('command-execution/', views.CommandExecutionListView.as_view(), name='command-execution-list'),
-    path('command-execution/start/', views.CommandExecutionStartView.as_view(), name='command-execution-start'),
+    path('command-execution/create/', views.CommandExecutionCreateView.as_view(), name='command-execution-create'),
 ]
diff --git a/apps/ops/views/command.py b/apps/ops/views/command.py
index d9b1f61be..87e0528c6 100644
--- a/apps/ops/views/command.py
+++ b/apps/ops/views/command.py
@@ -15,7 +15,7 @@ from ..forms import CommandExecutionForm
 
 
 __all__ = [
-    'CommandExecutionListView', 'CommandExecutionStartView'
+    'CommandExecutionListView', 'CommandExecutionCreateView'
 ]
 
 
@@ -55,7 +55,7 @@ class CommandExecutionListView(PermissionsMixin, DatetimeSearchMixin, ListView):
         return super().get_context_data(**kwargs)
 
 
-class CommandExecutionStartView(PermissionsMixin, TemplateView):
+class CommandExecutionCreateView(PermissionsMixin, TemplateView):
     template_name = 'ops/command_execution_create.html'
     form_class = CommandExecutionForm
     permission_classes = [IsValidUser]
diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py
index d83c15b6f..7ab4cf66b 100644
--- a/apps/perms/serializers/user_permission.py
+++ b/apps/perms/serializers/user_permission.py
@@ -46,6 +46,7 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
     被授权资产的数据结构
     """
     protocols = ProtocolsField(label=_('Protocols'), required=False, read_only=True)
+    platform = serializers.ReadOnlyField(source='platform_base')
 
     class Meta:
         model = Asset
diff --git a/apps/perms/utils/asset_permission.py b/apps/perms/utils/asset_permission.py
index f71f83663..27c839d59 100644
--- a/apps/perms/utils/asset_permission.py
+++ b/apps/perms/utils/asset_permission.py
@@ -437,7 +437,7 @@ def sort_assets(assets, order_by='hostname', reverse=False):
 
 class ParserNode:
     nodes_only_fields = ("key", "value", "id")
-    assets_only_fields = ("platform", "hostname", "id", "ip", "protocols")
+    assets_only_fields = ("hostname", "id", "ip", "protocols", "org_id")
     system_users_only_fields = (
         "id", "name", "username", "protocol", "priority", "login_mode",
     )
@@ -445,7 +445,6 @@ class ParserNode:
     @staticmethod
     def parse_node_to_tree_node(node):
         name = '{} ({})'.format(node.value, node.assets_amount)
-        # name = node.value
         data = {
             'id': node.key,
             'name': name,
@@ -468,7 +467,7 @@ class ParserNode:
     @staticmethod
     def parse_asset_to_tree_node(node, asset):
         icon_skin = 'file'
-        platform = asset.platform.lower()
+        platform = asset.platform_base.lower()
         if platform == 'windows':
             icon_skin = 'windows'
         elif platform == 'linux':
@@ -489,8 +488,8 @@ class ParserNode:
                     'hostname': asset.hostname,
                     'ip': asset.ip,
                     'protocols': asset.protocols_as_list,
-                    'platform': asset.platform,
-                    "org_name": asset.org_name,
+                    'platform': asset.platform_base,
+                    'org_name': asset.org_name,
                 },
             }
         }
diff --git a/apps/settings/models.py b/apps/settings/models.py
index 0cbd9dd0e..75b56bb54 100644
--- a/apps/settings/models.py
+++ b/apps/settings/models.py
@@ -5,9 +5,7 @@ from django.db.utils import ProgrammingError, OperationalError
 from django.utils.translation import ugettext_lazy as _
 from django.core.cache import cache
 
-from common.utils import get_signer
-
-signer = get_signer()
+from common.utils import signer
 
 
 class SettingQuerySet(models.QuerySet):
diff --git a/apps/templates/_base_asset_tree_list.html b/apps/templates/_base_asset_tree_list.html
index 399b8c3d0..a989a4da1 100644
--- a/apps/templates/_base_asset_tree_list.html
+++ b/apps/templates/_base_asset_tree_list.html
@@ -44,7 +44,6 @@ function toggleSpliter() {
             showTree = 1;
         });
     } else {
-        console.log("hide")
         $("#split-right").attr("class", "col-sm-9");
         $("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
         $("#split-left").show(500);
diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html
index b2c00e09d..578cedfa6 100644
--- a/apps/templates/_nav.html
+++ b/apps/templates/_nav.html
@@ -116,7 +116,7 @@
         </a>
         <ul class="nav nav-second-level">
             <li id="task"><a href="{% url 'ops:task-list' %}">{% trans 'Task list' %}</a></li>
-            <li id="command-execution"><a href="{% url 'ops:command-execution-start' %}">{% trans 'Batch command' %}</a></li>
+            <li id="command-execution"><a href="{% url 'ops:command-execution-create' %}">{% trans 'Batch command' %}</a></li>
             {% if request.user.is_superuser %}
             <li><a href="{% url 'flower-view' path='' %}" target="_blank" >{% trans 'Task monitor' %}</a></li>
             {% endif %}
diff --git a/apps/templates/_nav_user.html b/apps/templates/_nav_user.html
index c745c0cdc..8196a4f44 100644
--- a/apps/templates/_nav_user.html
+++ b/apps/templates/_nav_user.html
@@ -22,7 +22,7 @@
 
 {% if SECURITY_COMMAND_EXECUTION %}
 <li id="ops">
-    <a href="{% url 'ops:command-execution-start' %}">
+    <a href="{% url 'ops:command-execution-create' %}">
         <i class="fa fa-terminal" style="width: 14px"></i> <span class="nav-label">{% trans 'Command execution' %}</span><span class="label label-info pull-right"></span>
     </a>
 </li>
@@ -41,4 +41,4 @@
     <a href="{% url 'terminal:web-sftp' %}" target="_blank"><i class="fa fa-file" style="width: 14px"></i>
         <span class="nav-label">{% trans 'File manager' %}</span>
     </a>
-</li>
\ No newline at end of file
+</li>
diff --git a/apps/terminal/migrations/0017_auto_20191125_0931.py b/apps/terminal/migrations/0017_auto_20191125_0931.py
index db340afd7..c555e6db6 100644
--- a/apps/terminal/migrations/0017_auto_20191125_0931.py
+++ b/apps/terminal/migrations/0017_auto_20191125_0931.py
@@ -4,9 +4,8 @@ from django.db import migrations
 
 
 def get_storage_data(s):
-    from common.utils import get_signer
+    from common.utils import signer
     import json
-    signer = get_signer()
     value = s.value
     encrypted = s.encrypted
     if encrypted:
diff --git a/apps/users/models/user.py b/apps/users/models/user.py
index af2de523b..a062f6c99 100644
--- a/apps/users/models/user.py
+++ b/apps/users/models/user.py
@@ -17,15 +17,13 @@ from django.utils import timezone
 from django.shortcuts import reverse
 
 from orgs.utils import current_org
-from common.utils import get_signer, date_expired_default, get_logger, lazyproperty
+from common.utils import signer, date_expired_default, get_logger, lazyproperty
 from common import fields
 from ..signals import post_user_change_password
 
 
 __all__ = ['User']
 
-signer = get_signer()
-
 logger = get_logger(__file__)