From 284e8be45c9f9d2f539e4737a7d7f68cf208420a Mon Sep 17 00:00:00 2001
From: BaiJiangJie <bugatti_it@163.com>
Date: Tue, 23 Oct 2018 19:22:18 +0800
Subject: [PATCH 01/17] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E7=B3=BB?=
 =?UTF-8?q?=E7=BB=9F=E8=AE=BE=E7=BD=AE-=E5=91=BD=E4=BB=A4/=E5=BD=95?=
 =?UTF-8?q?=E5=83=8F=E5=AD=98=E5=82=A8=E9=A1=B5=E9=9D=A2(=E6=B7=BB?=
 =?UTF-8?q?=E5=8A=A0=EF=BC=8C=E5=88=A0=E9=99=A4)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 apps/common/api.py                            |  76 ++++++
 apps/common/forms.py                          |  36 +--
 apps/common/models.py                         |  24 ++
 .../common/command_storage_create.html        | 177 +++++++++++++
 .../common/replay_storage_create.html         | 242 ++++++++++++++++++
 .../templates/common/terminal_setting.html    | 104 +++++---
 apps/common/urls/api_urls.py                  |   4 +
 apps/common/urls/view_urls.py                 |   2 +
 apps/common/utils.py                          |  43 ++++
 apps/common/views.py                          |  33 ++-
 apps/templates/_base_create_update.html       |   4 +-
 apps/terminal/backends/__init__.py            |   7 +-
 apps/terminal/forms.py                        |  29 ++-
 13 files changed, 710 insertions(+), 71 deletions(-)
 create mode 100644 apps/common/templates/common/command_storage_create.html
 create mode 100644 apps/common/templates/common/replay_storage_create.html

diff --git a/apps/common/api.py b/apps/common/api.py
index e09cf9726..193132b7a 100644
--- a/apps/common/api.py
+++ b/apps/common/api.py
@@ -1,6 +1,9 @@
 # -*- coding: utf-8 -*-
 #
+
+import os
 import json
+import jms_storage
 
 from rest_framework.views import Response, APIView
 from ldap3 import Server, Connection
@@ -10,6 +13,7 @@ from django.conf import settings
 
 from .permissions import IsOrgAdmin
 from .serializers import MailTestSerializer, LDAPTestSerializer
+from .models import Setting
 
 
 class MailTestingAPI(APIView):
@@ -85,6 +89,78 @@ class LDAPTestingAPI(APIView):
             return Response({"error": str(serializer.errors)}, status=401)
 
 
+class ReplayStorageCreateAPI(APIView):
+    permission_classes = (IsOrgAdmin,)
+
+    def post(self, request):
+        storage_data = request.data
+
+        if storage_data.get('TYPE') == 'ceph':
+            port = storage_data.get('PORT')
+            if port.isdigit():
+                storage_data['PORT'] = int(storage_data.get('PORT'))
+
+        storage_name = storage_data.pop('NAME')
+        data = {storage_name: storage_data}
+
+        if not self.is_valid(storage_data):
+            return Response({"error": _("Error: Account invalid")}, status=401)
+
+        Setting.save_storage('TERMINAL_REPLAY_STORAGE', data)
+        return Response({"msg": _('Create succeed')}, status=200)
+
+    @staticmethod
+    def is_valid(storage_data):
+        if storage_data.get('TYPE') == 'server':
+            return True
+        storage = jms_storage.get_object_storage(storage_data)
+        target = 'tests.py'
+        src = os.path.join(settings.BASE_DIR, 'common', target)
+        ok, msg = storage.upload(src=src, target=target)
+        if not ok:
+            return False
+        storage.delete(path=target)
+        return True
+
+
+class ReplayStorageDeleteAPI(APIView):
+
+    def post(self, request):
+        storage_name = str(request.data.get('name'))
+        Setting.delete_storage('TERMINAL_REPLAY_STORAGE', storage_name)
+        return Response({"msg": _('Delete succeed')}, status=200)
+
+
+class CommandStorageCreateAPI(APIView):
+    permission_classes = (IsOrgAdmin,)
+
+    def post(self, request):
+        storage_data = request.data
+        storage_name = storage_data.pop('NAME')
+        data = {storage_name: storage_data}
+        if not self.is_valid(storage_data):
+            return Response({"error": _("Error: Account invalid")}, status=401)
+
+        Setting.save_storage('TERMINAL_COMMAND_STORAGE', data)
+        return Response({"msg": _('Create succeed')}, status=200)
+
+    @staticmethod
+    def is_valid(storage_data):
+        if storage_data.get('TYPE') == 'server':
+            return True
+        storage = jms_storage.get_log_storage(storage_data)
+        return storage.ping()
+
+
+class CommandStorageDeleteAPI(APIView):
+    permission_classes = (IsOrgAdmin,)
+
+    def post(self, request):
+        storage_name = str(request.data.get('name'))
+        Setting.delete_storage('TERMINAL_COMMAND_STORAGE', storage_name)
+        return Response({"msg": _('Delete succeed')}, status=200)
+
+
 class DjangoSettingsAPI(APIView):
     def get(self, request):
         if not settings.DEBUG:
diff --git a/apps/common/forms.py b/apps/common/forms.py
index 610e1a83e..491ae779a 100644
--- a/apps/common/forms.py
+++ b/apps/common/forms.py
@@ -135,30 +135,34 @@ class TerminalSettingForm(BaseForm):
         ('hostname', _('Hostname')),
         ('ip', _('IP')),
     )
-    TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField(
-        choices=SORT_BY_CHOICES, initial='hostname', label=_("List sort by")
-    )
-    TERMINAL_HEARTBEAT_INTERVAL = forms.IntegerField(
-        initial=5, label=_("Heartbeat interval"), help_text=_("Units: seconds")
-    )
     TERMINAL_PASSWORD_AUTH = forms.BooleanField(
         initial=True, required=False, label=_("Password auth")
     )
     TERMINAL_PUBLIC_KEY_AUTH = forms.BooleanField(
         initial=True, required=False, label=_("Public key auth")
     )
-    TERMINAL_COMMAND_STORAGE = FormEncryptDictField(
-        label=_("Command storage"), help_text=_(
-            "Set terminal storage setting, `default` is the using as default,"
-            "You can set other storage and some terminal using"
-        )
+    TERMINAL_HEARTBEAT_INTERVAL = forms.IntegerField(
+        initial=5, label=_("Heartbeat interval"), help_text=_("Units: seconds")
     )
-    TERMINAL_REPLAY_STORAGE = FormEncryptDictField(
-        label=_("Replay storage"), help_text=_(
-            "Set replay storage setting, `default` is the using as default,"
-            "You can set other storage and some terminal using"
-        )
+    TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField(
+        choices=SORT_BY_CHOICES, initial='hostname', label=_("List sort by")
     )
+    # TERMINAL_COMMAND_STORAGE = FormEncryptDictField(
+    #     label=_("Command storage"), help_text=_(
+    #         "Set terminal storage setting, `default` is the using as default,"
+    #         "You can set other storage and some terminal using"
+    #     )
+    # )
+    # TERMINAL_REPLAY_STORAGE = FormEncryptDictField(
+    #     label=_("Replay storage"), help_text=_(
+    #         "Set replay storage setting, `default` is the using as default,"
+    #         "You can set other storage and some terminal using"
+    #     )
+    # )
+
+
+class TerminalCommandStorage(BaseForm):
+    pass
 
 
 class SecuritySettingForm(BaseForm):
diff --git a/apps/common/models.py b/apps/common/models.py
index 61f5512c9..812d491a9 100644
--- a/apps/common/models.py
+++ b/apps/common/models.py
@@ -67,6 +67,30 @@ class Setting(models.Model):
         except json.JSONDecodeError as e:
             raise ValueError("Json dump error: {}".format(str(e)))
 
+    @classmethod
+    def save_storage(cls, name, data):
+        obj = cls.objects.filter(name=name).first()
+        if not obj:
+            obj = cls()
+            obj.name = name
+            obj.encrypted = True
+            obj.cleaned_value = data
+        else:
+            value = obj.cleaned_value
+            value.update(data)
+            obj.cleaned_value = value
+        obj.save()
+        return obj
+
+    @classmethod
+    def delete_storage(cls, name, storage_name):
+        obj = cls.objects.get(name=name)
+        value = obj.cleaned_value
+        value.pop(storage_name, '')
+        obj.cleaned_value = value
+        obj.save()
+        return True
+
     @classmethod
     def refresh_all_settings(cls):
         try:
diff --git a/apps/common/templates/common/command_storage_create.html b/apps/common/templates/common/command_storage_create.html
new file mode 100644
index 000000000..0f631161f
--- /dev/null
+++ b/apps/common/templates/common/command_storage_create.html
@@ -0,0 +1,177 @@
+{#{% extends 'base.html' %}#}
+{% extends '_base_create_update.html' %}
+{% load static %}
+{% load bootstrap3 %}
+{% load i18n %}
+{% load common_tags %}
+
+{% block content %}
+    <div class="wrapper wrapper-content animated fadeInRight">
+        <div class="row">
+            <div class="col-sm-12">
+                <div class="ibox float-e-margins">
+                    <div class="ibox-title">
+                        <h5>{{ action }}</h5>
+                        <div class="ibox-tools">
+                            <a class="collapse-link">
+                                <i class="fa fa-chevron-up"></i>
+                            </a>
+                            <a class="dropdown-toggle" data-toggle="dropdown" href="#">
+                                <i class="fa fa-wrench"></i>
+                            </a>
+                            <a class="close-link">
+                                <i class="fa fa-times"></i>
+                            </a>
+                        </div>
+                    </div>
+
+                    <div class="ibox-content">
+                        <form action="" method="POST" class="form-horizontal">
+                            <div class="form-group">
+                                <label class="col-md-2 control-label" for="id_type">{% trans "Type" %}</label>
+                                <div class="col-md-9">
+                                    <select id="id_type" class="selector form-control">
+                                        <option value ="server" selected="selected">server</option>
+                                        <option value ="es">es (elasticsearch)</option>
+                                    </select>
+                                </div>
+                            </div>
+
+                            <div class="form-group">
+                                <label class="col-md-2 control-label" for="id_name">{% trans "Name" %}</label>
+                                <div class="col-md-9">
+                                    <input id="id_name" class="form-control" type="text" name="NAME" value="">
+                                    <div class="help-block">* required</div>
+                                    <div id="id_error" style="color: red;"></div>
+                                </div>
+                            </div>
+
+                            <div class="form-group" style="display: none;" >
+                                <label class="col-md-2 control-label" for="id_hosts">{% trans "Hosts" %}</label>
+                                <div class="col-md-9">
+                                    <input id="id_hosts" class="form-control" type="text" name="HOSTS" value="">
+                                    <div class="help-block">如果有多台主机,请使用逗号 ( , ) 进行分割</div>
+                                </div>
+                            </div>
+
+{#                            <div class="form-group" style="display: none;" >#}
+{#                                <label class="col-md-2 control-label" for="id_other">{% trans "Other" %}</label>#}
+{#                                <div class="col-md-9">#}
+{#                                    <input id="id_other" class="form-control" type="text" name="OTHER" value="">#}
+{#                                </div>#}
+{#                            </div>#}
+
+                            <div class="form-group" style="display: none;" >
+                                <label class="col-md-2 control-label" for="id_bucket">{% trans "Index" %}</label>
+                                <div class="col-md-9">
+                                    <input id="id_index" class="form-control" type="text" name="INDEX" value="jumpserver">
+                                </div>
+                            </div>
+
+                            <div class="form-group" style="display: none;" >
+                                <label class="col-md-2 control-label" for="id_doc_type">{% trans "Doc type" %}</label>
+                                <div class="col-md-9">
+                                    <input id="id_doc_type" class="form-control" type="text" name="DOC_TYPE" value="command_store">
+                                </div>
+                            </div>
+
+                            <div class="hr-line-dashed"></div>
+                            <div class="form-group">
+                                <div class="col-sm-4 col-sm-offset-2">
+                                    <button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
+                                    <a class="btn btn-primary" type="" id="id_submit_button" >{% trans 'Submit' %}</a>
+                                </div>
+                            </div>
+                        </form>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+{% endblock %}
+
+{% block custom_foot_js %}
+<script>
+
+var field_of_all, need_get_field_of_server, need_get_field_of_es;
+
+function showField(field){
+    $.each(field, function(index, value){
+        $(value).parent('div').parent('div').css('display', '');
+    });
+}
+
+function hiddenField(field){
+    $.each(field, function(index, value){
+        $(value).parent('div').parent('div').css('display', 'none');
+    })
+}
+
+function getFieldByType(type){
+
+    if(type === 'server'){
+        return need_get_field_of_server
+    }
+    else if(type === 'es'){
+        return need_get_field_of_es
+    }
+}
+
+function ajaxAPI(url, data, success, error){
+    $.ajax({
+        url: url,
+        data: data,
+        method: 'POST',
+        contentType: 'application/json; charset=utf-8',
+        success: success,
+        error: error
+    })
+}
+
+$(document).ready(function() {
+    var name_id = '#id_name';
+    var hosts_id = '#id_hosts';
+    {#var other_id = '#id_other';#}
+    var index_id = '#id_index';
+    var doc_type_id = '#id_doc_type';
+
+    field_of_all = [name_id, hosts_id, index_id, doc_type_id];
+    need_get_field_of_server = [name_id];
+    need_get_field_of_es = [name_id, hosts_id, index_id, doc_type_id];
+})
+.on('change', '.selector', function(){
+    var type = $('.selector').val();
+    console.log(type);
+    hiddenField(field_of_all);
+    var field = getFieldByType(type);
+    showField(field)
+})
+
+.on('click', '#id_submit_button', function(){
+    var type = $('.selector').val();
+    var field = getFieldByType(type);
+    var data = {'TYPE': type};
+    $.each(field, function(index, id_field){
+        var name = $(id_field).attr('name');
+        var value =  $(id_field).val();
+        if(name === 'HOSTS'){
+            data[name] = value.split(',');
+        }
+        else{
+            data[name] = value
+        }
+    });
+    var url = "{% url 'api-common:command-storage-create' %}";
+    var success = function(data, textStatus) {
+        console.log(data, textStatus);
+        location = "{% url 'common:terminal-setting' %}";
+    };
+    var error = function(data,  textStatus) {
+        var error_msg = data.responseJSON.error;
+        $('#id_error').html(error_msg)
+    };
+    ajaxAPI(url, JSON.stringify(data), success, error)
+
+})
+</script>
+{% endblock %}
diff --git a/apps/common/templates/common/replay_storage_create.html b/apps/common/templates/common/replay_storage_create.html
new file mode 100644
index 000000000..d717a508d
--- /dev/null
+++ b/apps/common/templates/common/replay_storage_create.html
@@ -0,0 +1,242 @@
+{#{% extends 'base.html' %}#}
+{% extends '_base_create_update.html' %}
+{% load static %}
+{% load bootstrap3 %}
+{% load i18n %}
+{% load common_tags %}
+
+{% block content %}
+    <div class="wrapper wrapper-content animated fadeInRight">
+        <div class="row">
+            <div class="col-sm-12">
+                <div class="ibox float-e-margins">
+                    <div class="ibox-title">
+                        <h5>{{ action }}</h5>
+                        <div class="ibox-tools">
+                            <a class="collapse-link">
+                                <i class="fa fa-chevron-up"></i>
+                            </a>
+                            <a class="dropdown-toggle" data-toggle="dropdown" href="#">
+                                <i class="fa fa-wrench"></i>
+                            </a>
+                            <a class="close-link">
+                                <i class="fa fa-times"></i>
+                            </a>
+                        </div>
+                    </div>
+
+                    <div class="ibox-content">
+                        <form action="" method="POST" class="form-horizontal">
+                            <div class="form-group">
+                                <label class="col-md-2 control-label" for="id_type">{% trans "Type" %}</label>
+                                <div class="col-md-9">
+                                    <select id="id_type" class="selector form-control">
+                                        <option value ="server" selected="selected">server</option>
+                                        <option value ="s3">s3</option>
+                                        <option value="oss">oss</option>
+                                        <option value ="azure">azure</option>
+                                        <option value="ceph">ceph</option>
+                                    </select>
+                                </div>
+                            </div>
+
+                            <div class="form-group">
+                                <label class="col-md-2 control-label" for="id_name">{% trans "Name" %}</label>
+                                <div class="col-md-9">
+                                    <input id="id_name" class="form-control" type="text" name="NAME" value="">
+                                    <div class="help-block">* required</div>
+{#                                    <div id="id_error" style="display: none; color: red;">{% trans 'Error: Account invalid' %}</div>#}
+                                    <div id="id_error" style="color: red;"></div>
+                                </div>
+                            </div>
+
+                            <div class="form-group" style="display: none;" >
+                                <label class="col-md-2 control-label" for="id_host">{% trans "Host" %}</label>
+                                <div class="col-md-9">
+                                    <input id="id_host" class="form-control" type="text" name="HOSTNAME" value="">
+                                </div>
+                            </div>
+
+                            <div class="form-group" style="display: none;" >
+                                <label class="col-md-2 control-label" for="id_port">{% trans "Port" %}</label>
+                                <div class="col-md-9">
+                                    <input id="id_port" class="form-control" type="text" name="PORT" value="">
+                                </div>
+                            </div>
+
+                            <div class="form-group" style="display: none;" >
+                                <label class="col-md-2 control-label" for="id_bucket">{% trans "Bucket" %}</label>
+                                <div class="col-md-9">
+                                    <input id="id_bucket" class="form-control" type="text" name="BUCKET" value="">
+                                </div>
+                            </div>
+
+                            <div class="form-group" style="display: none;" >
+                                <label class="col-md-2 control-label" for="id_access_key">{% trans "Access key" %}</label>
+                                <div class="col-md-9">
+                                    <input id="id_access_key" class="form-control" type="text" name="ACCESS_KEY" value="">
+                                </div>
+                            </div>
+
+                            <div class="form-group" style="display: none;" >
+                                <label class="col-md-2 control-label" for="id_secret_key">{% trans "Secret key" %}</label>
+                                <div class="col-md-9">
+                                    <input id="id_secret_key" class="form-control" type="text" name="SECRET_KEY" value="">
+                                </div>
+                            </div>
+
+                            <div class="form-group" style="display: none;" >
+                                <label class="col-md-2 control-label" for="id_container_name">{% trans "Container name" %}</label>
+                                <div class="col-md-9">
+                                    <input id="id_container_name" class="form-control" type="text" name="CONTAINER_NAME" value="">
+                                </div>
+                            </div>
+
+                            <div class="form-group" style="display: none;" >
+                                <label class="col-md-2 control-label" for="id_account_name">{% trans "Account name" %}</label>
+                                <div class="col-md-9">
+                                    <input id="id_account_name" class="form-control" type="text" name="ACCOUNT_NAME" value="">
+                                </div>
+                            </div>
+
+                            <div class="form-group" style="display: none;" >
+                                <label class="col-md-2 control-label" for="id_account_key">{% trans "Account key" %}</label>
+                                <div class="col-md-9">
+                                    <input id="id_account_key" class="form-control" type="text" name="ACCOUNT_KEY" value="">
+                                </div>
+                            </div>
+
+                            <div class="form-group" style="display: none;" >
+                                <label class="col-md-2 control-label" for="id_endpoint">{% trans "Endpoint" %}</label>
+                                <div class="col-md-9">
+                                    <input id="id_endpoint" class="form-control" type="text" name="ENDPOINT" value="">
+                                </div>
+                            </div>
+
+                            <div class="form-group" style="display: none;" >
+                                <label class="col-md-2 control-label" for="id_endpoint_suffix">{% trans "Endpoint suffix" %}</label>
+                                <div class="col-md-9">
+                                    <input id="id_endpoint_suffix" class="form-control" type="text" name="ENDPOINT_SUFFIX" value="">
+                                </div>
+                            </div>
+
+                            <div class="form-group" style="display: none;" >
+                                <label class="col-md-2 control-label" for="id_region">{% trans "Region" %}</label>
+                                <div class="col-md-9">
+                                    <input id="id_region" class="form-control" type="text" name="REGION" value="">
+                                </div>
+                            </div>
+
+                            <div class="hr-line-dashed"></div>
+                            <div class="form-group">
+                                <div class="col-sm-4 col-sm-offset-2">
+                                    <button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
+                                    <a class="btn btn-primary" type="" id="id_submit_button" >{% trans 'Submit' %}</a>
+                                </div>
+                            </div>
+                        </form>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+{% endblock %}
+
+{% block custom_foot_js %}
+<script>
+
+var field_of_all, need_get_field_of_server, need_get_field_of_s3,
+    need_get_field_of_oss, need_get_field_of_azure, need_get_field_of_ceph;
+
+function showField(field){
+    $.each(field, function(index, value){
+        $(value).parent('div').parent('div').css('display', '');
+    });
+}
+
+function hiddenField(field){
+    $.each(field, function(index, value){
+        $(value).parent('div').parent('div').css('display', 'none');
+    })
+}
+
+function getFieldByType(type){
+
+    if(type === 'server'){
+        return need_get_field_of_server
+    }
+    else if(type === 's3'){
+        return need_get_field_of_s3
+    }
+    else if(type === 'oss'){
+        return need_get_field_of_oss
+    }
+    else if(type === 'azure'){
+        return need_get_field_of_azure
+    }
+    else if(type === 'ceph'){
+        return need_get_field_of_ceph
+    }
+}
+
+function ajaxAPI(url, data, success, error){
+    $.ajax({
+        url: url,
+        data: data,
+        method: 'POST',
+        contentType: 'application/json; charset=utf-8',
+        success: success,
+        error: error
+    })
+}
+
+$(document).ready(function() {
+    var name_id = '#id_name';
+    var host_id = '#id_host';
+    var port_id = '#id_port';
+    var bucket_id = '#id_bucket';
+    var access_key_id = '#id_access_key';
+    var secret_key_id = '#id_secret_key';
+    var container_name_id = '#id_container_name';
+    var account_name_id = '#id_account_name';
+    var account_key_id = '#id_account_key';
+    var endpoint_id = '#id_endpoint';
+    var endpoint_suffix_id = '#id_endpoint_suffix';
+    var region_id = '#id_region';
+
+    field_of_all = [name_id, host_id, port_id, bucket_id, access_key_id, secret_key_id, container_name_id, account_name_id, account_key_id, endpoint_id, endpoint_suffix_id, region_id];
+    need_get_field_of_server = [name_id];
+    need_get_field_of_s3 = [name_id, bucket_id, access_key_id, secret_key_id, region_id];
+    need_get_field_of_oss = [name_id, access_key_id, secret_key_id, endpoint_id];
+    need_get_field_of_azure = [name_id, container_name_id, account_name_id, account_key_id, endpoint_suffix_id];
+    need_get_field_of_ceph = [name_id, host_id, port_id, bucket_id, access_key_id, secret_key_id, region_id];
+})
+.on('change', '.selector', function(){
+    var type = $('.selector').val();
+    console.log(type);
+    hiddenField(field_of_all);
+    var field = getFieldByType(type);
+    showField(field)
+})
+
+.on('click', '#id_submit_button', function(){
+    var type = $('.selector').val();
+    var field = getFieldByType(type);
+    var data = {'TYPE': type};
+    $.each(field, function(index, id_field){
+        var name = $(id_field).attr('name');
+        data[name] = $(id_field).val();
+    });
+    var url = "{% url 'api-common:replay-storage-create' %}";
+    var success = function(data, textStatus) {
+        location = "{% url 'common:terminal-setting' %}";
+    };
+    var error = function(data,  textStatus) {
+        var error_msg = data.responseJSON.error;
+        $('#id_error').html(error_msg)
+    };
+    ajaxAPI(url, JSON.stringify(data), success, error)
+
+})
+</script>
+{% endblock %}
diff --git a/apps/common/templates/common/terminal_setting.html b/apps/common/templates/common/terminal_setting.html
index 320f628b0..f75dea79c 100644
--- a/apps/common/templates/common/terminal_setting.html
+++ b/apps/common/templates/common/terminal_setting.html
@@ -63,6 +63,14 @@
                                         {% endif %}
                                     {% endfor %}
 
+                                    <div class="form-group">
+                                        <div class="col-sm-4 col-sm-offset-2">
+                                            <button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
+                                            <button id="submit_button" class="btn btn-primary"
+                                                    type="submit">{% trans 'Submit' %}</button>
+                                        </div>
+                                    </div>
+
                                     <div class="hr-line-dashed"></div>
 
                                     <h3>{% trans "Command storage" %}</h3>
@@ -71,6 +79,7 @@
                                         <tr>
                                             <th>{% trans 'Name' %}</th>
                                             <th>{% trans 'Type' %}</th>
+                                            <th>{% trans 'Action' %}</th>
                                         </tr>
                                         </thead>
                                         <tbody>
@@ -78,10 +87,13 @@
                                             <tr>
                                                 <td>{{ name }}</td>
                                                 <td>{{ setting.TYPE }}</td>
+                                                <td><a class="btn btn-xs btn-danger m-l-xs btn-del-command" data-name="{{ name }}">{% trans 'Delete' %}</a></td>
                                             </tr>
                                         {% endfor %}
                                         </tbody>
                                     </table>
+                                    <a href="{% url 'common:command-storage-create' %}" class="btn btn-primary">{% trans 'Add' %}</a>
+
                                     <div class="hr-line-dashed"></div>
                                     <h3>{% trans "Replay storage" %}</h3>
                                     <table class="table table-hover " id="task-history-list-table">
@@ -89,6 +101,7 @@
                                         <tr>
                                             <th>{% trans 'Name' %}</th>
                                             <th>{% trans 'Type' %}</th>
+                                            <th>{% trans 'Action' %}</th>
                                         </tr>
                                         </thead>
                                         <tbody>
@@ -96,18 +109,14 @@
                                             <tr>
                                                 <td>{{ name }}</td>
                                                 <td>{{ setting.TYPE }}</td>
+                                                <td><a class="btn btn-xs btn-danger m-l-xs btn-del-replay" data-name="{{ name }}">{% trans 'Delete' %}</a></td>
                                             </tr>
                                         {% endfor %}
                                         </tbody>
                                     </table>
+                                    <a href="{% url 'common:replay-storage-create' %}" class="btn btn-primary">{% trans 'Add' %}</a>
+
                                     <div class="hr-line-dashed"></div>
-                                    <div class="form-group">
-                                        <div class="col-sm-4 col-sm-offset-2">
-                                            <button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
-                                            <button id="submit_button" class="btn btn-primary"
-                                                    type="submit">{% trans 'Submit' %}</button>
-                                        </div>
-                                    </div>
                                 </form>
                             </div>
                         </div>
@@ -116,40 +125,63 @@
             </div>
         </div>
     </div>
-    </div>
 {% endblock %}
 {% block custom_foot_js %}
-    <script>
-        $(document).ready(function () {
-        })
-            .on("click", ".btn-test", function () {
-                var data = {};
-                var form = $("form").serializeArray();
-                $.each(form, function (i, field) {
-                    data[field.name] = field.value;
-                });
+<script>
 
-                var the_url = "{% url 'api-common:ldap-testing' %}";
+function ajaxAPI(url, data, success, error, method){
+    $.ajax({
+        url: url,
+        data: data,
+        method: method,
+        contentType: 'application/json; charset=utf-8',
+        success: success,
+        error: error
+    })
+}
 
-                function error(message) {
-                    toastr.error(message)
-                }
+function deleteStorage($this, the_url){
+    var name = $this.data('name');
+    function doDelete(){
+        console.log('delete storage');
+        var data = {"name": name};
+        var method = 'POST';
+        var success = function(){
+            $this.parent().parent().remove();
+            toastr.success("{% trans 'Delete success' %}");
+        };
+        var error = function(){
+            toastr.error("{% trans 'Delete failed' %}}");
+        };
+        ajaxAPI(the_url, JSON.stringify(data), success, error, method);
+    }
+    swal({
+        title: "{% trans  'Are you sure about deleting it?' %}",
+        text: " [" + name + "] ",
+        type: "warning",
+        showCancelButton: true,
+        cancelButtonText: "{% trans 'Cancel' %}",
+        confirmButtonColor: "#ed5565",
+        confirmButtonText: "{% trans 'Confirm' %}",
+        closeOnConfirm: true
+    }, function () {
+        doDelete()
+    });
+}
 
-                function success(message) {
-                    toastr.success(message.msg)
-                }
+$(document).ready(function () {
 
-                APIUpdateAttr({
-                    url: the_url,
-                    body: JSON.stringify(data),
-                    method: "POST",
-                    flash_message: false,
-                    success: success,
-                    error: error
-                });
-            })
-            .on('click', '', function () {
+})
+.on('click', '.btn-del-replay', function(){
+    var $this = $(this);
+    var the_url = "{% url 'api-common:replay-storage-delete' %}";
+    deleteStorage($this, the_url);
+})
+.on('click', '.btn-del-command', function() {
+    var $this = $(this);
+    var the_url = "{% url 'api-common:command-storage-delete' %}";
+    deleteStorage($this, the_url)
+});
 
-            })
-    </script>
+</script>
 {% endblock %}
diff --git a/apps/common/urls/api_urls.py b/apps/common/urls/api_urls.py
index 5b44684ec..3c86a3a2a 100644
--- a/apps/common/urls/api_urls.py
+++ b/apps/common/urls/api_urls.py
@@ -9,5 +9,9 @@ app_name = 'common'
 urlpatterns = [
     path('mail/testing/', api.MailTestingAPI.as_view(), name='mail-testing'),
     path('ldap/testing/', api.LDAPTestingAPI.as_view(), name='ldap-testing'),
+    path('terminal/replay-storage/create/', api.ReplayStorageCreateAPI.as_view(), name='replay-storage-create'),
+    path('terminal/replay-storage/delete/', api.ReplayStorageDeleteAPI.as_view(), name='replay-storage-delete'),
+    path('terminal/command-storage/create/', api.CommandStorageCreateAPI.as_view(), name='command-storage-create'),
+    path('terminal/command-storage/delete/', api.CommandStorageDeleteAPI.as_view(), name='command-storage-delete'),
     # path('django-settings/', api.DjangoSettingsAPI.as_view(), name='django-settings'),
 ]
diff --git a/apps/common/urls/view_urls.py b/apps/common/urls/view_urls.py
index e7ccddd06..8c2b91297 100644
--- a/apps/common/urls/view_urls.py
+++ b/apps/common/urls/view_urls.py
@@ -11,5 +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'^terminal/replay-storage/create$', views.ReplayStorageCreateView.as_view(), name='replay-storage-create'),
+    url(r'^terminal/command-storage/create$', views.CommandStorageCreateView.as_view(), name='command-storage-create'),
     url(r'^security/$', views.SecuritySettingView.as_view(), name='security-setting'),
 ]
diff --git a/apps/common/utils.py b/apps/common/utils.py
index ec55f43a1..e8476194f 100644
--- a/apps/common/utils.py
+++ b/apps/common/utils.py
@@ -387,6 +387,49 @@ def get_request_ip(request):
     return login_ip
 
 
+def get_command_storage_or_create_default_storage():
+    from common.models import common_settings, Setting
+    name = 'TERMINAL_COMMAND_STORAGE'
+    default = {'default': {'TYPE': 'server'}}
+    command_storage = common_settings.TERMINAL_COMMAND_STORAGE
+    if command_storage is None:
+        obj = Setting()
+        obj.name = name
+        obj.encrypted = True
+        obj.cleaned_value = default
+        obj.save()
+    if isinstance(command_storage, dict) and not command_storage:
+        obj = Setting.objects.get(name=name)
+        value = obj.cleaned_value
+        value.update(default)
+        obj.cleaned_value = value
+        obj.save()
+    command_storage = common_settings.TERMINAL_COMMAND_STORAGE
+    return command_storage
+
+
+def get_replay_storage_or_create_default_storage():
+    from common.models import common_settings, Setting
+    name = 'TERMINAL_REPLAY_STORAGE'
+    default = {'default': {'TYPE': 'server'}}
+    replay_storage = common_settings.TERMINAL_REPLAY_STORAGE
+    if replay_storage is None:
+        obj = Setting()
+        obj.name = name
+        obj.encrypted = True
+        obj.cleaned_value = default
+        obj.save()
+        replay_storage = common_settings.TERMINAL_REPLAY_STORAGE
+    if isinstance(replay_storage, dict) and not replay_storage:
+        obj = Setting.objects.get(name=name)
+        value = obj.cleaned_value
+        value.update(default)
+        obj.cleaned_value = value
+        obj.save()
+    replay_storage = common_settings.TERMINAL_REPLAY_STORAGE
+    return replay_storage
+
+
 class TeeObj:
     origin_stdout = sys.stdout
 
diff --git a/apps/common/views.py b/apps/common/views.py
index 08c4828f9..04d844c04 100644
--- a/apps/common/views.py
+++ b/apps/common/views.py
@@ -4,10 +4,12 @@ from django.contrib import messages
 from django.utils.translation import ugettext as _
 from django.conf import settings
 
+from common.models import common_settings
 from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \
     TerminalSettingForm, SecuritySettingForm
 from common.permissions import SuperUserRequiredMixin
 from .signals import ldap_auth_enable
+from . import utils
 
 
 class BasicSettingView(SuperUserRequiredMixin, TemplateView):
@@ -95,14 +97,15 @@ class TerminalSettingView(SuperUserRequiredMixin, TemplateView):
     template_name = "common/terminal_setting.html"
 
     def get_context_data(self, **kwargs):
-        command_storage = settings.TERMINAL_COMMAND_STORAGE
-        replay_storage = settings.TERMINAL_REPLAY_STORAGE
+        command_storage = utils.get_command_storage_or_create_default_storage()
+        replay_storage = utils.get_replay_storage_or_create_default_storage()
+
         context = {
             'app': _('Settings'),
             'action': _('Terminal setting'),
             'form': self.form_class(),
             'replay_storage': replay_storage,
-            'command_storage': command_storage,
+            'command_storage': command_storage
         }
         kwargs.update(context)
         return super().get_context_data(**kwargs)
@@ -120,6 +123,30 @@ class TerminalSettingView(SuperUserRequiredMixin, TemplateView):
             return render(request, self.template_name, context)
 
 
+class ReplayStorageCreateView(SuperUserRequiredMixin, TemplateView):
+    template_name = 'common/replay_storage_create.html'
+
+    def get_context_data(self, **kwargs):
+        context = {
+            'app': _('Settings'),
+            'action': _('Create replay storage')
+        }
+        kwargs.update(context)
+        return super().get_context_data(**kwargs)
+
+
+class CommandStorageCreateView(SuperUserRequiredMixin, TemplateView):
+    template_name = 'common/command_storage_create.html'
+
+    def get_context_data(self, **kwargs):
+        context = {
+            'app': _('Settings'),
+            'action': _('Create command storage')
+        }
+        kwargs.update(context)
+        return super().get_context_data(**kwargs)
+
+
 class SecuritySettingView(SuperUserRequiredMixin, TemplateView):
     form_class = SecuritySettingForm
     template_name = "common/security_setting.html"
diff --git a/apps/templates/_base_create_update.html b/apps/templates/_base_create_update.html
index ec14da79b..be3813804 100644
--- a/apps/templates/_base_create_update.html
+++ b/apps/templates/_base_create_update.html
@@ -31,8 +31,8 @@
                     <div class="ibox-content">
                         {% if form.errors.all %}
                             <div class="alert alert-danger" style="margin: 20px auto 0px">
-                            {{ form.errors.all }}
-                        </div>
+                                {{ form.errors.all }}
+                            </div>
                         {% endif %}
                         {% block form %}
                         {% endblock %}
diff --git a/apps/terminal/backends/__init__.py b/apps/terminal/backends/__init__.py
index 9a1c338f5..1c454a32d 100644
--- a/apps/terminal/backends/__init__.py
+++ b/apps/terminal/backends/__init__.py
@@ -2,6 +2,9 @@ from importlib import import_module
 from django.conf import settings
 from .command.serializers import SessionCommandSerializer
 
+from common import utils
+from common.models import common_settings, Setting
+
 TYPE_ENGINE_MAPPING = {
     'elasticsearch': 'terminal.backends.command.es',
 }
@@ -16,7 +19,9 @@ def get_command_storage():
 
 def get_terminal_command_storages():
     storage_list = {}
-    for name, params in settings.TERMINAL_COMMAND_STORAGE.items():
+    command_storage = utils.get_command_storage_or_create_default_storage()
+
+    for name, params in command_storage.items():
         tp = params['TYPE']
         if tp == 'server':
             storage = get_command_storage()
diff --git a/apps/terminal/forms.py b/apps/terminal/forms.py
index dbba3cb01..55997a31b 100644
--- a/apps/terminal/forms.py
+++ b/apps/terminal/forms.py
@@ -2,36 +2,39 @@
 #
 
 from django import forms
-from django.conf import settings
 from django.utils.translation import ugettext_lazy as _
 
 from .models import Terminal
 
 
 def get_all_command_storage():
-    # storage_choices = []
-    from common.models import Setting
-    Setting.refresh_all_settings()
-    for k, v in settings.TERMINAL_COMMAND_STORAGE.items():
-        yield (k, k)
+    from common import utils
+    command_storage = utils.get_command_storage_or_create_default_storage()
+    command_storage_choice = []
+    for k, v in command_storage.items():
+        command_storage_choice.append((k, k))
+
+    return command_storage_choice
 
 
 def get_all_replay_storage():
-    # storage_choices = []
-    from common.models import Setting
-    Setting.refresh_all_settings()
-    for k, v in settings.TERMINAL_REPLAY_STORAGE.items():
-        yield (k, k)
+    from common import utils
+    replay_storage = utils.get_replay_storage_or_create_default_storage()
+    replay_storage_choice = []
+    for k, v in replay_storage.items():
+        replay_storage_choice.append((k, k))
+
+    return replay_storage_choice
 
 
 class TerminalForm(forms.ModelForm):
     command_storage = forms.ChoiceField(
-        choices=get_all_command_storage(),
+        choices=get_all_command_storage,
         label=_("Command storage"),
         help_text=_("Command can store in server db or ES, default to server, more see docs"),
     )
     replay_storage = forms.ChoiceField(
-        choices=get_all_replay_storage(),
+        choices=get_all_replay_storage,
         label=_("Replay storage"),
         help_text=_("Replay file can store in server disk, AWS S3, Aliyun OSS, default to server, more see docs"),
     )

From 279121384435ef2349d46d0abad11597cd5de951 Mon Sep 17 00:00:00 2001
From: BaiJiangJie <bugatti_it@163.com>
Date: Tue, 23 Oct 2018 20:41:01 +0800
Subject: [PATCH 02/17] =?UTF-8?q?[Update]=20=E6=9B=B4=E6=96=B0storage?=
 =?UTF-8?q?=E7=BF=BB=E8=AF=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 apps/common/api.py                            |   6 +-
 .../common/command_storage_create.html        |   2 +-
 .../common/replay_storage_create.html         |  30 +-
 apps/locale/zh/LC_MESSAGES/django.mo          | Bin 55096 -> 55532 bytes
 apps/locale/zh/LC_MESSAGES/django.po          | 345 +++++++++++-------
 5 files changed, 242 insertions(+), 141 deletions(-)

diff --git a/apps/common/api.py b/apps/common/api.py
index 193132b7a..f65efa8ca 100644
--- a/apps/common/api.py
+++ b/apps/common/api.py
@@ -116,11 +116,7 @@ class ReplayStorageCreateAPI(APIView):
         storage = jms_storage.get_object_storage(storage_data)
         target = 'tests.py'
         src = os.path.join(settings.BASE_DIR, 'common', target)
-        ok, msg = storage.upload(src=src, target=target)
-        if not ok:
-            return False
-        storage.delete(path=target)
-        return True
+        return storage.is_valid(src, target)
 
 
 class ReplayStorageDeleteAPI(APIView):
diff --git a/apps/common/templates/common/command_storage_create.html b/apps/common/templates/common/command_storage_create.html
index 0f631161f..ad28e4cb1 100644
--- a/apps/common/templates/common/command_storage_create.html
+++ b/apps/common/templates/common/command_storage_create.html
@@ -50,7 +50,7 @@
                                 <label class="col-md-2 control-label" for="id_hosts">{% trans "Hosts" %}</label>
                                 <div class="col-md-9">
                                     <input id="id_hosts" class="form-control" type="text" name="HOSTS" value="">
-                                    <div class="help-block">如果有多台主机,请使用逗号 ( , ) 进行分割</div>
+                                    <div class="help-block">{% trans 'Tips: If there are multiple hosts, separate them with a comma (,)' %}</div>
                                 </div>
                             </div>
 
diff --git a/apps/common/templates/common/replay_storage_create.html b/apps/common/templates/common/replay_storage_create.html
index d717a508d..6382d27e1 100644
--- a/apps/common/templates/common/replay_storage_create.html
+++ b/apps/common/templates/common/replay_storage_create.html
@@ -45,7 +45,6 @@
                                 <div class="col-md-9">
                                     <input id="id_name" class="form-control" type="text" name="NAME" value="">
                                     <div class="help-block">* required</div>
-{#                                    <div id="id_error" style="display: none; color: red;">{% trans 'Error: Account invalid' %}</div>#}
                                     <div id="id_error" style="color: red;"></div>
                                 </div>
                             </div>
@@ -53,77 +52,84 @@
                             <div class="form-group" style="display: none;" >
                                 <label class="col-md-2 control-label" for="id_host">{% trans "Host" %}</label>
                                 <div class="col-md-9">
-                                    <input id="id_host" class="form-control" type="text" name="HOSTNAME" value="">
+                                    <input id="id_host" class="form-control" type="text" name="HOSTNAME" value="" placeholder="Host">
                                 </div>
                             </div>
 
                             <div class="form-group" style="display: none;" >
                                 <label class="col-md-2 control-label" for="id_port">{% trans "Port" %}</label>
                                 <div class="col-md-9">
-                                    <input id="id_port" class="form-control" type="text" name="PORT" value="">
+                                    <input id="id_port" class="form-control" type="text" name="PORT" value="" placeholder="7480">
                                 </div>
                             </div>
 
                             <div class="form-group" style="display: none;" >
                                 <label class="col-md-2 control-label" for="id_bucket">{% trans "Bucket" %}</label>
                                 <div class="col-md-9">
-                                    <input id="id_bucket" class="form-control" type="text" name="BUCKET" value="">
+                                    <input id="id_bucket" class="form-control" type="text" name="BUCKET" value="" placeholder="Bucket">
                                 </div>
                             </div>
 
                             <div class="form-group" style="display: none;" >
                                 <label class="col-md-2 control-label" for="id_access_key">{% trans "Access key" %}</label>
                                 <div class="col-md-9">
-                                    <input id="id_access_key" class="form-control" type="text" name="ACCESS_KEY" value="">
+                                    <input id="id_access_key" class="form-control" type="text" name="ACCESS_KEY" value="" placeholder="Access key">
                                 </div>
                             </div>
 
                             <div class="form-group" style="display: none;" >
                                 <label class="col-md-2 control-label" for="id_secret_key">{% trans "Secret key" %}</label>
                                 <div class="col-md-9">
-                                    <input id="id_secret_key" class="form-control" type="text" name="SECRET_KEY" value="">
+                                    <input id="id_secret_key" class="form-control" type="text" name="SECRET_KEY" value="", placeholder="Secret key">
                                 </div>
                             </div>
 
                             <div class="form-group" style="display: none;" >
                                 <label class="col-md-2 control-label" for="id_container_name">{% trans "Container name" %}</label>
                                 <div class="col-md-9">
-                                    <input id="id_container_name" class="form-control" type="text" name="CONTAINER_NAME" value="">
+                                    <input id="id_container_name" class="form-control" type="text" name="CONTAINER_NAME" value="" placeholder="Container">
                                 </div>
                             </div>
 
                             <div class="form-group" style="display: none;" >
                                 <label class="col-md-2 control-label" for="id_account_name">{% trans "Account name" %}</label>
                                 <div class="col-md-9">
-                                    <input id="id_account_name" class="form-control" type="text" name="ACCOUNT_NAME" value="">
+                                    <input id="id_account_name" class="form-control" type="text" name="ACCOUNT_NAME" value="" placeholder="Account name">
                                 </div>
                             </div>
 
                             <div class="form-group" style="display: none;" >
                                 <label class="col-md-2 control-label" for="id_account_key">{% trans "Account key" %}</label>
                                 <div class="col-md-9">
-                                    <input id="id_account_key" class="form-control" type="text" name="ACCOUNT_KEY" value="">
+                                    <input id="id_account_key" class="form-control" type="text" name="ACCOUNT_KEY" value="" placeholder="Account key">
                                 </div>
                             </div>
 
                             <div class="form-group" style="display: none;" >
                                 <label class="col-md-2 control-label" for="id_endpoint">{% trans "Endpoint" %}</label>
                                 <div class="col-md-9">
-                                    <input id="id_endpoint" class="form-control" type="text" name="ENDPOINT" value="">
+                                    <input id="id_endpoint" class="form-control" type="text" name="ENDPOINT" value="" placeholder="Endpoint">
                                 </div>
                             </div>
 
                             <div class="form-group" style="display: none;" >
                                 <label class="col-md-2 control-label" for="id_endpoint_suffix">{% trans "Endpoint suffix" %}</label>
+{#                                <div class="col-md-9">#}
+{#                                    <input id="id_endpoint_suffix" class="form-control" type="text" name="ENDPOINT_SUFFIX" value="">#}
+{#                                    <div class="help-block">{% trans '' %}</div>#}
+{#                                </div>#}
                                 <div class="col-md-9">
-                                    <input id="id_endpoint_suffix" class="form-control" type="text" name="ENDPOINT_SUFFIX" value="">
+                                    <select id="id_endpoint_suffix" name="ENDPOINT_SUFFIX" class="endpoint-suffix-selector form-control">
+                                        <option value="core.chinacloudapi.cn" selected="selected">core.chinacloudapi.cn</option>
+                                        <option value="core.windows.net">core.windows.net</option>
+                                    </select>
                                 </div>
                             </div>
 
                             <div class="form-group" style="display: none;" >
                                 <label class="col-md-2 control-label" for="id_region">{% trans "Region" %}</label>
                                 <div class="col-md-9">
-                                    <input id="id_region" class="form-control" type="text" name="REGION" value="">
+                                    <input id="id_region" class="form-control" type="text" name="REGION" value="" placeholder="">
                                 </div>
                             </div>
 
diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo
index d88a5bfaef399ce91967de0660f258bf9005b329..b91198c4a60f99df3b260f584b3ef4661f0b65e4 100644
GIT binary patch
delta 17673
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
EKV!QHI{*Lx

delta 17354
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
LRbrL1n^XM{*><->

diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po
index 4c0305a50..1dd36ba15 100644
--- a/apps/locale/zh/LC_MESSAGES/django.po
+++ b/apps/locale/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-10-16 16:03+0800\n"
+"POT-Creation-Date: 2018-10-23 20:30+0800\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: ibuler <ibuler@qq.com>\n"
 "Language-Team: Jumpserver team<ibuler@qq.com>\n"
@@ -34,8 +34,8 @@ msgid "Test if the assets under the node are connectable: {}"
 msgstr "测试节点下资产是否可连接: {}"
 
 #: assets/forms/asset.py:27 assets/models/asset.py:83 assets/models/user.py:113
-#: assets/templates/assets/asset_detail.html:183
-#: assets/templates/assets/asset_detail.html:191
+#: assets/templates/assets/asset_detail.html:187
+#: assets/templates/assets/asset_detail.html:195
 #: assets/templates/assets/system_user_asset.html:95 perms/models.py:32
 msgid "Nodes"
 msgstr "节点管理"
@@ -43,7 +43,7 @@ msgstr "节点管理"
 #: assets/forms/asset.py:30 assets/forms/asset.py:69 assets/forms/asset.py:112
 #: assets/forms/asset.py:116 assets/models/asset.py:88
 #: assets/models/cluster.py:19 assets/models/user.py:73
-#: assets/templates/assets/asset_detail.html:73 templates/_nav.html:24
+#: assets/templates/assets/asset_detail.html:77 templates/_nav.html:24
 #: xpack/plugins/cloud/models.py:137
 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:67
 #: xpack/plugins/orgs/templates/orgs/org_list.html:18
@@ -110,6 +110,7 @@ msgstr "选择资产"
 #: assets/templates/assets/domain_gateway_list.html:58
 #: assets/templates/assets/system_user_asset.html:52
 #: assets/templates/assets/user_asset_list.html:163
+#: common/templates/common/replay_storage_create.html:60
 msgid "Port"
 msgstr "端口"
 
@@ -154,8 +155,10 @@ msgstr "不能包含特殊字符"
 #: assets/templates/assets/label_list.html:14
 #: assets/templates/assets/system_user_detail.html:58
 #: assets/templates/assets/system_user_list.html:29 common/models.py:30
-#: common/templates/common/terminal_setting.html:72
-#: common/templates/common/terminal_setting.html:90 ops/models/adhoc.py:37
+#: common/templates/common/command_storage_create.html:41
+#: common/templates/common/replay_storage_create.html:44
+#: common/templates/common/terminal_setting.html:80
+#: common/templates/common/terminal_setting.html:102 ops/models/adhoc.py:37
 #: ops/templates/ops/task_detail.html:59 ops/templates/ops/task_list.html:35
 #: orgs/models.py:12 perms/models.py:28
 #: perms/templates/perms/asset_permission_detail.html:62
@@ -243,7 +246,9 @@ msgstr "自动推送系统用户到资产"
 msgid ""
 "1-100, High level will be using login asset as default, if user was granted "
 "more than 2 system user"
-msgstr "1-100, 1最低优先级,100最高优先级。授权多个用户时,高优先级的系统用户将会作为默认登录用户"
+msgstr ""
+"1-100, 1最低优先级,100最高优先级。授权多个用户时,高优先级的系统用户将会作为"
+"默认登录用户"
 
 #: assets/forms/user.py:155
 msgid ""
@@ -281,7 +286,7 @@ msgid "Hostname"
 msgstr "主机名"
 
 #: assets/models/asset.py:75 assets/models/domain.py:49
-#: assets/models/user.py:117
+#: assets/models/user.py:117 assets/templates/assets/asset_detail.html:73
 #: assets/templates/assets/domain_gateway_list.html:59
 #: assets/templates/assets/system_user_detail.html:70
 #: assets/templates/assets/system_user_list.html:31
@@ -290,14 +295,14 @@ msgstr "主机名"
 msgid "Protocol"
 msgstr "协议"
 
-#: assets/models/asset.py:77 assets/templates/assets/asset_detail.html:97
+#: assets/models/asset.py:77 assets/templates/assets/asset_detail.html:101
 #: assets/templates/assets/user_asset_list.html:165
 msgid "Platform"
 msgstr "系统平台"
 
 #: assets/models/asset.py:84 assets/models/cmd_filter.py:20
 #: assets/models/domain.py:52 assets/models/label.py:21
-#: assets/templates/assets/asset_detail.html:105
+#: assets/templates/assets/asset_detail.html:109
 #: assets/templates/assets/user_asset_list.html:169
 msgid "Is active"
 msgstr "激活"
@@ -306,19 +311,19 @@ msgstr "激活"
 msgid "Public IP"
 msgstr "公网IP"
 
-#: assets/models/asset.py:92 assets/templates/assets/asset_detail.html:113
+#: assets/models/asset.py:92 assets/templates/assets/asset_detail.html:117
 msgid "Asset number"
 msgstr "资产编号"
 
-#: assets/models/asset.py:96 assets/templates/assets/asset_detail.html:77
+#: assets/models/asset.py:96 assets/templates/assets/asset_detail.html:81
 msgid "Vendor"
 msgstr "制造商"
 
-#: assets/models/asset.py:98 assets/templates/assets/asset_detail.html:81
+#: assets/models/asset.py:98 assets/templates/assets/asset_detail.html:85
 msgid "Model"
 msgstr "型号"
 
-#: assets/models/asset.py:100 assets/templates/assets/asset_detail.html:109
+#: assets/models/asset.py:100 assets/templates/assets/asset_detail.html:113
 msgid "Serial number"
 msgstr "序列号"
 
@@ -338,7 +343,7 @@ msgstr "CPU核数"
 msgid "CPU vcpus"
 msgstr "CPU总数"
 
-#: assets/models/asset.py:108 assets/templates/assets/asset_detail.html:89
+#: assets/models/asset.py:108 assets/templates/assets/asset_detail.html:93
 msgid "Memory"
 msgstr "内存"
 
@@ -350,7 +355,7 @@ msgstr "硬盘大小"
 msgid "Disk info"
 msgstr "硬盘信息"
 
-#: assets/models/asset.py:115 assets/templates/assets/asset_detail.html:101
+#: assets/models/asset.py:115 assets/templates/assets/asset_detail.html:105
 #: assets/templates/assets/user_asset_list.html:166
 msgid "OS"
 msgstr "操作系统"
@@ -368,7 +373,7 @@ msgid "Hostname raw"
 msgstr "主机名原始"
 
 #: assets/models/asset.py:125 assets/templates/assets/asset_create.html:34
-#: assets/templates/assets/asset_detail.html:220
+#: assets/templates/assets/asset_detail.html:224
 #: assets/templates/assets/asset_update.html:39 templates/_nav.html:26
 msgid "Labels"
 msgstr "标签管理"
@@ -377,7 +382,7 @@ msgstr "标签管理"
 #: assets/models/cluster.py:28 assets/models/cmd_filter.py:24
 #: assets/models/cmd_filter.py:54 assets/models/group.py:21
 #: assets/templates/assets/admin_user_detail.html:68
-#: assets/templates/assets/asset_detail.html:117
+#: assets/templates/assets/asset_detail.html:121
 #: assets/templates/assets/cmd_filter_detail.html:77
 #: assets/templates/assets/domain_detail.html:72
 #: assets/templates/assets/system_user_detail.html:100
@@ -412,7 +417,7 @@ msgstr "创建日期"
 #: assets/models/domain.py:51 assets/models/group.py:23
 #: assets/models/label.py:22 assets/templates/assets/admin_user_detail.html:72
 #: assets/templates/assets/admin_user_list.html:32
-#: assets/templates/assets/asset_detail.html:125
+#: assets/templates/assets/asset_detail.html:129
 #: assets/templates/assets/cmd_filter_detail.html:65
 #: assets/templates/assets/cmd_filter_list.html:27
 #: assets/templates/assets/cmd_filter_rule_list.html:62
@@ -533,8 +538,10 @@ msgstr "过滤器"
 #: assets/models/cmd_filter.py:46
 #: assets/templates/assets/cmd_filter_rule_list.html:58
 #: audits/templates/audits/login_log_list.html:50
-#: common/templates/common/terminal_setting.html:73
-#: common/templates/common/terminal_setting.html:91
+#: common/templates/common/command_storage_create.html:31
+#: common/templates/common/replay_storage_create.html:31
+#: common/templates/common/terminal_setting.html:81
+#: common/templates/common/terminal_setting.html:103
 msgid "Type"
 msgstr "类型"
 
@@ -568,6 +575,8 @@ msgstr "每行一个命令"
 #: assets/templates/assets/system_user_list.html:38 audits/models.py:37
 #: audits/templates/audits/operate_log_list.html:41
 #: audits/templates/audits/operate_log_list.html:67
+#: common/templates/common/terminal_setting.html:82
+#: common/templates/common/terminal_setting.html:104
 #: 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:42
 #: perms/templates/perms/asset_permission_list.html:60
@@ -843,10 +852,12 @@ msgstr "其它"
 #: assets/templates/assets/gateway_create_update.html:58
 #: assets/templates/assets/label_create_update.html:18
 #: common/templates/common/basic_setting.html:61
+#: common/templates/common/command_storage_create.html:81
 #: common/templates/common/email_setting.html:62
 #: common/templates/common/ldap_setting.html:62
+#: common/templates/common/replay_storage_create.html:139
 #: common/templates/common/security_setting.html:70
-#: common/templates/common/terminal_setting.html:106
+#: common/templates/common/terminal_setting.html:68
 #: perms/templates/perms/asset_permission_create_update.html:69
 #: terminal/templates/terminal/terminal_update.html:47
 #: users/templates/users/_user.html:46
@@ -874,10 +885,12 @@ msgstr "重置"
 #: assets/templates/assets/gateway_create_update.html:59
 #: assets/templates/assets/label_create_update.html:19
 #: common/templates/common/basic_setting.html:62
+#: common/templates/common/command_storage_create.html:82
 #: common/templates/common/email_setting.html:63
 #: common/templates/common/ldap_setting.html:63
+#: common/templates/common/replay_storage_create.html:140
 #: common/templates/common/security_setting.html:71
-#: common/templates/common/terminal_setting.html:108
+#: common/templates/common/terminal_setting.html:70
 #: perms/templates/perms/asset_permission_create_update.html:70
 #: terminal/templates/terminal/command_list.html:103
 #: terminal/templates/terminal/session_list.html:127
@@ -945,12 +958,12 @@ msgid "Quick update"
 msgstr "快速更新"
 
 #: assets/templates/assets/admin_user_assets.html:72
-#: assets/templates/assets/asset_detail.html:168
+#: assets/templates/assets/asset_detail.html:172
 msgid "Test connective"
 msgstr "测试可连接性"
 
 #: assets/templates/assets/admin_user_assets.html:75
-#: assets/templates/assets/asset_detail.html:171
+#: assets/templates/assets/asset_detail.html:175
 #: assets/templates/assets/system_user_asset.html:75
 #: assets/templates/assets/system_user_asset.html:161
 #: assets/templates/assets/system_user_detail.html:151
@@ -1003,6 +1016,8 @@ msgstr "更新"
 #: assets/templates/assets/label_list.html:39
 #: assets/templates/assets/system_user_detail.html:30
 #: assets/templates/assets/system_user_list.html:93 audits/models.py:33
+#: common/templates/common/terminal_setting.html:90
+#: common/templates/common/terminal_setting.html:112
 #: ops/templates/ops/task_list.html:72
 #: perms/templates/perms/asset_permission_detail.html:34
 #: perms/templates/perms/asset_permission_list.html:201
@@ -1031,12 +1046,13 @@ msgid "Select nodes"
 msgstr "选择节点"
 
 #: assets/templates/assets/admin_user_detail.html:100
-#: assets/templates/assets/asset_detail.html:200
+#: assets/templates/assets/asset_detail.html:204
 #: assets/templates/assets/asset_list.html:633
 #: assets/templates/assets/cmd_filter_detail.html:106
 #: assets/templates/assets/system_user_asset.html:112
 #: assets/templates/assets/system_user_detail.html:182
-#: assets/templates/assets/system_user_list.html:143 templates/_modal.html:22
+#: assets/templates/assets/system_user_list.html:143
+#: common/templates/common/terminal_setting.html:165 templates/_modal.html:22
 #: terminal/templates/terminal/session_detail.html:108
 #: users/templates/users/user_detail.html:382
 #: users/templates/users/user_detail.html:408
@@ -1096,28 +1112,28 @@ msgstr "选择需要修改属性"
 msgid "Select all"
 msgstr "全选"
 
-#: assets/templates/assets/asset_detail.html:85
+#: assets/templates/assets/asset_detail.html:89
 msgid "CPU"
 msgstr "CPU"
 
-#: assets/templates/assets/asset_detail.html:93
+#: assets/templates/assets/asset_detail.html:97
 msgid "Disk"
 msgstr "硬盘"
 
-#: assets/templates/assets/asset_detail.html:121
+#: assets/templates/assets/asset_detail.html:125
 #: users/templates/users/user_detail.html:115
 #: users/templates/users/user_profile.html:104
 msgid "Date joined"
 msgstr "创建日期"
 
-#: assets/templates/assets/asset_detail.html:137
+#: assets/templates/assets/asset_detail.html:141
 #: terminal/templates/terminal/session_detail.html:81
 #: users/templates/users/user_detail.html:134
 #: users/templates/users/user_profile.html:142
 msgid "Quick modify"
 msgstr "快速修改"
 
-#: assets/templates/assets/asset_detail.html:143
+#: assets/templates/assets/asset_detail.html:147
 #: assets/templates/assets/asset_list.html:95
 #: assets/templates/assets/user_asset_list.html:47 perms/models.py:34
 #: perms/models.py:82
@@ -1134,15 +1150,15 @@ msgstr "快速修改"
 msgid "Active"
 msgstr "激活中"
 
-#: assets/templates/assets/asset_detail.html:160
+#: assets/templates/assets/asset_detail.html:164
 msgid "Refresh hardware"
 msgstr "更新硬件信息"
 
-#: assets/templates/assets/asset_detail.html:163
+#: assets/templates/assets/asset_detail.html:167
 msgid "Refresh"
 msgstr "刷新"
 
-#: assets/templates/assets/asset_detail.html:300
+#: assets/templates/assets/asset_detail.html:304
 #: users/templates/users/user_detail.html:301
 #: users/templates/users/user_detail.html:328
 msgid "Update successfully!"
@@ -1271,6 +1287,7 @@ msgstr "删除选择资产"
 
 #: assets/templates/assets/asset_list.html:631
 #: assets/templates/assets/system_user_list.html:141
+#: common/templates/common/terminal_setting.html:163
 #: users/templates/users/user_detail.html:380
 #: users/templates/users/user_detail.html:406
 #: users/templates/users/user_detail.html:474
@@ -1747,22 +1764,34 @@ msgstr "用户管理"
 msgid "Login log"
 msgstr "登录日志"
 
-#: common/api.py:18
+#: common/api.py:22
 msgid "Test mail sent to {}, please check"
 msgstr "邮件已经发送{}, 请检查"
 
-#: common/api.py:42
+#: common/api.py:46
 msgid "Test ldap success"
 msgstr "连接LDAP成功"
 
-#: common/api.py:72
+#: common/api.py:76
 msgid "Search no entry matched in ou {}"
 msgstr "在ou:{}中没有匹配条目"
 
-#: common/api.py:81
+#: common/api.py:85
 msgid "Match {} s users"
 msgstr "匹配 {} 个用户"
 
+#: common/api.py:107 common/api.py:138
+msgid "Error: Account invalid"
+msgstr ""
+
+#: common/api.py:110 common/api.py:141
+msgid "Create succeed"
+msgstr "创建成功"
+
+#: common/api.py:127 common/api.py:157
+msgid "Delete succeed"
+msgstr "删除成功"
+
 #: common/const.py:6
 #, python-format
 msgid "<b>%(name)s</b> was created successfully"
@@ -1879,120 +1908,98 @@ msgid "Enable LDAP auth"
 msgstr "启用LDAP认证"
 
 #: common/forms.py:139
-msgid "List sort by"
-msgstr "资产列表排序"
-
-#: common/forms.py:142
-msgid "Heartbeat interval"
-msgstr "心跳间隔"
-
-#: common/forms.py:142 ops/models/adhoc.py:38
-msgid "Units: seconds"
-msgstr "单位: 秒"
-
-#: common/forms.py:145
 msgid "Password auth"
 msgstr "密码认证"
 
-#: common/forms.py:148
+#: common/forms.py:142
 msgid "Public key auth"
 msgstr "密钥认证"
 
-#: common/forms.py:151 common/templates/common/terminal_setting.html:68
-#: terminal/forms.py:30 terminal/models.py:22
-msgid "Command storage"
-msgstr "命令存储"
+#: common/forms.py:145
+msgid "Heartbeat interval"
+msgstr "心跳间隔"
 
-#: common/forms.py:152
-msgid ""
-"Set terminal storage setting, `default` is the using as default,You can set "
-"other storage and some terminal using"
-msgstr "设置终端命令存储,default是默认用的存储方式"
+#: common/forms.py:145 ops/models/adhoc.py:38
+msgid "Units: seconds"
+msgstr "单位: 秒"
 
-#: common/forms.py:157 common/templates/common/terminal_setting.html:86
-#: terminal/forms.py:35 terminal/models.py:23
-msgid "Replay storage"
-msgstr "录像存储"
+#: common/forms.py:148
+msgid "List sort by"
+msgstr "资产列表排序"
 
-#: common/forms.py:158
-msgid ""
-"Set replay storage setting, `default` is the using as default,You can set "
-"other storage and some terminal using"
-msgstr "设置终端录像存储,default是默认用的存储方式"
-
-#: common/forms.py:168
+#: common/forms.py:172
 msgid "MFA Secondary certification"
 msgstr "MFA 二次认证"
 
-#: common/forms.py:170
+#: common/forms.py:174
 msgid ""
 "After opening, the user login must use MFA secondary authentication (valid "
 "for all users, including administrators)"
 msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)"
 
-#: common/forms.py:177
+#: common/forms.py:181
 msgid "Limit the number of login failures"
 msgstr "限制登录失败次数"
 
-#: common/forms.py:182
+#: common/forms.py:186
 msgid "No logon interval"
 msgstr "禁止登录时间间隔"
 
-#: common/forms.py:184
+#: common/forms.py:188
 msgid ""
 "Tip :(unit/minute) if the user has failed to log in for a limited number of "
 "times, no login is allowed during this time interval."
 msgstr ""
 "提示: (单位: 分钟) 当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录."
 
-#: common/forms.py:190
+#: common/forms.py:194
 msgid "Connection max idle time"
 msgstr "SSH最大空闲时间"
 
-#: common/forms.py:192
+#: common/forms.py:196
 msgid ""
 "If idle time more than it, disconnect connection(only ssh now) Unit: minute"
 msgstr "提示: (单位: 分钟) 如果超过该配置没有操作,连接会被断开(仅ssh) "
 
-#: common/forms.py:198
+#: common/forms.py:202
 msgid "Password minimum length"
 msgstr "密码最小长度 "
 
-#: common/forms.py:204
+#: common/forms.py:208
 msgid "Must contain capital letters"
 msgstr "必须包含大写字母"
 
-#: common/forms.py:206
+#: common/forms.py:210
 msgid ""
 "After opening, the user password changes and resets must contain uppercase "
 "letters"
 msgstr "开启后,用户密码修改、重置必须包含大写字母"
 
-#: common/forms.py:212
+#: common/forms.py:216
 msgid "Must contain lowercase letters"
 msgstr "必须包含小写字母"
 
-#: common/forms.py:213
+#: common/forms.py:217
 msgid ""
 "After opening, the user password changes and resets must contain lowercase "
 "letters"
 msgstr "开启后,用户密码修改、重置必须包含小写字母"
 
-#: common/forms.py:219
+#: common/forms.py:223
 msgid "Must contain numeric characters"
 msgstr "必须包含数字字符"
 
-#: common/forms.py:220
+#: common/forms.py:224
 msgid ""
 "After opening, the user password changes and resets must contain numeric "
 "characters"
 msgstr "开启后,用户密码修改、重置必须包含数字字符"
 
-#: common/forms.py:226
+#: common/forms.py:230
 msgid "Must contain special characters"
 msgstr "必须包含特殊字符"
 
-#: common/forms.py:227
+#: common/forms.py:231
 msgid ""
 "After opening, the user password changes and resets must contain special "
 "characters"
@@ -2016,7 +2023,7 @@ msgstr "启用"
 #: common/templates/common/ldap_setting.html:15
 #: common/templates/common/security_setting.html:15
 #: common/templates/common/terminal_setting.html:16
-#: common/templates/common/terminal_setting.html:46 common/views.py:20
+#: common/templates/common/terminal_setting.html:46 common/views.py:22
 msgid "Basic setting"
 msgstr "基本设置"
 
@@ -2024,7 +2031,7 @@ msgstr "基本设置"
 #: common/templates/common/email_setting.html:18
 #: common/templates/common/ldap_setting.html:18
 #: common/templates/common/security_setting.html:18
-#: common/templates/common/terminal_setting.html:20 common/views.py:46
+#: common/templates/common/terminal_setting.html:20 common/views.py:48
 msgid "Email setting"
 msgstr "邮件设置"
 
@@ -2032,7 +2039,7 @@ msgstr "邮件设置"
 #: common/templates/common/email_setting.html:21
 #: common/templates/common/ldap_setting.html:21
 #: common/templates/common/security_setting.html:21
-#: common/templates/common/terminal_setting.html:24 common/views.py:72
+#: common/templates/common/terminal_setting.html:24 common/views.py:74
 msgid "LDAP setting"
 msgstr "LDAP设置"
 
@@ -2040,7 +2047,7 @@ msgstr "LDAP设置"
 #: common/templates/common/email_setting.html:24
 #: common/templates/common/ldap_setting.html:24
 #: common/templates/common/security_setting.html:24
-#: common/templates/common/terminal_setting.html:28 common/views.py:102
+#: common/templates/common/terminal_setting.html:28 common/views.py:105
 msgid "Terminal setting"
 msgstr "终端设置"
 
@@ -2048,10 +2055,72 @@ msgstr "终端设置"
 #: common/templates/common/email_setting.html:27
 #: common/templates/common/ldap_setting.html:27
 #: common/templates/common/security_setting.html:27
-#: common/templates/common/terminal_setting.html:31 common/views.py:130
+#: common/templates/common/terminal_setting.html:31 common/views.py:157
 msgid "Security setting"
 msgstr "安全设置"
 
+#: common/templates/common/command_storage_create.html:50
+#: ops/models/adhoc.py:159 ops/templates/ops/adhoc_detail.html:53
+#: ops/templates/ops/task_adhoc.html:59 ops/templates/ops/task_list.html:38
+msgid "Hosts"
+msgstr "主机"
+
+#: common/templates/common/command_storage_create.html:53
+msgid "Tips: If there are multiple hosts, separate them with a comma (,)"
+msgstr "提示: 如果有多台主机,请使用逗号 ( , ) 进行分割"
+
+#: common/templates/common/command_storage_create.html:65
+msgid "Index"
+msgstr "索引"
+
+#: common/templates/common/command_storage_create.html:72
+msgid "Doc type"
+msgstr "文档类型"
+
+#: common/templates/common/replay_storage_create.html:53
+#: templates/index.html:91
+msgid "Host"
+msgstr "主机"
+
+#: common/templates/common/replay_storage_create.html:67
+msgid "Bucket"
+msgstr "桶名称"
+
+#: common/templates/common/replay_storage_create.html:74
+msgid "Access key"
+msgstr ""
+
+#: common/templates/common/replay_storage_create.html:81
+msgid "Secret key"
+msgstr ""
+
+#: common/templates/common/replay_storage_create.html:88
+msgid "Container name"
+msgstr "容器名称"
+
+#: common/templates/common/replay_storage_create.html:95
+msgid "Account name"
+msgstr "账户名称"
+
+#: common/templates/common/replay_storage_create.html:102
+msgid "Account key"
+msgstr "账户密钥"
+
+#: common/templates/common/replay_storage_create.html:109
+msgid "Endpoint"
+msgstr "端点"
+
+#: common/templates/common/replay_storage_create.html:116
+msgid "Endpoint suffix"
+msgstr "端点后缀"
+
+#: common/templates/common/replay_storage_create.html:130
+#: xpack/plugins/cloud/models.py:206
+#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:83
+#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:64
+msgid "Region"
+msgstr "地域"
+
 #: common/templates/common/security_setting.html:42
 msgid "User login settings"
 msgstr "用户登录设置"
@@ -2060,20 +2129,65 @@ msgstr "用户登录设置"
 msgid "Password check rule"
 msgstr "密码校验规则"
 
+#: common/templates/common/terminal_setting.html:76 terminal/forms.py:33
+#: terminal/models.py:22
+msgid "Command storage"
+msgstr "命令存储"
+
+#: common/templates/common/terminal_setting.html:95
+#: common/templates/common/terminal_setting.html:117
+#: perms/templates/perms/asset_permission_asset.html:97
+#: perms/templates/perms/asset_permission_detail.html:157
+#: perms/templates/perms/asset_permission_user.html:97
+#: perms/templates/perms/asset_permission_user.html:125
+#: users/templates/users/user_group_detail.html:95
+#: xpack/plugins/orgs/templates/orgs/org_detail.html:93
+#: xpack/plugins/orgs/templates/orgs/org_detail.html:130
+msgid "Add"
+msgstr "添加"
+
+#: common/templates/common/terminal_setting.html:98 terminal/forms.py:38
+#: terminal/models.py:23
+msgid "Replay storage"
+msgstr "录像存储"
+
+#: common/templates/common/terminal_setting.html:151
+#, fuzzy
+#| msgid "Delete succeed"
+msgid "Delete success"
+msgstr "删除成功"
+
+#: common/templates/common/terminal_setting.html:154
+msgid "Delete failed"
+msgstr "删除失败"
+
+#: common/templates/common/terminal_setting.html:159
+msgid "Are you sure about deleting it?"
+msgstr "您确定删除吗?"
+
 #: common/validators.py:7
 msgid "Special char not allowed"
 msgstr "不能包含特殊字符"
 
-#: common/views.py:19 common/views.py:45 common/views.py:71 common/views.py:101
-#: common/views.py:129 templates/_nav.html:116
+#: common/views.py:21 common/views.py:47 common/views.py:73 common/views.py:104
+#: common/views.py:131 common/views.py:143 common/views.py:156
+#: templates/_nav.html:116
 msgid "Settings"
 msgstr "系统设置"
 
-#: common/views.py:30 common/views.py:56 common/views.py:84 common/views.py:114
-#: common/views.py:140
+#: common/views.py:32 common/views.py:58 common/views.py:86 common/views.py:117
+#: common/views.py:167
 msgid "Update setting successfully, please restart program"
 msgstr "更新设置成功, 请手动重启程序"
 
+#: common/views.py:132
+msgid "Create replay storage"
+msgstr "创建录像存储"
+
+#: common/views.py:144
+msgid "Create command storage"
+msgstr "创建命令存储"
+
 #: jumpserver/views.py:180
 msgid ""
 "<div>Luna is a separately deployed program, you need to deploy Luna, coco, "
@@ -2117,11 +2231,6 @@ msgstr "模式"
 msgid "Options"
 msgstr "选项"
 
-#: ops/models/adhoc.py:159 ops/templates/ops/adhoc_detail.html:53
-#: ops/templates/ops/task_adhoc.html:59 ops/templates/ops/task_list.html:38
-msgid "Hosts"
-msgstr "主机"
-
 #: ops/models/adhoc.py:160
 msgid "Run as admin"
 msgstr "再次执行"
@@ -2380,16 +2489,6 @@ msgstr "资产或资产组"
 msgid "Add asset to this permission"
 msgstr "添加资产"
 
-#: perms/templates/perms/asset_permission_asset.html:97
-#: perms/templates/perms/asset_permission_detail.html:157
-#: perms/templates/perms/asset_permission_user.html:97
-#: perms/templates/perms/asset_permission_user.html:125
-#: users/templates/users/user_group_detail.html:95
-#: xpack/plugins/orgs/templates/orgs/org_detail.html:93
-#: xpack/plugins/orgs/templates/orgs/org_detail.html:130
-msgid "Add"
-msgstr "添加"
-
 #: perms/templates/perms/asset_permission_asset.html:108
 msgid "Add node to this permission"
 msgstr "添加节点"
@@ -2665,10 +2764,6 @@ msgid ""
 "assets per user host per month, respectively."
 msgstr "以下图形分别描述一个月活跃用户和资产占所有用户主机的百分比"
 
-#: templates/index.html:91
-msgid "Host"
-msgstr "主机"
-
 #: templates/index.html:106 templates/index.html:121
 msgid "Top 10 assets in a week"
 msgstr "一周Top10资产"
@@ -2787,12 +2882,12 @@ msgstr "输入"
 msgid "Session"
 msgstr "会话"
 
-#: terminal/forms.py:31
+#: terminal/forms.py:34
 msgid "Command can store in server db or ES, default to server, more see docs"
 msgstr ""
 "命令支持存储到服务器端数据库、ES中,默认存储的服务器端数据库,更多查看文档"
 
-#: terminal/forms.py:36
+#: terminal/forms.py:39
 msgid ""
 "Replay file can store in server disk, AWS S3, Aliyun OSS, default to server, "
 "more see docs"
@@ -4160,12 +4255,6 @@ msgstr "同步实例任务历史"
 msgid "Instance"
 msgstr "实例"
 
-#: xpack/plugins/cloud/models.py:206
-#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:83
-#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:64
-msgid "Region"
-msgstr "地域"
-
 #: xpack/plugins/cloud/providers/base.py:73
 msgid "任务执行开始: {}"
 msgstr ""
@@ -4339,6 +4428,16 @@ msgstr "创建组织"
 msgid "Update org"
 msgstr "更新组织"
 
+#~ msgid ""
+#~ "Set terminal storage setting, `default` is the using as default,You can "
+#~ "set other storage and some terminal using"
+#~ msgstr "设置终端命令存储,default是默认用的存储方式"
+
+#~ msgid ""
+#~ "Set replay storage setting, `default` is the using as default,You can set "
+#~ "other storage and some terminal using"
+#~ msgstr "设置终端录像存储,default是默认用的存储方式"
+
 #~ msgid "Sync instance task detail"
 #~ msgstr "同步实例任务详情"
 

From 1bfef829f3c0ae2550bf8f1dff721c0137cb825e Mon Sep 17 00:00:00 2001
From: BaiJiangJie <bugatti_it@163.com>
Date: Wed, 24 Oct 2018 10:11:38 +0800
Subject: [PATCH 03/17] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9terminal?=
 =?UTF-8?q?=E8=A1=A8=E5=8D=95=E8=8E=B7=E5=8F=96storage,=20=E5=88=A0?=
 =?UTF-8?q?=E9=99=A4=E6=97=A0=E7=94=A8=E4=BB=A3=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 apps/common/forms.py   | 12 ------------
 apps/terminal/forms.py | 10 ++--------
 2 files changed, 2 insertions(+), 20 deletions(-)

diff --git a/apps/common/forms.py b/apps/common/forms.py
index 491ae779a..10ac4ec17 100644
--- a/apps/common/forms.py
+++ b/apps/common/forms.py
@@ -147,18 +147,6 @@ class TerminalSettingForm(BaseForm):
     TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField(
         choices=SORT_BY_CHOICES, initial='hostname', label=_("List sort by")
     )
-    # TERMINAL_COMMAND_STORAGE = FormEncryptDictField(
-    #     label=_("Command storage"), help_text=_(
-    #         "Set terminal storage setting, `default` is the using as default,"
-    #         "You can set other storage and some terminal using"
-    #     )
-    # )
-    # TERMINAL_REPLAY_STORAGE = FormEncryptDictField(
-    #     label=_("Replay storage"), help_text=_(
-    #         "Set replay storage setting, `default` is the using as default,"
-    #         "You can set other storage and some terminal using"
-    #     )
-    # )
 
 
 class TerminalCommandStorage(BaseForm):
diff --git a/apps/terminal/forms.py b/apps/terminal/forms.py
index 55997a31b..893f15b12 100644
--- a/apps/terminal/forms.py
+++ b/apps/terminal/forms.py
@@ -10,21 +10,15 @@ from .models import Terminal
 def get_all_command_storage():
     from common import utils
     command_storage = utils.get_command_storage_or_create_default_storage()
-    command_storage_choice = []
     for k, v in command_storage.items():
-        command_storage_choice.append((k, k))
-
-    return command_storage_choice
+        yield (k, k)
 
 
 def get_all_replay_storage():
     from common import utils
     replay_storage = utils.get_replay_storage_or_create_default_storage()
-    replay_storage_choice = []
     for k, v in replay_storage.items():
-        replay_storage_choice.append((k, k))
-
-    return replay_storage_choice
+        yield (k, k)
 
 
 class TerminalForm(forms.ModelForm):

From cdf8398169f455b840a7fb89ba55fd0f58d763f0 Mon Sep 17 00:00:00 2001
From: BaiJiangJie <bugatti_it@163.com>
Date: Wed, 24 Oct 2018 10:29:40 +0800
Subject: [PATCH 04/17] =?UTF-8?q?[Update]=20=E4=BF=AE=E5=A4=8D=E5=88=9B?=
 =?UTF-8?q?=E5=BB=BAes=E5=91=BD=E4=BB=A4=E5=AD=98=E5=82=A8=E6=A0=A1?=
 =?UTF-8?q?=E9=AA=8C=E6=9C=89=E6=95=88=E6=80=A7=E5=BC=82=E5=B8=B8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 apps/common/api.py | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/apps/common/api.py b/apps/common/api.py
index f65efa8ca..064c1a6b9 100644
--- a/apps/common/api.py
+++ b/apps/common/api.py
@@ -144,7 +144,11 @@ class CommandStorageCreateAPI(APIView):
     def is_valid(storage_data):
         if storage_data.get('TYPE') == 'server':
             return True
-        storage = jms_storage.get_log_storage(storage_data)
+        try:
+            storage = jms_storage.get_log_storage(storage_data)
+        except Exception:
+            return False
+
         return storage.ping()
 
 

From 1f502e02c71f178ad72b5e112f81bec4456c0f38 Mon Sep 17 00:00:00 2001
From: BaiJiangJie <bugatti_it@163.com>
Date: Wed, 24 Oct 2018 10:48:03 +0800
Subject: [PATCH 05/17] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9settings?=
 =?UTF-8?q?=E4=B8=BAcommon=5Fsettings?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 apps/common/tasks.py                          |  3 ++-
 .../templates/common/basic_setting.html       | 26 -------------------
 apps/common/utils.py                          |  3 ++-
 apps/static/js/jumpserver.js                  |  3 +++
 apps/users/views/login.py                     |  3 ++-
 5 files changed, 9 insertions(+), 29 deletions(-)

diff --git a/apps/common/tasks.py b/apps/common/tasks.py
index bfb005511..00420bc8b 100644
--- a/apps/common/tasks.py
+++ b/apps/common/tasks.py
@@ -3,6 +3,7 @@ from django.conf import settings
 from celery import shared_task
 from .utils import get_logger
 from .models import Setting
+from common.models import common_settings
 
 
 logger = get_logger(__file__)
@@ -28,7 +29,7 @@ def send_mail_async(*args, **kwargs):
 
     if len(args) == 3:
         args = list(args)
-        args[0] = settings.EMAIL_SUBJECT_PREFIX + args[0]
+        args[0] = common_settings.EMAIL_SUBJECT_PREFIX + args[0]
         args.insert(2, settings.EMAIL_HOST_USER)
         args = tuple(args)
 
diff --git a/apps/common/templates/common/basic_setting.html b/apps/common/templates/common/basic_setting.html
index 9c9258e33..17c8057bc 100644
--- a/apps/common/templates/common/basic_setting.html
+++ b/apps/common/templates/common/basic_setting.html
@@ -75,32 +75,6 @@
 {% block custom_foot_js %}
 <script>
 $(document).ready(function () {
-})
-.on("click", ".btn-test", function () {
-    var data = {};
-    var form = $("form").serializeArray();
-    $.each(form, function (i, field) {
-        data[field.name] = field.value;
-    });
-
-    var the_url = "{% url 'api-common:mail-testing' %}";
-
-    function error(message) {
-        toastr.error(message)
-    }
-
-    function success(message) {
-        toastr.success(message.msg)
-    }
-    APIUpdateAttr({
-        url: the_url,
-        body: JSON.stringify(data),
-        method: "POST",
-        flash_message: false,
-        success: success,
-        error: error
-    });
-
 })
 </script>
 {% endblock %}
diff --git a/apps/common/utils.py b/apps/common/utils.py
index e8476194f..5b870c088 100644
--- a/apps/common/utils.py
+++ b/apps/common/utils.py
@@ -37,7 +37,8 @@ def reverse(view_name, urlconf=None, args=None, kwargs=None,
                      kwargs=kwargs, current_app=current_app)
 
     if external:
-        url = settings.SITE_URL.strip('/') + url
+        from common.models import common_settings
+        url = common_settings.SITE_URL.strip('/') + url
     return url
 
 
diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js
index 5f2335c1d..207733618 100644
--- a/apps/static/js/jumpserver.js
+++ b/apps/static/js/jumpserver.js
@@ -152,6 +152,9 @@ function activeNav() {
         $('#' + app + ' #' + resource).addClass('active');
         $('#' + app + ' #' + resource + ' #' + item + ' a').css('color', '#ffffff');
     }
+    else if (app === 'settings'){
+        $("#" + app).addClass('active');
+    }
     else {
         $("#" + app).addClass('active');
         $('#' + app + ' #' + resource).addClass('active');
diff --git a/apps/users/views/login.py b/apps/users/views/login.py
index c4e7538e6..31021d75e 100644
--- a/apps/users/views/login.py
+++ b/apps/users/views/login.py
@@ -21,6 +21,7 @@ from formtools.wizard.views import SessionWizardView
 from django.conf import settings
 
 from common.utils import get_object_or_none, get_request_ip
+from common.models import common_settings
 from ..models import User, LoginLog
 from ..utils import send_reset_password_mail, check_otp_code, \
     redirect_user_first_login_or_index, get_user_or_tmp_user, \
@@ -318,7 +319,7 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
         user.is_public_key_valid = True
         user.save()
         context = {
-            'user_guide_url': settings.USER_GUIDE_URL
+            'user_guide_url': common_settings.USER_GUIDE_URL
         }
         return render(self.request, 'users/first_login_done.html', context)
 

From 6278900201a26b06e86b0f187bae4a9851dabee6 Mon Sep 17 00:00:00 2001
From: BaiJiangJie <bugatti_it@163.com>
Date: Wed, 24 Oct 2018 10:57:55 +0800
Subject: [PATCH 06/17] =?UTF-8?q?[Update]=20=E5=88=9B=E5=BB=BAes=E5=91=BD?=
 =?UTF-8?q?=E4=BB=A4=E5=AD=98=E5=82=A8=EF=BC=8C=E6=B7=BB=E5=8A=A0=E6=8F=90?=
 =?UTF-8?q?=E7=A4=BA=E4=BF=A1=E6=81=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 apps/common/templates/common/command_storage_create.html | 1 +
 1 file changed, 1 insertion(+)

diff --git a/apps/common/templates/common/command_storage_create.html b/apps/common/templates/common/command_storage_create.html
index ad28e4cb1..da79a3d97 100644
--- a/apps/common/templates/common/command_storage_create.html
+++ b/apps/common/templates/common/command_storage_create.html
@@ -51,6 +51,7 @@
                                 <div class="col-md-9">
                                     <input id="id_hosts" class="form-control" type="text" name="HOSTS" value="">
                                     <div class="help-block">{% trans 'Tips: If there are multiple hosts, separate them with a comma (,)' %}</div>
+                                    <div class="help-block">eg: http://www.jumpserver.a.com, http://www.jumpserver.b.com</div>
                                 </div>
                             </div>
 

From ebd92c79c7fcc5330b60d4eb9247a543032cbeec Mon Sep 17 00:00:00 2001
From: BaiJiangJie <bugatti_it@163.com>
Date: Wed, 24 Oct 2018 11:05:39 +0800
Subject: [PATCH 07/17] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E7=BF=BB?=
 =?UTF-8?q?=E8=AF=91=E5=B0=8F=E7=BB=86=E8=8A=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../common/command_storage_create.html        |  2 -
 .../templates/common/terminal_setting.html    |  2 +-
 apps/locale/zh/LC_MESSAGES/django.po          | 92 +++++++++----------
 3 files changed, 47 insertions(+), 49 deletions(-)

diff --git a/apps/common/templates/common/command_storage_create.html b/apps/common/templates/common/command_storage_create.html
index da79a3d97..a3a83f2e7 100644
--- a/apps/common/templates/common/command_storage_create.html
+++ b/apps/common/templates/common/command_storage_create.html
@@ -54,14 +54,12 @@
                                     <div class="help-block">eg: http://www.jumpserver.a.com, http://www.jumpserver.b.com</div>
                                 </div>
                             </div>
-
 {#                            <div class="form-group" style="display: none;" >#}
 {#                                <label class="col-md-2 control-label" for="id_other">{% trans "Other" %}</label>#}
 {#                                <div class="col-md-9">#}
 {#                                    <input id="id_other" class="form-control" type="text" name="OTHER" value="">#}
 {#                                </div>#}
 {#                            </div>#}
-
                             <div class="form-group" style="display: none;" >
                                 <label class="col-md-2 control-label" for="id_bucket">{% trans "Index" %}</label>
                                 <div class="col-md-9">
diff --git a/apps/common/templates/common/terminal_setting.html b/apps/common/templates/common/terminal_setting.html
index f75dea79c..0435e48e9 100644
--- a/apps/common/templates/common/terminal_setting.html
+++ b/apps/common/templates/common/terminal_setting.html
@@ -148,7 +148,7 @@ function deleteStorage($this, the_url){
         var method = 'POST';
         var success = function(){
             $this.parent().parent().remove();
-            toastr.success("{% trans 'Delete success' %}");
+            toastr.success("{% trans 'Delete succeed' %}");
         };
         var error = function(){
             toastr.error("{% trans 'Delete failed' %}}");
diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po
index 1dd36ba15..402d71d67 100644
--- a/apps/locale/zh/LC_MESSAGES/django.po
+++ b/apps/locale/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-10-23 20:30+0800\n"
+"POT-Creation-Date: 2018-10-24 11:03+0800\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: ibuler <ibuler@qq.com>\n"
 "Language-Team: Jumpserver team<ibuler@qq.com>\n"
@@ -852,7 +852,7 @@ msgstr "其它"
 #: assets/templates/assets/gateway_create_update.html:58
 #: assets/templates/assets/label_create_update.html:18
 #: common/templates/common/basic_setting.html:61
-#: common/templates/common/command_storage_create.html:81
+#: common/templates/common/command_storage_create.html:80
 #: common/templates/common/email_setting.html:62
 #: common/templates/common/ldap_setting.html:62
 #: common/templates/common/replay_storage_create.html:139
@@ -885,7 +885,7 @@ msgstr "重置"
 #: assets/templates/assets/gateway_create_update.html:59
 #: assets/templates/assets/label_create_update.html:19
 #: common/templates/common/basic_setting.html:62
-#: common/templates/common/command_storage_create.html:82
+#: common/templates/common/command_storage_create.html:81
 #: common/templates/common/email_setting.html:63
 #: common/templates/common/ldap_setting.html:63
 #: common/templates/common/replay_storage_create.html:140
@@ -1754,7 +1754,7 @@ msgstr "改密日志"
 
 #: audits/views.py:183 templates/_nav.html:10 users/views/group.py:28
 #: users/views/group.py:44 users/views/group.py:60 users/views/group.py:76
-#: users/views/group.py:92 users/views/login.py:327 users/views/user.py:68
+#: users/views/group.py:92 users/views/login.py:328 users/views/user.py:68
 #: users/views/user.py:83 users/views/user.py:111 users/views/user.py:193
 #: users/views/user.py:354 users/views/user.py:404 users/views/user.py:439
 msgid "Users"
@@ -1788,7 +1788,8 @@ msgstr ""
 msgid "Create succeed"
 msgstr "创建成功"
 
-#: common/api.py:127 common/api.py:157
+#: common/api.py:127 common/api.py:161
+#: common/templates/common/terminal_setting.html:151
 msgid "Delete succeed"
 msgstr "删除成功"
 
@@ -1927,79 +1928,79 @@ msgstr "单位: 秒"
 msgid "List sort by"
 msgstr "资产列表排序"
 
-#: common/forms.py:172
+#: common/forms.py:160
 msgid "MFA Secondary certification"
 msgstr "MFA 二次认证"
 
-#: common/forms.py:174
+#: common/forms.py:162
 msgid ""
 "After opening, the user login must use MFA secondary authentication (valid "
 "for all users, including administrators)"
 msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)"
 
-#: common/forms.py:181
+#: common/forms.py:169
 msgid "Limit the number of login failures"
 msgstr "限制登录失败次数"
 
-#: common/forms.py:186
+#: common/forms.py:174
 msgid "No logon interval"
 msgstr "禁止登录时间间隔"
 
-#: common/forms.py:188
+#: common/forms.py:176
 msgid ""
 "Tip :(unit/minute) if the user has failed to log in for a limited number of "
 "times, no login is allowed during this time interval."
 msgstr ""
 "提示: (单位: 分钟) 当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录."
 
-#: common/forms.py:194
+#: common/forms.py:182
 msgid "Connection max idle time"
 msgstr "SSH最大空闲时间"
 
-#: common/forms.py:196
+#: common/forms.py:184
 msgid ""
 "If idle time more than it, disconnect connection(only ssh now) Unit: minute"
 msgstr "提示: (单位: 分钟) 如果超过该配置没有操作,连接会被断开(仅ssh) "
 
-#: common/forms.py:202
+#: common/forms.py:190
 msgid "Password minimum length"
 msgstr "密码最小长度 "
 
-#: common/forms.py:208
+#: common/forms.py:196
 msgid "Must contain capital letters"
 msgstr "必须包含大写字母"
 
-#: common/forms.py:210
+#: common/forms.py:198
 msgid ""
 "After opening, the user password changes and resets must contain uppercase "
 "letters"
 msgstr "开启后,用户密码修改、重置必须包含大写字母"
 
-#: common/forms.py:216
+#: common/forms.py:204
 msgid "Must contain lowercase letters"
 msgstr "必须包含小写字母"
 
-#: common/forms.py:217
+#: common/forms.py:205
 msgid ""
 "After opening, the user password changes and resets must contain lowercase "
 "letters"
 msgstr "开启后,用户密码修改、重置必须包含小写字母"
 
-#: common/forms.py:223
+#: common/forms.py:211
 msgid "Must contain numeric characters"
 msgstr "必须包含数字字符"
 
-#: common/forms.py:224
+#: common/forms.py:212
 msgid ""
 "After opening, the user password changes and resets must contain numeric "
 "characters"
 msgstr "开启后,用户密码修改、重置必须包含数字字符"
 
-#: common/forms.py:230
+#: common/forms.py:218
 msgid "Must contain special characters"
 msgstr "必须包含特殊字符"
 
-#: common/forms.py:231
+#: common/forms.py:219
 msgid ""
 "After opening, the user password changes and resets must contain special "
 "characters"
@@ -2069,11 +2070,11 @@ msgstr "主机"
 msgid "Tips: If there are multiple hosts, separate them with a comma (,)"
 msgstr "提示: 如果有多台主机,请使用逗号 ( , ) 进行分割"
 
-#: common/templates/common/command_storage_create.html:65
+#: common/templates/common/command_storage_create.html:64
 msgid "Index"
 msgstr "索引"
 
-#: common/templates/common/command_storage_create.html:72
+#: common/templates/common/command_storage_create.html:71
 msgid "Doc type"
 msgstr "文档类型"
 
@@ -2129,7 +2130,7 @@ msgstr "用户登录设置"
 msgid "Password check rule"
 msgstr "密码校验规则"
 
-#: common/templates/common/terminal_setting.html:76 terminal/forms.py:33
+#: common/templates/common/terminal_setting.html:76 terminal/forms.py:27
 #: terminal/models.py:22
 msgid "Command storage"
 msgstr "命令存储"
@@ -2146,17 +2147,11 @@ msgstr "命令存储"
 msgid "Add"
 msgstr "添加"
 
-#: common/templates/common/terminal_setting.html:98 terminal/forms.py:38
+#: common/templates/common/terminal_setting.html:98 terminal/forms.py:32
 #: terminal/models.py:23
 msgid "Replay storage"
 msgstr "录像存储"
 
-#: common/templates/common/terminal_setting.html:151
-#, fuzzy
-#| msgid "Delete succeed"
-msgid "Delete success"
-msgstr "删除成功"
-
 #: common/templates/common/terminal_setting.html:154
 msgid "Delete failed"
 msgstr "删除失败"
@@ -2882,12 +2877,12 @@ msgstr "输入"
 msgid "Session"
 msgstr "会话"
 
-#: terminal/forms.py:34
+#: terminal/forms.py:28
 msgid "Command can store in server db or ES, default to server, more see docs"
 msgstr ""
 "命令支持存储到服务器端数据库、ES中,默认存储的服务器端数据库,更多查看文档"
 
-#: terminal/forms.py:39
+#: terminal/forms.py:33
 msgid ""
 "Replay file can store in server disk, AWS S3, Aliyun OSS, default to server, "
 "more see docs"
@@ -4020,56 +4015,56 @@ msgstr "更新用户组"
 msgid "User group granted asset"
 msgstr "用户组授权资产"
 
-#: users/views/login.py:69
+#: users/views/login.py:70
 msgid "Please enable cookies and try again."
 msgstr "设置你的浏览器支持cookie"
 
-#: users/views/login.py:175 users/views/user.py:526 users/views/user.py:551
+#: users/views/login.py:176 users/views/user.py:526 users/views/user.py:551
 msgid "MFA code invalid, or ntp sync server time"
 msgstr "MFA验证码不正确,或者服务器端时间不对"
 
-#: users/views/login.py:204
+#: users/views/login.py:205
 msgid "Logout success"
 msgstr "退出登录成功"
 
-#: users/views/login.py:205
+#: users/views/login.py:206
 msgid "Logout success, return login page"
 msgstr "退出登录成功,返回到登录页面"
 
-#: users/views/login.py:221
+#: users/views/login.py:222
 msgid "Email address invalid, please input again"
 msgstr "邮箱地址错误,重新输入"
 
-#: users/views/login.py:234
+#: users/views/login.py:235
 msgid "Send reset password message"
 msgstr "发送重置密码邮件"
 
-#: users/views/login.py:235
+#: users/views/login.py:236
 msgid "Send reset password mail success, login your mail box and follow it "
 msgstr ""
 "发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)"
 
-#: users/views/login.py:248
+#: users/views/login.py:249
 msgid "Reset password success"
 msgstr "重置密码成功"
 
-#: users/views/login.py:249
+#: users/views/login.py:250
 msgid "Reset password success, return to login page"
 msgstr "重置密码成功,返回到登录页面"
 
-#: users/views/login.py:270 users/views/login.py:283
+#: users/views/login.py:271 users/views/login.py:284
 msgid "Token invalid or expired"
 msgstr "Token错误或失效"
 
-#: users/views/login.py:279
+#: users/views/login.py:280
 msgid "Password not same"
 msgstr "密码不一致"
 
-#: users/views/login.py:289 users/views/user.py:127 users/views/user.py:422
+#: users/views/login.py:290 users/views/user.py:127 users/views/user.py:422
 msgid "* Your password does not meet the requirements"
 msgstr "* 您的密码不符合要求"
 
-#: users/views/login.py:327
+#: users/views/login.py:328
 msgid "First login"
 msgstr "首次登陆"
 
@@ -4428,6 +4423,11 @@ msgstr "创建组织"
 msgid "Update org"
 msgstr "更新组织"
 
+#, fuzzy
+#~| msgid "Delete succeed"
+#~ msgid "Delete success"
+#~ msgstr "删除成功"
+
 #~ msgid ""
 #~ "Set terminal storage setting, `default` is the using as default,You can "
 #~ "set other storage and some terminal using"

From 41a88310349d297cb12abfce1940ad8ec771c77e Mon Sep 17 00:00:00 2001
From: BaiJiangJie <bugatti_it@163.com>
Date: Wed, 24 Oct 2018 12:13:47 +0800
Subject: [PATCH 08/17] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8D=E6=8E=88?=
 =?UTF-8?q?=E6=9D=83=E8=A7=84=E5=88=99=E4=B8=8BDefault=E8=8A=82=E7=82=B9?=
 =?UTF-8?q?=E4=B8=8B=E7=9A=84=E8=B5=84=E4=BA=A7=E4=B8=8D=E6=98=BE=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 apps/assets/models/node.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py
index 8876fc2f1..c157d9d58 100644
--- a/apps/assets/models/node.py
+++ b/apps/assets/models/node.py
@@ -121,7 +121,7 @@ class Node(OrgModelMixin):
     def get_assets(self):
         from .asset import Asset
         if self.is_default_node():
-            assets = Asset.objects.filter(nodes__isnull=True)
+            assets = Asset.objects.filter(Q(nodes__id=self.id) | Q(nodes__isnull=True))
         else:
             assets = Asset.objects.filter(nodes__id=self.id)
         return assets

From 5c002e91ee2b1b04488b6a97716e8a593b7ebdd5 Mon Sep 17 00:00:00 2001
From: BaiJiangJie <bugatti_it@163.com>
Date: Wed, 24 Oct 2018 13:05:32 +0800
Subject: [PATCH 09/17] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E8=8E=B7?=
 =?UTF-8?q?=E5=8F=96=E8=B5=84=E4=BA=A7distinct?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 apps/assets/api/asset.py   | 6 +++---
 apps/assets/models/node.py | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py
index 54f0bacc2..92a1775d0 100644
--- a/apps/assets/api/asset.py
+++ b/apps/assets/api/asset.py
@@ -53,14 +53,14 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
             if show_current_asset:
                 self.queryset = self.queryset.filter(
                     Q(nodes=node_id) | Q(nodes__isnull=True)
-                ).distinct()
+                )
             return
         if show_current_asset:
-            self.queryset = self.queryset.filter(nodes=node).distinct()
+            self.queryset = self.queryset.filter(nodes=node)
         else:
             self.queryset = self.queryset.filter(
                 nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
-            ).distinct()
+            )
 
     def filter_admin_user_id(self):
         admin_user_id = self.request.query_params.get('admin_user_id')
diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py
index c157d9d58..b1dc64029 100644
--- a/apps/assets/models/node.py
+++ b/apps/assets/models/node.py
@@ -124,7 +124,7 @@ class Node(OrgModelMixin):
             assets = Asset.objects.filter(Q(nodes__id=self.id) | Q(nodes__isnull=True))
         else:
             assets = Asset.objects.filter(nodes__id=self.id)
-        return assets
+        return assets.distinct()
 
     def get_valid_assets(self):
         return self.get_assets().valid()

From cc387bf5116983d7efc9998714caa9aea223e274 Mon Sep 17 00:00:00 2001
From: BaiJiangJie <bugatti_it@163.com>
Date: Wed, 24 Oct 2018 14:55:04 +0800
Subject: [PATCH 10/17] =?UTF-8?q?[Update]=20=E7=94=A8=E6=88=B7=E5=88=97?=
 =?UTF-8?q?=E8=A1=A8=E9=A1=B5=EF=BC=8C=E6=B7=BB=E5=8A=A0=E5=88=86=E9=A1=B5?=
 =?UTF-8?q?=EF=BC=8C=E6=90=9C=E7=B4=A2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 apps/users/api/user.py                    | 5 ++++-
 apps/users/templates/users/user_list.html | 2 +-
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/apps/users/api/user.py b/apps/users/api/user.py
index ba0113a36..6bc0d4622 100644
--- a/apps/users/api/user.py
+++ b/apps/users/api/user.py
@@ -9,6 +9,7 @@ from rest_framework import generics
 from rest_framework.response import Response
 from rest_framework.permissions import IsAuthenticated
 from rest_framework_bulk import BulkModelViewSet
+from rest_framework.pagination import LimitOffsetPagination
 
 from ..serializers import UserSerializer, UserPKUpdateSerializer, \
     UserUpdateGroupSerializer, ChangeUserPasswordSerializer
@@ -28,10 +29,12 @@ __all__ = [
 
 
 class UserViewSet(IDInFilterMixin, BulkModelViewSet):
+    filter_fields = ('username', 'email', 'name', 'id')
+    search_fields = filter_fields
     queryset = User.objects.exclude(role="App")
     serializer_class = UserSerializer
     permission_classes = (IsOrgAdmin,)
-    filter_fields = ('username', 'email', 'name', 'id')
+    pagination_class = LimitOffsetPagination
 
     def get_queryset(self):
         queryset = super().get_queryset()
diff --git a/apps/users/templates/users/user_list.html b/apps/users/templates/users/user_list.html
index 4caf6e7a4..5b4565b8e 100644
--- a/apps/users/templates/users/user_list.html
+++ b/apps/users/templates/users/user_list.html
@@ -95,7 +95,7 @@ function initTable() {
         ],
         op_html: $('#actions').html()
     };
-    table = jumpserver.initDataTable(options);
+    var table = jumpserver.initServerSideDataTable(options);
     return table
 }
 

From e2072a1e02fbc6db8385ada191b9e9c4d016ae76 Mon Sep 17 00:00:00 2001
From: BaiJiangJie <bugatti_it@163.com>
Date: Fri, 26 Oct 2018 21:03:09 +0800
Subject: [PATCH 11/17] =?UTF-8?q?[Update]=20=E4=BF=AE=E5=A4=8D=E7=BB=84?=
 =?UTF-8?q?=E7=BB=87=E7=AE=A1=E7=90=86=E5=91=98=E5=88=B7=E6=96=B0=E7=A1=AC?=
 =?UTF-8?q?=E4=BB=B6=E4=BF=A1=E6=81=AF=EF=BC=8C=E8=8E=B7=E5=8F=96=E8=B5=84?=
 =?UTF-8?q?=E4=BA=A7=E4=B8=BANone(=E4=BD=BF=E7=94=A8fullname)=E5=92=8C?=
 =?UTF-8?q?=E6=9F=A5=E7=9C=8Bcelery=20log=20403=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 apps/ops/inventory.py | 2 +-
 apps/ops/views.py     | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/apps/ops/inventory.py b/apps/ops/inventory.py
index 117d961a9..5b5e98551 100644
--- a/apps/ops/inventory.py
+++ b/apps/ops/inventory.py
@@ -50,7 +50,7 @@ class JMSInventory(BaseInventory):
     def convert_to_ansible(self, asset, run_as_admin=False):
         info = {
             'id': asset.id,
-            'hostname': asset.hostname,
+            'hostname': asset.fullname,
             'ip': asset.ip,
             'port': asset.port,
             'vars': dict(),
diff --git a/apps/ops/views.py b/apps/ops/views.py
index 380fdadf4..1bbfbfc23 100644
--- a/apps/ops/views.py
+++ b/apps/ops/views.py
@@ -6,7 +6,7 @@ from django.views.generic import ListView, DetailView, TemplateView
 
 from common.mixins import DatetimeSearchMixin
 from .models import Task, AdHoc, AdHocRunHistory, CeleryTask
-from common.permissions import SuperUserRequiredMixin
+from common.permissions import SuperUserRequiredMixin, AdminUserRequiredMixin
 
 
 class TaskListView(SuperUserRequiredMixin, DatetimeSearchMixin, ListView):
@@ -121,6 +121,6 @@ class AdHocHistoryDetailView(SuperUserRequiredMixin, DetailView):
         return super().get_context_data(**kwargs)
 
 
-class CeleryTaskLogView(SuperUserRequiredMixin, DetailView):
+class CeleryTaskLogView(AdminUserRequiredMixin, DetailView):
     template_name = 'ops/celery_task_log.html'
     model = CeleryTask

From 4f806f11f2741db76d8ee6f21b8897f1b5c1685d Mon Sep 17 00:00:00 2001
From: BaiJiangJie <bugatti_it@163.com>
Date: Sun, 28 Oct 2018 02:17:46 +0800
Subject: [PATCH 12/17] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E5=91=BD?=
 =?UTF-8?q?=E4=BB=A4/=E5=BD=95=E5=83=8F=E5=AD=98=E5=82=A8=EF=BC=8C?=
 =?UTF-8?q?=E5=88=9B=E5=BB=BA/=E5=88=A0=E9=99=A4API=E7=9A=84=E6=9D=83?=
 =?UTF-8?q?=E9=99=90=E4=B8=BASuperUser?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 apps/common/api.py | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/apps/common/api.py b/apps/common/api.py
index 064c1a6b9..51416fa3b 100644
--- a/apps/common/api.py
+++ b/apps/common/api.py
@@ -11,7 +11,7 @@ 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 IsOrgAdmin
+from .permissions import IsOrgAdmin, IsSuperUser
 from .serializers import MailTestSerializer, LDAPTestSerializer
 from .models import Setting
 
@@ -90,7 +90,7 @@ class LDAPTestingAPI(APIView):
 
 
 class ReplayStorageCreateAPI(APIView):
-    permission_classes = (IsOrgAdmin,)
+    permission_classes = (IsSuperUser,)
 
     def post(self, request):
         storage_data = request.data
@@ -120,6 +120,7 @@ class ReplayStorageCreateAPI(APIView):
 
 
 class ReplayStorageDeleteAPI(APIView):
+    permission_classes = (IsSuperUser,)
 
     def post(self, request):
         storage_name = str(request.data.get('name'))
@@ -128,7 +129,7 @@ class ReplayStorageDeleteAPI(APIView):
 
 
 class CommandStorageCreateAPI(APIView):
-    permission_classes = (IsOrgAdmin,)
+    permission_classes = (IsSuperUser,)
 
     def post(self, request):
         storage_data = request.data
@@ -153,7 +154,7 @@ class CommandStorageCreateAPI(APIView):
 
 
 class CommandStorageDeleteAPI(APIView):
-    permission_classes = (IsOrgAdmin,)
+    permission_classes = (IsSuperUser,)
 
     def post(self, request):
         storage_name = str(request.data.get('name'))

From c8568eb244a84eedc529cab55ad904c73bf09024 Mon Sep 17 00:00:00 2001
From: fangjian <fangjian@baiqishi.com>
Date: Wed, 31 Oct 2018 11:26:24 +0800
Subject: [PATCH 13/17] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=AD=90=E7=94=A8?=
 =?UTF-8?q?=E6=88=B7=E8=B5=84=E4=BA=A7=E5=88=97=E8=A1=A8=E7=82=B9=E5=87=BB?=
 =?UTF-8?q?=E6=97=A0=E6=B3=95=E6=94=B6=E8=B5=B7?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 apps/assets/templates/assets/user_asset_list.html | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/apps/assets/templates/assets/user_asset_list.html b/apps/assets/templates/assets/user_asset_list.html
index dc799e734..2f03a361d 100644
--- a/apps/assets/templates/assets/user_asset_list.html
+++ b/apps/assets/templates/assets/user_asset_list.html
@@ -183,6 +183,21 @@ $(document).ready(function () {
     $('#asset_detail_tbody').html(trs)
 });
 
+function toggle() {
+    if ($("#split-left").is(':visible')) {
+        $("#split-left").hide(500, function () {
+            $("#split-right").attr("class", "col-lg-12");
+            $("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
+            show = 1;
+        });
+    } else {
+        $("#split-right").attr("class", "col-lg-9");
+        $("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
+        $("#split-left").show(500);
+        show = 0;
+    }
+}
+
 </script>
 
 {% endblock %}
\ No newline at end of file

From 82d866db7d16992ff843428cd2685c96a44332da Mon Sep 17 00:00:00 2001
From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com>
Date: Wed, 31 Oct 2018 15:31:09 +0800
Subject: [PATCH 14/17] =?UTF-8?q?[Update]=20=E6=B7=BB=E5=8A=A0=E7=94=A8?=
 =?UTF-8?q?=E6=88=B7=E6=8E=88=E6=9D=83=E8=B5=84=E4=BA=A7=E5=88=97=E8=A1=A8?=
 =?UTF-8?q?=E9=A1=B5=E7=9A=84=E5=88=86=E9=A1=B5=EF=BC=8C=E6=90=9C=E7=B4=A2?=
 =?UTF-8?q?=EF=BC=8C=E6=8E=92=E5=BA=8F=20(#1963)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* [Update] 分页获取用户授权资产

* [Update] 修改前端-用户授权资产分页

* [Update] 用户授权资产支持搜索

* [Update] 用户授权资产支持排序

* [Update] 用户授权的节点with资产Api,对资产进行排序

* [Update] 获取用户授权的节点下的资产的api,进行分页、排序、查询

* [Update] 抽象用户授权资产列表的查询,排序

* [Update] 优化小细节

* [Update] 删除无用导入

* [Update] 修改AssetFilterMixins目录从common到perms

* [Update] 资产授权规则列表: 添加分页、搜索

* [Update] 添加管理用户,系统用户列表分页、搜索

* [Update] 用户组列表添加分页,搜索

* [Update] 资产标签列表添加分页、搜索

* [Update] 网域网关列表添加分页、搜索

* [Update] 命令过滤列表添加分页、搜索,修改翻译小细节

* [Update] 删除前端注释initDataTable

* [Update] 修改文案,资产组-节点

* [Update] 普通用户资产列表添加分页、搜索
---
 apps/assets/api/admin_user.py                 |   9 +++++
 apps/assets/api/cmd_filter.py                 |   7 ++++
 apps/assets/api/domain.py                     |   9 ++++-
 apps/assets/api/label.py                      |   4 ++
 apps/assets/api/system_user.py                |   7 ++++
 .../templates/assets/admin_user_list.html     |   2 +-
 .../templates/assets/cmd_filter_list.html     |   2 +-
 .../assets/cmd_filter_rule_list.html          |   2 +-
 .../templates/assets/domain_gateway_list.html |   2 +-
 apps/assets/templates/assets/domain_list.html |   2 +-
 apps/assets/templates/assets/label_list.html  |   2 +-
 .../templates/assets/system_user_list.html    |   2 +-
 .../templates/assets/user_asset_list.html     |   2 +-
 apps/common/mixins.py                         |   3 --
 apps/locale/zh/LC_MESSAGES/django.mo          | Bin 55532 -> 55527 bytes
 apps/locale/zh/LC_MESSAGES/django.po          |   6 +--
 apps/perms/api.py                             |  31 ++++++++++++---
 apps/perms/mixins.py                          |  36 ++++++++++++++++++
 .../perms/asset_permission_asset.html         |   2 +-
 .../perms/asset_permission_detail.html        |   2 +-
 .../perms/asset_permission_list.html          |   2 +-
 .../perms/asset_permission_user.html          |   2 +-
 apps/perms/utils.py                           |  19 +++++++++
 apps/users/api/group.py                       |   4 ++
 .../templates/users/user_granted_asset.html   |   2 +-
 .../templates/users/user_group_list.html      |   3 +-
 26 files changed, 137 insertions(+), 27 deletions(-)
 create mode 100644 apps/perms/mixins.py

diff --git a/apps/assets/api/admin_user.py b/apps/assets/api/admin_user.py
index 7048ce461..8d30ee9d9 100644
--- a/apps/assets/api/admin_user.py
+++ b/apps/assets/api/admin_user.py
@@ -17,6 +17,7 @@ from django.db import transaction
 from rest_framework import generics
 from rest_framework.response import Response
 from rest_framework_bulk import BulkModelViewSet
+from rest_framework.pagination import LimitOffsetPagination
 
 from common.mixins import IDInFilterMixin
 from common.utils import get_logger
@@ -37,9 +38,17 @@ class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet):
     """
     Admin user api set, for add,delete,update,list,retrieve resource
     """
+
+    filter_fields = ("name", "username")
+    search_fields = filter_fields
     queryset = AdminUser.objects.all()
     serializer_class = serializers.AdminUserSerializer
     permission_classes = (IsOrgAdmin,)
+    pagination_class = LimitOffsetPagination
+
+    def get_queryset(self):
+        queryset = super().get_queryset().all()
+        return queryset
 
 
 class AdminUserAuthApi(generics.UpdateAPIView):
diff --git a/apps/assets/api/cmd_filter.py b/apps/assets/api/cmd_filter.py
index 14afc0ae3..cecdf2432 100644
--- a/apps/assets/api/cmd_filter.py
+++ b/apps/assets/api/cmd_filter.py
@@ -2,6 +2,7 @@
 #
 
 from rest_framework_bulk import BulkModelViewSet
+from rest_framework.pagination import LimitOffsetPagination
 from django.shortcuts import get_object_or_404
 
 from ..hands import IsOrgAdmin
@@ -13,14 +14,20 @@ __all__ = ['CommandFilterViewSet', 'CommandFilterRuleViewSet']
 
 
 class CommandFilterViewSet(BulkModelViewSet):
+    filter_fields = ("name",)
+    search_fields = filter_fields
     permission_classes = (IsOrgAdmin,)
     queryset = CommandFilter.objects.all()
     serializer_class = serializers.CommandFilterSerializer
+    pagination_class = LimitOffsetPagination
 
 
 class CommandFilterRuleViewSet(BulkModelViewSet):
+    filter_fields = ("content",)
+    search_fields = filter_fields
     permission_classes = (IsOrgAdmin,)
     serializer_class = serializers.CommandFilterRuleSerializer
+    pagination_class = LimitOffsetPagination
 
     def get_queryset(self):
         fpk = self.kwargs.get('filter_pk')
diff --git a/apps/assets/api/domain.py b/apps/assets/api/domain.py
index 37bebfb84..2e24829dc 100644
--- a/apps/assets/api/domain.py
+++ b/apps/assets/api/domain.py
@@ -2,6 +2,7 @@
 
 from rest_framework_bulk import BulkModelViewSet
 from rest_framework.views import APIView, Response
+from rest_framework.pagination import LimitOffsetPagination
 
 from django.views.generic.detail import SingleObjectMixin
 
@@ -20,6 +21,11 @@ class DomainViewSet(BulkModelViewSet):
     queryset = Domain.objects.all()
     permission_classes = (IsOrgAdmin,)
     serializer_class = serializers.DomainSerializer
+    pagination_class = LimitOffsetPagination
+
+    def get_queryset(self):
+        queryset = super().get_queryset().all()
+        return queryset
 
     def get_serializer_class(self):
         if self.request.query_params.get('gateway'):
@@ -33,11 +39,12 @@ class DomainViewSet(BulkModelViewSet):
 
 
 class GatewayViewSet(BulkModelViewSet):
-    filter_fields = ("domain",)
+    filter_fields = ("domain__name", "name", "username", "ip")
     search_fields = filter_fields
     queryset = Gateway.objects.all()
     permission_classes = (IsOrgAdmin,)
     serializer_class = serializers.GatewaySerializer
+    pagination_class = LimitOffsetPagination
 
 
 class GatewayTestConnectionApi(SingleObjectMixin, APIView):
diff --git a/apps/assets/api/label.py b/apps/assets/api/label.py
index e5391c76a..eb8594e4a 100644
--- a/apps/assets/api/label.py
+++ b/apps/assets/api/label.py
@@ -14,6 +14,7 @@
 # limitations under the License.
 
 from rest_framework_bulk import BulkModelViewSet
+from rest_framework.pagination import LimitOffsetPagination
 from django.db.models import Count
 
 from common.utils import get_logger
@@ -27,8 +28,11 @@ __all__ = ['LabelViewSet']
 
 
 class LabelViewSet(BulkModelViewSet):
+    filter_fields = ("name", "value")
+    search_fields = filter_fields
     permission_classes = (IsOrgAdmin,)
     serializer_class = serializers.LabelSerializer
+    pagination_class = LimitOffsetPagination
 
     def list(self, request, *args, **kwargs):
         if request.query_params.get("distinct"):
diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py
index 16d475c35..3c1d0b3bd 100644
--- a/apps/assets/api/system_user.py
+++ b/apps/assets/api/system_user.py
@@ -42,9 +42,16 @@ class SystemUserViewSet(BulkModelViewSet):
     """
     System user api set, for add,delete,update,list,retrieve resource
     """
+    filter_fields = ("name", "username")
+    search_fields = filter_fields
     queryset = SystemUser.objects.all()
     serializer_class = serializers.SystemUserSerializer
     permission_classes = (IsOrgAdminOrAppUser,)
+    pagination_class = LimitOffsetPagination
+
+    def get_queryset(self):
+        queryset = super().get_queryset().all()
+        return queryset
 
 
 class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
diff --git a/apps/assets/templates/assets/admin_user_list.html b/apps/assets/templates/assets/admin_user_list.html
index 16fcd1950..25ee56fba 100644
--- a/apps/assets/templates/assets/admin_user_list.html
+++ b/apps/assets/templates/assets/admin_user_list.html
@@ -93,7 +93,7 @@ $(document).ready(function(){
         columns: [{data: function(){return ""}}, {data: "name" }, {data: "username" }, {data: "assets_amount" },
                   {data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" }]
     };
-    jumpserver.initDataTable(options);
+    jumpserver.initServerSideDataTable(options)
 })
 
 .on('click', '.btn_admin_user_delete', function () {
diff --git a/apps/assets/templates/assets/cmd_filter_list.html b/apps/assets/templates/assets/cmd_filter_list.html
index 78060177b..3a4feeae0 100644
--- a/apps/assets/templates/assets/cmd_filter_list.html
+++ b/apps/assets/templates/assets/cmd_filter_list.html
@@ -66,7 +66,7 @@ function initTable() {
         ],
         op_html: $('#actions').html()
     };
-    jumpserver.initDataTable(options);
+    jumpserver.initServerSideDataTable(options);
 }
 $(document).ready(function(){
     initTable();
diff --git a/apps/assets/templates/assets/cmd_filter_rule_list.html b/apps/assets/templates/assets/cmd_filter_rule_list.html
index 119b44fd4..78c3a36d5 100644
--- a/apps/assets/templates/assets/cmd_filter_rule_list.html
+++ b/apps/assets/templates/assets/cmd_filter_rule_list.html
@@ -95,7 +95,7 @@ function initTable() {
         ],
         op_html: $('#actions').html()
     };
-    jumpserver.initDataTable(options);
+    jumpserver.initServerSideDataTable(options);
 }
 $(document).ready(function(){
     initTable();
diff --git a/apps/assets/templates/assets/domain_gateway_list.html b/apps/assets/templates/assets/domain_gateway_list.html
index 43e8f43df..e7a3467e3 100644
--- a/apps/assets/templates/assets/domain_gateway_list.html
+++ b/apps/assets/templates/assets/domain_gateway_list.html
@@ -98,7 +98,7 @@ function initTable() {
         ],
         op_html: $('#actions').html()
     };
-    jumpserver.initDataTable(options);
+    jumpserver.initServerSideDataTable(options);
 }
 $(document).ready(function(){
     initTable();
diff --git a/apps/assets/templates/assets/domain_list.html b/apps/assets/templates/assets/domain_list.html
index 6913671f4..a0c6e869e 100644
--- a/apps/assets/templates/assets/domain_list.html
+++ b/apps/assets/templates/assets/domain_list.html
@@ -62,7 +62,7 @@ function initTable() {
         ],
         op_html: $('#actions').html()
     };
-    jumpserver.initDataTable(options);
+    jumpserver.initServerSideDataTable(options);
 }
 $(document).ready(function(){
     initTable();
diff --git a/apps/assets/templates/assets/label_list.html b/apps/assets/templates/assets/label_list.html
index 1c1b380d5..d2fa9958a 100644
--- a/apps/assets/templates/assets/label_list.html
+++ b/apps/assets/templates/assets/label_list.html
@@ -47,7 +47,7 @@ function initTable() {
         ],
         op_html: $('#actions').html()
     };
-    jumpserver.initDataTable(options);
+    jumpserver.initServerSideDataTable(options);
 }
 $(document).ready(function(){
     initTable();
diff --git a/apps/assets/templates/assets/system_user_list.html b/apps/assets/templates/assets/system_user_list.html
index 2d1358e46..f7c2a613b 100644
--- a/apps/assets/templates/assets/system_user_list.html
+++ b/apps/assets/templates/assets/system_user_list.html
@@ -100,7 +100,7 @@ function initTable() {
         ],
         op_html: $('#actions').html()
         };
-    jumpserver.initDataTable(options);
+    jumpserver.initServerSideDataTable(options);
 }
 
 $(document).ready(function(){
diff --git a/apps/assets/templates/assets/user_asset_list.html b/apps/assets/templates/assets/user_asset_list.html
index 2f03a361d..1b1a76e1d 100644
--- a/apps/assets/templates/assets/user_asset_list.html
+++ b/apps/assets/templates/assets/user_asset_list.html
@@ -102,7 +102,7 @@ function initTable() {
             {data: "system_users_granted", orderable: false}
         ]
     };
-    asset_table = jumpserver.initDataTable(options);
+    asset_table = jumpserver.initServerSideDataTable(options);
     return asset_table
 }
 
diff --git a/apps/common/mixins.py b/apps/common/mixins.py
index ceeaed6c1..0a7d15fef 100644
--- a/apps/common/mixins.py
+++ b/apps/common/mixins.py
@@ -117,6 +117,3 @@ class DatetimeSearchMixin:
     def get(self, request, *args, **kwargs):
         self.get_date_range()
         return super().get(request, *args, **kwargs)
-
-
-
diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo
index b91198c4a60f99df3b260f584b3ef4661f0b65e4..1315c19c3dc03e3491612be772c9f56ef3dd5609 100644
GIT binary patch
delta 12092
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

delta 12100
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

diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po
index 402d71d67..9e3459248 100644
--- a/apps/locale/zh/LC_MESSAGES/django.po
+++ b/apps/locale/zh/LC_MESSAGES/django.po
@@ -1363,7 +1363,7 @@ msgstr "创建命令过滤器"
 #: assets/templates/assets/cmd_filter_rule_list.html:33
 #: assets/views/cmd_filter.py:98
 msgid "Command filter rule list"
-msgstr "命令过滤器列表"
+msgstr "命令过滤器规则列表"
 
 #: assets/templates/assets/cmd_filter_rule_list.html:50
 msgid "Create rule"
@@ -2477,8 +2477,8 @@ msgstr "用户或用户组"
 #: perms/templates/perms/asset_permission_asset.html:27
 #: perms/templates/perms/asset_permission_detail.html:27
 #: perms/templates/perms/asset_permission_user.html:27
-msgid "Assets and asset groups"
-msgstr "资产或资产组"
+msgid "Assets and node"
+msgstr "资产或节点"
 
 #: perms/templates/perms/asset_permission_asset.html:80
 msgid "Add asset to this permission"
diff --git a/apps/perms/api.py b/apps/perms/api.py
index 910ffb7f9..a1472d46a 100644
--- a/apps/perms/api.py
+++ b/apps/perms/api.py
@@ -5,6 +5,7 @@ from django.shortcuts import get_object_or_404
 from rest_framework.views import APIView, Response
 from rest_framework.generics import ListAPIView, get_object_or_404, RetrieveUpdateAPIView
 from rest_framework import viewsets
+from rest_framework.pagination import LimitOffsetPagination
 
 from common.utils import set_or_append_attr_bulk
 from common.permissions import IsValidUser, IsOrgAdmin, IsOrgAdminOrAppUser
@@ -15,6 +16,7 @@ from .hands import AssetGrantedSerializer, User, UserGroup, Asset, Node, \
     NodeGrantedSerializer, SystemUser, NodeSerializer
 from orgs.utils import set_to_root_org
 from . import serializers
+from .mixins import AssetsFilterMixin
 
 
 class AssetPermissionViewSet(viewsets.ModelViewSet):
@@ -23,6 +25,7 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
     """
     queryset = AssetPermission.objects.all()
     serializer_class = serializers.AssetPermissionCreateUpdateSerializer
+    pagination_class = LimitOffsetPagination
     permission_classes = (IsOrgAdmin,)
 
     def get_serializer_class(self):
@@ -31,10 +34,15 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
         return self.serializer_class
 
     def get_queryset(self):
-        queryset = super().get_queryset()
+        queryset = super().get_queryset().all()
+        search = self.request.query_params.get('search')
         asset_id = self.request.query_params.get('asset')
         node_id = self.request.query_params.get('node')
         inherit_nodes = set()
+
+        if search:
+            queryset = queryset.filter(name__icontains=search)
+
         if not asset_id and not node_id:
             return queryset
 
@@ -53,15 +61,17 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
             _permissions = queryset.filter(nodes=n)
             set_or_append_attr_bulk(_permissions, "inherit", n.value)
             permissions.update(_permissions)
-        return permissions
+
+        return list(permissions)
 
 
-class UserGrantedAssetsApi(ListAPIView):
+class UserGrantedAssetsApi(AssetsFilterMixin, ListAPIView):
     """
     用户授权的所有资产
     """
     permission_classes = (IsOrgAdminOrAppUser,)
     serializer_class = AssetGrantedSerializer
+    pagination_class = LimitOffsetPagination
     
     def change_org_if_need(self):
         if self.request.user.is_superuser or \
@@ -84,6 +94,7 @@ class UserGrantedAssetsApi(ListAPIView):
             system_users_granted = [s for s in v if s.protocol == k.protocol]
             k.system_users_granted = system_users_granted
             queryset.append(k)
+
         return queryset
 
     def get_permissions(self):
@@ -122,7 +133,7 @@ class UserGrantedNodesApi(ListAPIView):
         return super().get_permissions()
 
 
-class UserGrantedNodesWithAssetsApi(ListAPIView):
+class UserGrantedNodesWithAssetsApi(AssetsFilterMixin, ListAPIView):
     """
     用户授权的节点并带着节点下资产的api
     """
@@ -155,19 +166,25 @@ class UserGrantedNodesWithAssetsApi(ListAPIView):
             queryset.append(node)
         return queryset
 
+    def sort_assets(self, queryset):
+        for node in queryset:
+            node.assets_granted = super().sort_assets(node.assets_granted)
+        return queryset
+
     def get_permissions(self):
         if self.kwargs.get('pk') is None:
             self.permission_classes = (IsValidUser,)
         return super().get_permissions()
 
 
-class UserGrantedNodeAssetsApi(ListAPIView):
+class UserGrantedNodeAssetsApi(AssetsFilterMixin, ListAPIView):
     """
     查询用户授权的节点下的资产的api, 与上面api不同的是,只返回某个节点下的资产
     """
     permission_classes = (IsOrgAdminOrAppUser,)
     serializer_class = AssetGrantedSerializer
-    
+    pagination_class = LimitOffsetPagination
+
     def change_org_if_need(self):
         if self.request.user.is_superuser or \
                 self.request.user.is_app or \
@@ -189,6 +206,8 @@ class UserGrantedNodeAssetsApi(ListAPIView):
         assets = nodes.get(node, [])
         for asset, system_users in assets.items():
             asset.system_users_granted = system_users
+
+        assets = list(assets.keys())
         return assets
 
     def get_permissions(self):
diff --git a/apps/perms/mixins.py b/apps/perms/mixins.py
new file mode 100644
index 000000000..e41c0529b
--- /dev/null
+++ b/apps/perms/mixins.py
@@ -0,0 +1,36 @@
+# ~*~ coding: utf-8 ~*~
+#
+
+
+class AssetsFilterMixin(object):
+    """
+    对资产进行过滤(查询,排序)
+    """
+
+    def filter_queryset(self, queryset):
+        queryset = self.search_assets(queryset)
+        queryset = self.sort_assets(queryset)
+        return queryset
+
+    def search_assets(self, queryset):
+        from perms.utils import is_obj_attr_has
+        value = self.request.query_params.get('search')
+        if not value:
+            return queryset
+        queryset = [asset for asset in queryset if is_obj_attr_has(asset, value)]
+        return queryset
+
+    def sort_assets(self, queryset):
+        from perms.utils import sort_assets
+        order_by = self.request.query_params.get('order')
+        if not order_by:
+            order_by = 'hostname'
+
+        if order_by.startswith('-'):
+            order_by = order_by.lstrip('-')
+            reverse = True
+        else:
+            reverse = False
+
+        queryset = sort_assets(queryset, order_by=order_by, reverse=reverse)
+        return queryset
diff --git a/apps/perms/templates/perms/asset_permission_asset.html b/apps/perms/templates/perms/asset_permission_asset.html
index 364d7e60f..e774692f3 100644
--- a/apps/perms/templates/perms/asset_permission_asset.html
+++ b/apps/perms/templates/perms/asset_permission_asset.html
@@ -24,7 +24,7 @@
                             </li>
                             <li class="active">
                                 <a href="{% url 'perms:asset-permission-asset-list' pk=asset_permission.id  %}" class="text-center">
-                                <i class="fa fa-bar-chart-o"></i> {% trans 'Assets and asset groups' %}</a>
+                                <i class="fa fa-bar-chart-o"></i> {% trans 'Assets and node' %}</a>
                             </li>
                         </ul>
                     </div>
diff --git a/apps/perms/templates/perms/asset_permission_detail.html b/apps/perms/templates/perms/asset_permission_detail.html
index 3996cb274..9c38cfc29 100644
--- a/apps/perms/templates/perms/asset_permission_detail.html
+++ b/apps/perms/templates/perms/asset_permission_detail.html
@@ -24,7 +24,7 @@
                             </li>
                             <li>
                                 <a href="{% url 'perms:asset-permission-asset-list' pk=object.id %}" class="text-center">
-                                <i class="fa fa-bar-chart-o"></i> {% trans 'Assets and asset groups' %}</a>
+                                <i class="fa fa-bar-chart-o"></i> {% trans 'Assets and node' %}</a>
                             </li>
                             <li class="pull-right">
                                 <a class="btn btn-outline btn-default" href="{% url 'perms:asset-permission-update' pk=object.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
diff --git a/apps/perms/templates/perms/asset_permission_list.html b/apps/perms/templates/perms/asset_permission_list.html
index 31ccce112..1c1a21470 100644
--- a/apps/perms/templates/perms/asset_permission_list.html
+++ b/apps/perms/templates/perms/asset_permission_list.html
@@ -217,7 +217,7 @@ function initTable() {
         select: {},
         op_html: $('#actions').html()
     };
-    table = jumpserver.initDataTable(options);
+    table = jumpserver.initServerSideDataTable(options);
     return table
 }
 
diff --git a/apps/perms/templates/perms/asset_permission_user.html b/apps/perms/templates/perms/asset_permission_user.html
index 5d490bc28..eef6bf601 100644
--- a/apps/perms/templates/perms/asset_permission_user.html
+++ b/apps/perms/templates/perms/asset_permission_user.html
@@ -24,7 +24,7 @@
                             </li>
                             <li>
                                 <a href="{% url 'perms:asset-permission-asset-list' pk=asset_permission.id  %}" class="text-center">
-                                <i class="fa fa-bar-chart-o"></i> {% trans 'Assets and asset groups' %}</a>
+                                <i class="fa fa-bar-chart-o"></i> {% trans 'Assets and node' %}</a>
                             </li>
                         </ul>
                     </div>
diff --git a/apps/perms/utils.py b/apps/perms/utils.py
index 817c8b144..8ebe696e0 100644
--- a/apps/perms/utils.py
+++ b/apps/perms/utils.py
@@ -156,3 +156,22 @@ class AssetPermissionUtil:
         return tree.nodes
 
 
+def is_obj_attr_has(obj, val, attrs=("hostname", "ip", "comment")):
+    if not attrs:
+        vals = [val for val in obj.__dict__.values() if isinstance(val, (str, int))]
+    else:
+        vals = [getattr(obj, attr) for attr in attrs if
+                hasattr(obj, attr) and isinstance(hasattr(obj, attr), (str, int))]
+
+    for v in vals:
+        if str(v).find(val) != -1:
+            return True
+    return False
+
+
+def sort_assets(assets, order_by='hostname', reverse=False):
+    if order_by == 'ip':
+        assets = sorted(assets, key=lambda asset: [int(d) for d in asset.ip.split('.') if d.isdigit()], reverse=reverse)
+    else:
+        assets = sorted(assets, key=lambda asset: getattr(asset, order_by), reverse=reverse)
+    return assets
diff --git a/apps/users/api/group.py b/apps/users/api/group.py
index 0f42d7426..2b738722c 100644
--- a/apps/users/api/group.py
+++ b/apps/users/api/group.py
@@ -3,6 +3,7 @@
 
 from rest_framework import generics
 from rest_framework_bulk import BulkModelViewSet
+from rest_framework.pagination import LimitOffsetPagination
 
 from ..serializers import UserGroupSerializer, \
     UserGroupUpdateMemeberSerializer
@@ -15,9 +16,12 @@ __all__ = ['UserGroupViewSet', 'UserGroupUpdateUserApi']
 
 
 class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet):
+    filter_fields = ("name",)
+    search_fields = filter_fields
     queryset = UserGroup.objects.all()
     serializer_class = UserGroupSerializer
     permission_classes = (IsOrgAdmin,)
+    pagination_class = LimitOffsetPagination
 
 
 class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView):
diff --git a/apps/users/templates/users/user_granted_asset.html b/apps/users/templates/users/user_granted_asset.html
index fc2cec031..8a68f3f60 100644
--- a/apps/users/templates/users/user_granted_asset.html
+++ b/apps/users/templates/users/user_granted_asset.html
@@ -103,7 +103,7 @@ function initTable() {
             {data: "system_users_granted", orderable: false}
         ]
     };
-    asset_table = jumpserver.initDataTable(options);
+    asset_table = jumpserver.initServerSideDataTable(options)
 }
 
 function onSelected(event, treeNode) {
diff --git a/apps/users/templates/users/user_group_list.html b/apps/users/templates/users/user_group_list.html
index 25acfd439..b2eecc01f 100644
--- a/apps/users/templates/users/user_group_list.html
+++ b/apps/users/templates/users/user_group_list.html
@@ -58,7 +58,8 @@ $(document).ready(function() {
         order: [],
         op_html: $('#actions').html()
     };
-    jumpserver.initDataTable(options);
+    jumpserver.initServerSideDataTable(options);
+
 }).on('click', '.btn_delete_user_group', function(){
     var $this = $(this);
     var group_id = $this.data('gid');

From f87e08efffb9095971692c60e898ac2489d47c55 Mon Sep 17 00:00:00 2001
From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com>
Date: Thu, 1 Nov 2018 16:28:19 +0800
Subject: [PATCH 15/17] =?UTF-8?q?[Update]=20=E6=B7=BB=E5=8A=A0deb=E4=BE=9D?=
 =?UTF-8?q?=E8=B5=96=EF=BC=8C=E5=AE=8C=E5=96=84=E7=94=A8=E6=88=B7=E7=99=BB?=
 =?UTF-8?q?=E5=BD=95=E5=A4=B1=E8=B4=A5=E6=97=A5=E5=BF=97=EF=BC=8C=E4=BF=AE?=
 =?UTF-8?q?=E5=A4=8D=E8=B5=84=E4=BA=A7=E6=A0=87=E7=AD=BEbug=20(#1983)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* [Update] 修改deb依赖

* [Update] 修改记录用户登录失败日志,用户不存在的情况,但是显示登录日志列表还不全..

* [Update] 用户登录日志,记录用户名不存在的情况(Default组织显示所有用户的登录日志)

* [Bugfix] 修复标签名为search, limit时资产列表不显示的bug
---
 .../templates/assets/user_asset_list.html     |   4 +-
 apps/assets/views/label.py                    |  11 ++
 apps/audits/views.py                          |   8 +-
 apps/locale/zh/LC_MESSAGES/django.mo          | Bin 55527 -> 55705 bytes
 apps/locale/zh/LC_MESSAGES/django.po          | 106 ++++++++++--------
 apps/users/api/auth.py                        |   7 +-
 apps/users/models/authentication.py           |   4 +-
 apps/users/views/login.py                     |   5 +-
 requirements/deb_requirements.txt             |   2 +-
 9 files changed, 89 insertions(+), 58 deletions(-)

diff --git a/apps/assets/templates/assets/user_asset_list.html b/apps/assets/templates/assets/user_asset_list.html
index 1b1a76e1d..8a563cadc 100644
--- a/apps/assets/templates/assets/user_asset_list.html
+++ b/apps/assets/templates/assets/user_asset_list.html
@@ -62,7 +62,7 @@
 
 {% block custom_foot_js %}
 <script>
-var zTree, asset_table;
+var zTree, asset_table, show=0;
 var inited = false;
 var url;
 function initTable() {
@@ -184,7 +184,7 @@ $(document).ready(function () {
 });
 
 function toggle() {
-    if ($("#split-left").is(':visible')) {
+    if (show === 0) {
         $("#split-left").hide(500, function () {
             $("#split-right").attr("class", "col-lg-12");
             $("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
diff --git a/apps/assets/views/label.py b/apps/assets/views/label.py
index de30eaaa3..9ed289e3e 100644
--- a/apps/assets/views/label.py
+++ b/apps/assets/views/label.py
@@ -36,6 +36,7 @@ class LabelCreateView(AdminUserRequiredMixin, CreateView):
     form_class = LabelForm
     success_url = reverse_lazy('assets:label-list')
     success_message = create_success_msg
+    disable_name = ['draw', 'search', 'limit', 'offset', '_']
 
     def get_context_data(self, **kwargs):
         context = {
@@ -45,6 +46,16 @@ class LabelCreateView(AdminUserRequiredMixin, CreateView):
         kwargs.update(context)
         return super().get_context_data(**kwargs)
 
+    def form_valid(self, form):
+        name = form.cleaned_data.get('name')
+        if name in self.disable_name:
+            msg = _(
+                'Tips: Avoid using label names reserved internally: {}'
+            ).format(', '.join(self.disable_name))
+            form.add_error("name", msg)
+            return self.form_invalid(form)
+        return super().form_valid(form)
+
 
 class LabelUpdateView(AdminUserRequiredMixin, UpdateView):
     model = Label
diff --git a/apps/audits/views.py b/apps/audits/views.py
index 4b1c11c16..c64bbe5b4 100644
--- a/apps/audits/views.py
+++ b/apps/audits/views.py
@@ -160,8 +160,12 @@ class LoginLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
         return users
 
     def get_queryset(self):
-        users = self.get_org_users()
-        queryset = super().get_queryset().filter(username__in=users)
+        if current_org.is_default():
+            queryset = super().get_queryset()
+        else:
+            users = self.get_org_users()
+            queryset = super().get_queryset().filter(username__in=users)
+
         self.user = self.request.GET.get('user', '')
         self.keyword = self.request.GET.get("keyword", '')
 
diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo
index 1315c19c3dc03e3491612be772c9f56ef3dd5609..9f41c1d67939c0a96cc3a11a618c509fd2a70610 100644
GIT binary patch
delta 14128
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

delta 14002
zcmXxq37pQ=9>?+jV;M7+*(b|1#u#JY#$+4Ipp0lEGL~dd!o;PrjYlLQl0Pj9p+Xtb
z$W|JX>|~8(Dc2-gxDgWF&v(wRdEKY?`Tfp*{^$RUbl=J=`qP@C!PC*j=6U>7DC&84
z<1ghrFQu60eV(eU=XGr3dDF2IPQ>r=aKQ6=-RpT@VrpAr@>erm|DEkTFPiu~#^DVN
z!SMS%uMEavDNM$@JTK_gqmW1<6Pee05*y$UY>o@DE}q2xSh76}#B8jNbIr|onz#Tp
z!S)WG7lQ{;3pkI38^l=Rum?P^1oL|pDTI@#it5-9OJi&EVbnl9Q3E|=K8M;-4o2V%
zEQ@cW`sX2s>TSU={0g;z{iqYVgrUsu-JuYVB_6b+!BFCQSQ(q3I`%}J&}b}yIT(pk
zP&;}Zi{nRF64#iYV-)cY)Iv{SKl~Yk+QEYlv3u-+HSh&|20y})82_;6J&LblXZ+D@
z^oYw($HwGOp%xI=(VbL0Dz1$8VRfvF!%z!)rz7WIioz-qx&xa~NBtd^!QW6j{u?z=
zpp!e=5*SV#i(Dcv88uG2<ukD~@nfiQ`dR&O)X9uT^`F{_^VhR^orF5Pi&ZceBk>UG
z(VRow=6|pP#&I0F3-vJq8>1G`4mCk%)U$sA)vh0EBZE=%WMdSL3sTSmW@1Hr12y0}
ztdCz?{dLqp0nT6bp{Rc4u<(dcJFSjt*T`&vYJVSU$6Zhh>5sZY!KWzb)(%G<<;$p*
zFG5YQ6g6NjYQ<}@JbsNjp|i+WhIa+kuVfe3FWf9=RyJ#!jgU(o^jcBSK<%+9_CQTI
z7q!B-Q9D?QdMol!3;P^(nZCw)cn!6)l*inO)I=S1ebhKD%(j?G{1Arg{hv!Aj>J-o
z!HuXL?L$pmfEwsei;H%33yDDud^bj871SeWZ27jRBYz0B!N<*E7)v|>%jy01Dd_U7
zL=CtB)p3i(2T(iz7Sr%7>V#smT)TMGC9R2iM46}scf+zc5OrcX7>83)mv{jN@1n4Q
zf>wS2HSsCb0KcGib`y20OLucCFN>O}Hfq3@s10;M_3wkaBhQ%AP~$8`wOfbk|7ADM
zUjrW@p`D*W9o0{$72iNT(<0s7f=ZxHBG%#r)POZn{TiVb&<-_jSF<l_yrHO#J%<`+
za(B*OD|v;47O)WY%s)Wgg>|T--i<n;gQyA5pxRwRO?V5n(2_k|`w0A;_%77Ahfx!s
zG%uhQa4kqd6Wu`#5Z%+Qv@B`|NmvPMqrM*=!VWkTFXMXDK+7L@{nwxtybiVC&8YGB
zVBx}1?Jr?F3|^&BlS1=eu3`}C3uQR!mOh6ya0Y6?Pf-(WLml~U)Iv|9c3Oa%;5YL+
z>ZH6U+_;gbh1`XV6Z9%m&=J-^b!>uKKx@>1?NAdwf?7aV)KLz=N;tyeIjH`NQ0<qa
z`maV!xZd*HP?vspVV>vzgEjaS@1f!*YJ$4G-Gt3hcceXPCp}RE4?(pXiF$Ms@ot=g
zx@=2O3tEYK1fODA+=Y4@e!yZu3RfxA!<(pyYV>g{ZiX7L9cqFBsDTGtJQ6j*Xw*Vp
zK%LwyRJ*xY6&GR=+-~kf{kdWf237ct!UGu9*ZtDzg<8-o<h#cE0^4I;KX<gfQ4>Ci
zI@*!A5ra4uoA&p-1YC<+&>_?Yj$3>RHU7E&JbxW!%mBBOc+_2}g4#iv<y)AUs5|fo
z>PUN`+KoUhWE|=;PPKRr7M_6Rw_`Etzd>EzqXRhql@tm{WTQXO4OHYwx5IGM4&qS*
zCt(QIMlB=_b(vbD>bs#HNq^Kg<51L{nS^RL2erW!SQ$42Dd;FqqIPfrQ}8Nk;tGS@
zz?D$-wXiBSw)_*Q6Bvw|c%;SSt^Os{_^(;K5JQMppcWGRm_ibTd@PDTU_743@)&r^
zUCMYYM_dOrQ6|>FM^KMq3~FIBQMY?7Y9l*P6a9)h^1o3FC^NXwpchL)JBvr{xEhwg
zI;f*-h`LN|Q48pAjz@h#%|jjahp2^YLA^Ccup(Z;M2vjeSs%lQJ7O{JpVxze7SJ1Y
zR6`0Yc&$(kb1)RAqF&G0R=)&G5-&q7{1ep9HlW(&qjr1{E8ve<1aG4@=nbJi^LwEb
zG;s{-=qjO(EDg28HmH-xLbV%&X*dcs&~hw=AEOrfIqFevN7e5_jeF4Y|HYET7ci&}
ze^AiCf1_@7*iiSHltu0AZq&{yquSR%EwBOVJ#LNaHv;ttf~Jr9##@FuiF}K{L7mWr
zq5A%(@D~Z0FwFg#t`(}mGpL=vfVz~gnYmcFGt>ePn%|-pcnWpo1*n||vOO;YW3d``
z!74aDoAa+rVKIrqF9y^AN6ph%hWIBeh1XCM2cB^Y3q!RpZ&pU#nOdkLZio@s3L9WY
z)W#;G=9?F!pl3WEwW8ISh)1wAUNr;5-A=<%6U3q(Nk!CKk%C%SON_)!)CpvvPUt_V
z_M=hlf))p-QV1t83$?=qsMl!)*28?u|Bh;R3#((v5pKXV>_ME4s-I`RhnhGKwcw4Y
zjc!9--s4F7pjY}o?yE5#Yg5qx^}hGEd=Bb}=b|3P0@O}EGCx5b<wn$mU!ZRPS606h
zb&>~A3;7<k(E^Ot`~N?yC_d6Ph(JwL-r{Ozs^y!Z7S!H+6eEaxqE284YJp?1625>@
zxCA3{E$W+fJC<gC?+k?)`~@|DH_9y_0(FTJEUu0kpb=^Z?NB@Gj5V<js{ahsc=J*1
zR$(-5LbczAI<fCCsLOMP!hLuH^_sPK*1fmgQ71713oi|7f~n@~sGYx$vG}RwccT_?
z0(DZqqWb@7-m!ehbDY1PS;TX0rEyq|I0<!x?NJNth&qYEmLG*0c$~#Anln%n%|R`E
zzQxN?3;!6aV?OExE<DHitKqLCbVRqz@aNs@lZ5&PYmPd)E~rb?7xgHHpq}k`)PiTB
zc0Lc|a0Tiz=c69QDOCHPu>;-;S|W3_TR=DbgNh-j33`ul6AwoXkb_#lG}A}5TYy^N
zD%6HPL!Im{)JYvMkE0&h52$v*D-<-qKd6Bt$GU}7K&`Y6R>BO_k@dkEI1u%f{VG<#
zPw+0>hnnEO=2dJ+Tx^^hFCEqILFCQ^y=N%s_P&V8xEeLVVH}LNQI~Vjcz4NG;$q^h
z$RT<CbKLLw?@|44;U-L+z`t_icgVf=8uO?>#FbbTvnJ}RpI`z7U5Z@u80r%KgN?D;
z3+^u_dZRAaTd0#*jJlK`Api5$@Q-zvHi_SecoEBC?aA&AwMCs!FVv$PC7IuwXo*))
zxAaZa1k11puEe6a8r3lmb=mS!3;G)M?DwPkpG390h-&vIYN5ALmooH4_sA+@P#v05
z(6hcDHBb-K+b{?#;8-k+^H9$&7d6l~R$qX+Oo1tG0THNiDxw~3Ez|<KqV7gt)O;hR
zaQ;e6CXt3dR>B>qFO&<Yh1^E9i+suLtP*N~+Ncv~X=Y*=@xwR-yP?MU3X9<h)XAJi
zE%@R~oWFK*gM>~Ze5xBT0k!iK)XH0-Iy`^_vAgB-Q3LKq^*@H%$a##x@-Msobx?0v
z6V$@nqwYq}AO-Dg6l$OsQ9GE8ns7eW!sVy|kDw;HfXR3r)jocjJE;`Z!keHb>Wu0)
z5JPbc>QBX!us8-6Q_u-4Lk+wc)o?FrVMkF7&tnK)!V>sDRQtbB6NF87{Sr{mzA9=%
zX&8nXxER~x8oY!&lAyO>hU>5dHNk4Ei|a56&trQm@d`&6;6FrQE8>1L-D|cK8>k)X
zM9R%_?VF=c;w22n#i;Q=Mzvp$h424G3VJr>U*#fVO$^6X_z)9rM!p=qQm=7rI0d(p
z51qp+i2Hp8BR^}NyS$mNyYU90F7Ig6<(q+}a5n0M-@z!o|L;>s#PwG3E!HGHYnFP$
z{Ss+}6UleMws;ip#`rg#O>iIaW2i@v{FbvmYUeFb3%bwpSr}Bs080!pN1Id3Ip!i%
z`<19?nrHdV=5Exj{?_8lsP?xp0*lRe3yMYExup3#f338!RosVa*vaC)7H3;L0X5(Z
zbB_6z`7UZ<%TVL4F~30Ve2;k=^%`H9&-trE*4yrn-+i$<@k^M6tFaH3c*p$<#8c*S
z^MYA@f%_ft6l#G>P~)z!I1kf_H=$m?YpAy-Hn`BOv^px$&}@%oh<l+19Af^*9D~~7
zM02J&A9aGuEM9N%SEvOYvHWQ>c*zPk%;JmO3ZqdIq@tc_OVkl&S^TusPex5R+v0ao
z3(m!&xYqI;EWh32y~ub$?}R0O!6Z7|v^f4<w~*?n0o$1mqZam<#r@2|mLG0$4r&1}
zTK;8o9+o7(1VfqM%cY>(xX#>*dS8D;P4pA$=&qtp;123Uq87WjCMs@$ny@RXeGhYh
znQe|XCt(Qld#_l+$9squr~(gJ{siiX&sqKt%m0N{$QN6}zY|~$RKGsvAk+r3Egol1
z!ff)>FsOlVP|yj4E_GJKc;b6d19U{~yqCqp%^cK((=C3@;)Uk>mS2gQXQRctEIziB
z^Vh@$By?ndn<4MHi6c=HB$!pO8F4L(2Vvn3Q41Vr&a(Vsi&vq>--?=V4{C!yyvO;g
z<82ZeDDr)0f>{$)-`L^~W|rj#n9o~&y7>m`PP}JsMvZgK>VHO^$e%$9T3N^k?$>H0
zs-he!UmZiR0cwFw%}lei*#|YjFms~iee+#2*W835v=8pE#C}VhM1Ar6h+4>H%X`b*
z0!o;%W|CPKLulX3Y=eC5d+jYghuXkJ^Sa9iy<*GVM5Ryz#G@KkHtV6DRdcf&HYFZ~
ze2sgnQ0?2VaQ(ZQy-@i9sEv%U_ywz<h3R_#=TWFegKw<jhWQUhkq`OMeKD0o4VaEv
zXgk!O8y?0m>}~nMsCL6G&awDKi(fO}lzRUcS%VL(A`dn3Ce*-tEq@F(@F|NgqMrE`
z%NP5|wU096Q438mYny4P%bAWr4cM82?1MUqVHVFbm!OVtwZ-|Uf%jT`*y6KTll)Iu
z5~EkTb_u9QSOqI$D=dw@S91OuXefy!9EsY|JZrEVwbD;fC$S&Z{<wL@@&#CUSuOA7
zx&@R#^^e9PSj()7b%|4RIe!)Ukm!c5Vk^90)>y?~zY;%<Phu`=0kNyy4&qU9b<}OI
zheL1|>Wik~$FBYX^HH-qwx+&MkV0t+ORQqG#p^8IX6`l*Vg&7vqfY9A<!_<JDYC{H
zfm+zzn2gDmZ)f=~sQ$s86lPG!#xD35>e)T=iL(c4pn<3fhMHqAn)oFwk8fE%&)kAK
z`hBSW1s4Bm^&xqM^+7L&LNpC(qbA6}3iu$Z;~>jto8z!L`IjwTgSs1EU?}di{6X`y
zdC9zC7GJA*_5Dvlw>AZfVn>X`F6Kbg1ka(4ej=8^*{EN;3sLQlVhR>mT>4YD&;-=R
zGBE}_W8uc6-v4ZC@T^r#GN+ldQ780<`JuTPwV-dzvlvHw8Flm_>zs*Z6SEU)K~H1h
zpZ}k?#01n%XPEOWzsOvP8h9gW2Zt<w!Yr`-AE*WYg*urs>s?$CHF2uhay{p-!~>S-
zW(^0Tb~f7Lm#zM7b2(;E|0!xA*DU`xY6D>#+(a>``bwxTplTN1YxNyAaQ<3AmL&$5
zBdp;BbBg(j>7#ZyAN6|WqQ*IBp0@f+sEypgau~kR-GOSTe6t`0RXl)tb^|OPYYlwV
zz)Mj(_}JWw+SvuG|J~xhEiSsrjTedPUlsM$o`(7g?uq)w4UVNyg~B_i0k@)#aHo05
z{N5};4R{%K0=H59B0qEavZ&Xv3Tm7>mT!!j_&$sKBmIKja0(jedDMG79V=rVYNBK2
zIn+d#Q3GGMIPkeU!BA9v5^B8as0o{)j=G1{KW&aEl;=O1f+h-@ub?`-g{ip0;*;hj
z)FZovnlN^=`+I|U)HsXGm8klSs0D99jk_Bg;dfa0_y18}xD{7KeG#RiCU_imC!R$O
zGy^rk9E%s3ADFr3TJtmWOH{vI<}vde26a@IEm1VzedkA-@n%&s6}5xrs2x3Mb~XE$
zPoow*3KMV~Y5{MVAE4&V%eVLcYfBuq1_h{z{y+^>bc-9Hq*>OiVy2=_tfj?Ss0jvG
z{5)#B7cHKP8h7!QpzFAt#8V`;q6VtD)p;*!r;npLJc-)LFmtRq)tqZCHdmRSp~l&X
z8t0JZPXs9xCsBa<wfhU!#_QIg%9m~-si=uNpa#e?pFsW68ffugbGZ4uIRVvw3hGYH
z!D1L(ZWXzxBim%|L!HD~^CoJ5=xxqKRQuXy25MoCTHGDgekf|3F&59UcoAml{og=A
z14Vx2OvZD>jV!LP-3?U7OgGz^UCh21PQPr_v(7;+Xr?(AwV=1n<%Rm$wZjX42OhBW
zG4mWI(fl%MnCP$F*Hd|{Oxy;wuYu+S)bMYhw(}n9>uf#hci>q}#4A`4qjtFZ`a3uS
z4cOfh<FP66`}i21K@Hq+C;vB9Y=az}cLKG5)LqUFsQUh>ub02Dj_P;w=W6VY@pu^3
z?|0NfL-+6w=)mIlxc@#;3-2dxgKcpJhT%aB!IP*3{fOE4JHCle>~()uzK*fP>HFNj
z#yn#7MfD$v>Oa<;8nnV(Yp@uL(_lFk#Z{L71WOWcwD>F34)$OvJdIlDMXSGR@h#K_
zi|=>Fp%##W>K|-iiMH0@QPdIjvj*c(&ukXz<;g`I<u=qnXUt#Bo2W+`a=_KsFdLco
znjNu*-ho~eG~raMco(&@T#L7u`!It1cUZVn^D63S?_g&v{*CM39hDzyj>N*FxB96V
z!TjEQ3ZXRo5H)Zf>Je<lNZf`K@Ca&v&Ig@+FoJj(reh9j;Turn?6LZz=6Tc!UbDE=
zA=)v&7f+$^nV1c*4*3j>#$l+5CZb-;*{JrPSUw*$@Bxd@o7YeaDSp`fDvCh$tAo*)
zj)i{*_K;QdLUni=b@@hFJQ{VBlTZUpGhes-Qu9M|jky8G&^{lv(WXb-xEoRPY(7F-
z1MeV_g8Q)+Ud3)$=_vmPJsg92EuT8ZFJN4Qb@4aUWlZ?iomf*;JP5VW$>wZqNxT5N
z;P<E#NIhQ7y}$PzcPl!D0V?k2A61J`;OASs>0e2$7x>0cYLFQC+;82WY}}Vt`>dMt
zvzX6De^`U$s1wu-=TnNBvwZ&Y7dNOCc8km%qVN2p4Uz-@^KUk&6@7!;4}A1<hPr=>
z_>Iy!L~XZJC(@@VU-Vx~OG|i<(k2?U;-jC+d>UH0nEyjs|AY(HuRGbzR`WWR<a5^V
zdrwS_NIg}4BFr}QxJ`V{dcHzAmDYRw1@~kHa{X)fJW^*Db&dISwGrd!TZwWpa$z=(
z#vD&v#vjozCP8;cFVuRa`H7-t5}%Lgb--WTFfp*y-`X%OP{hC1u-RRA(fT85=iB(*
zDDz)KGaFS+2%%~+!6$_&E;2r0%{KYd8Z`@i;BRk~7WmV@NqMW^uyIykr9ZQAVqAcp
z<@h{K?-BHPkk2-MYvYVSaX-+cUdYdkbcfS!(kSquKdec;K$O3rNqq6mq|f`Cn<Piw
zrnHvML+LmdEBIHM#DtwB8_lo<ewn6a1K0eNrpbZpe*30rf&G3?)5(Fa{6KnQV3nVg
zUN*4SZ<<~&<uW}o`TYA6ZOcDOPX7UIA)lZ8nd!-atNxnwYH<h2=aRchhdmg>Y(@QF
z)2k(vr}Xbn60LeryWJA8ermG^0^j>N&Em_ip>8mrCDiNZS%<gUU(@W~KxzM4vwDG2
zeoFINfl$9w^Z4S2XuH85(L6qBFD3nahO=;=jiB7`e7^8=>2<_E(R@hM-?Zt+rygyF
z;D7zcS~L#i`!ic4CzQ06zGMBDQUCAHFaEw387YS?GmsXaS=FzW%O^g~=XXD~Wn$op
z->GGM)N$(i^Eqa1e)Mx%mQ7es)cN0mvFyJm#Hc&`H7zrWe@%;z{c9}~%a@?8Jo$Uc
zth05g>u$eJMttBmzkSBNfi3>bjK*a?r9})M{VewnW+Vo}{VN$UWmb^mk7b2Fm;9(!
z)dD;HrmYTy?IS;z&uKrZby~tY>)C}lrc*v*J^E74^ZT|=4xI6)w5}F--_LEmKJZ80
wz&0PX%FCQQKP2zQw3kCl*00mxo=T~y>Gjj{rq4dtJa6H?@*#P<4<9K0e~L&%&Hw-a

diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po
index 9e3459248..0f8a6448f 100644
--- a/apps/locale/zh/LC_MESSAGES/django.po
+++ b/apps/locale/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-10-24 11:03+0800\n"
+"POT-Creation-Date: 2018-11-01 13:58+0800\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: ibuler <ibuler@qq.com>\n"
 "Language-Team: Jumpserver team<ibuler@qq.com>\n"
@@ -191,7 +191,7 @@ msgstr "名称"
 #: assets/templates/assets/system_user_list.html:30
 #: audits/templates/audits/login_log_list.html:49
 #: perms/templates/perms/asset_permission_user.html:55 users/forms.py:15
-#: users/forms.py:33 users/models/authentication.py:70 users/models/user.py:49
+#: users/forms.py:33 users/models/authentication.py:72 users/models/user.py:49
 #: users/templates/users/_select_user_modal.html:14
 #: users/templates/users/login.html:62
 #: users/templates/users/user_detail.html:67
@@ -666,8 +666,8 @@ msgstr "手动登录"
 #: 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:145 assets/views/label.py:26 assets/views/label.py:42
-#: assets/views/label.py:58 assets/views/system_user.py:28
+#: assets/views/domain.py:145 assets/views/label.py:26 assets/views/label.py:43
+#: assets/views/label.py:69 assets/views/system_user.py:28
 #: assets/views/system_user.py:44 assets/views/system_user.py:60
 #: assets/views/system_user.py:74 templates/_nav.html:19
 msgid "Assets"
@@ -1059,7 +1059,7 @@ msgstr "选择节点"
 #: users/templates/users/user_detail.html:431
 #: users/templates/users/user_detail.html:476
 #: users/templates/users/user_group_create_update.html:32
-#: users/templates/users/user_group_list.html:87
+#: users/templates/users/user_group_list.html:88
 #: users/templates/users/user_list.html:201
 #: users/templates/users/user_profile.html:232
 #: xpack/plugins/cloud/templates/cloud/account_create_update.html:34
@@ -1276,7 +1276,7 @@ msgstr "重命名失败,不能更改root节点的名称"
 #: users/templates/users/user_detail.html:376
 #: users/templates/users/user_detail.html:402
 #: users/templates/users/user_detail.html:470
-#: users/templates/users/user_group_list.html:81
+#: users/templates/users/user_group_list.html:82
 #: users/templates/users/user_list.html:195
 msgid "Are you sure?"
 msgstr "你确认吗?"
@@ -1292,7 +1292,7 @@ msgstr "删除选择资产"
 #: users/templates/users/user_detail.html:406
 #: users/templates/users/user_detail.html:474
 #: users/templates/users/user_group_create_update.html:31
-#: users/templates/users/user_group_list.html:85
+#: users/templates/users/user_group_list.html:86
 #: users/templates/users/user_list.html:199
 #: xpack/plugins/orgs/templates/orgs/org_create_update.html:32
 msgid "Cancel"
@@ -1424,7 +1424,7 @@ msgstr "JMS => 网域网关 => 目标资产"
 msgid "Create domain"
 msgstr "创建网域"
 
-#: assets/templates/assets/label_list.html:6 assets/views/label.py:43
+#: assets/templates/assets/label_list.html:6 assets/views/label.py:44
 msgid "Create label"
 msgstr "创建标签"
 
@@ -1592,7 +1592,11 @@ msgstr "创建网关"
 msgid "Label list"
 msgstr "标签列表"
 
-#: assets/views/label.py:59
+#: assets/views/label.py:53
+msgid "Tips: Avoid using label names reserved internally: {}"
+msgstr "提示: 请避免使用内部预留标签名: {}"
+
+#: assets/views/label.py:70
 msgid "Update label"
 msgstr "更新标签"
 
@@ -1635,7 +1639,7 @@ msgid "Filename"
 msgstr "文件名"
 
 #: audits/models.py:22 audits/templates/audits/ftp_log_list.html:76
-#: ops/templates/ops/task_list.html:39 users/models/authentication.py:66
+#: ops/templates/ops/task_list.html:39 users/models/authentication.py:68
 #: users/templates/users/user_detail.html:452 xpack/plugins/cloud/api.py:61
 msgid "Success"
 msgstr "成功"
@@ -1703,19 +1707,19 @@ msgid "City"
 msgstr "城市"
 
 #: audits/templates/audits/login_log_list.html:54 users/forms.py:169
-#: users/models/authentication.py:75 users/models/user.py:73
+#: users/models/authentication.py:77 users/models/user.py:73
 #: users/templates/users/first_login.html:45
 msgid "MFA"
 msgstr "MFA"
 
 #: audits/templates/audits/login_log_list.html:55
-#: users/models/authentication.py:76 xpack/plugins/cloud/models.py:192
+#: users/models/authentication.py:78 xpack/plugins/cloud/models.py:192
 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:69
 msgid "Reason"
 msgstr "原因"
 
 #: audits/templates/audits/login_log_list.html:56
-#: users/models/authentication.py:77 xpack/plugins/cloud/models.py:191
+#: users/models/authentication.py:79 xpack/plugins/cloud/models.py:191
 #: xpack/plugins/cloud/models.py:208
 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70
 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:67
@@ -1752,15 +1756,15 @@ msgstr "操作日志"
 msgid "Password change log"
 msgstr "改密日志"
 
-#: audits/views.py:183 templates/_nav.html:10 users/views/group.py:28
+#: audits/views.py:187 templates/_nav.html:10 users/views/group.py:28
 #: users/views/group.py:44 users/views/group.py:60 users/views/group.py:76
-#: users/views/group.py:92 users/views/login.py:328 users/views/user.py:68
+#: users/views/group.py:92 users/views/login.py:331 users/views/user.py:68
 #: users/views/user.py:83 users/views/user.py:111 users/views/user.py:193
 #: users/views/user.py:354 users/views/user.py:404 users/views/user.py:439
 msgid "Users"
 msgstr "用户管理"
 
-#: audits/views.py:184 templates/_nav.html:76
+#: audits/views.py:188 templates/_nav.html:76
 msgid "Login log"
 msgstr "登录日志"
 
@@ -1780,15 +1784,15 @@ msgstr "在ou:{}中没有匹配条目"
 msgid "Match {} s users"
 msgstr "匹配 {} 个用户"
 
-#: common/api.py:107 common/api.py:138
+#: common/api.py:107 common/api.py:139
 msgid "Error: Account invalid"
 msgstr ""
 
-#: common/api.py:110 common/api.py:141
+#: common/api.py:110 common/api.py:142
 msgid "Create succeed"
 msgstr "创建成功"
 
-#: common/api.py:127 common/api.py:161
+#: common/api.py:128 common/api.py:162
 #: common/templates/common/terminal_setting.html:151
 msgid "Delete succeed"
 msgstr "删除成功"
@@ -3084,19 +3088,19 @@ msgstr "你可以使用ssh客户端工具连接终端"
 msgid "Log in frequently and try again later"
 msgstr "登录频繁, 稍后重试"
 
-#: users/api/auth.py:79
+#: users/api/auth.py:82
 msgid "Please carry seed value and conduct MFA secondary certification"
 msgstr "请携带seed值, 进行MFA二次认证"
 
-#: users/api/auth.py:192
+#: users/api/auth.py:195
 msgid "Please verify the user name and password first"
 msgstr "请先进行用户名和密码验证"
 
-#: users/api/auth.py:204
+#: users/api/auth.py:207
 msgid "MFA certification failed"
 msgstr "MFA认证失败"
 
-#: users/api/user.py:135
+#: users/api/user.py:138
 msgid "Could not reset self otp, use profile reset instead"
 msgstr "不能再该页面重置MFA, 请去个人信息页面重置"
 
@@ -3265,40 +3269,44 @@ msgstr "ssh密钥"
 msgid "Disabled"
 msgstr "禁用"
 
-#: users/models/authentication.py:52 users/models/authentication.py:60
+#: users/models/authentication.py:52 users/models/authentication.py:61
 msgid "-"
 msgstr ""
 
-#: users/models/authentication.py:61
+#: users/models/authentication.py:62
 msgid "Username/password check failed"
 msgstr "用户名/密码 校验失败"
 
-#: users/models/authentication.py:62
+#: users/models/authentication.py:63
 msgid "MFA authentication failed"
 msgstr "MFA 认证失败"
 
-#: users/models/authentication.py:67 xpack/plugins/cloud/models.py:184
+#: users/models/authentication.py:64
+msgid "Username does not exist"
+msgstr "用户名不存在"
+
+#: users/models/authentication.py:69 xpack/plugins/cloud/models.py:184
 #: xpack/plugins/cloud/models.py:198
 msgid "Failed"
 msgstr "失败"
 
-#: users/models/authentication.py:71
+#: users/models/authentication.py:73
 msgid "Login type"
 msgstr "登录方式"
 
-#: users/models/authentication.py:72
+#: users/models/authentication.py:74
 msgid "Login ip"
 msgstr "登录IP"
 
-#: users/models/authentication.py:73
+#: users/models/authentication.py:75
 msgid "Login city"
 msgstr "登录城市"
 
-#: users/models/authentication.py:74
+#: users/models/authentication.py:76
 msgid "User agent"
 msgstr "Agent"
 
-#: users/models/authentication.py:78
+#: users/models/authentication.py:80
 msgid "Date login"
 msgstr "登录日期"
 
@@ -3731,20 +3739,20 @@ msgstr "添加用户"
 msgid "Create user group"
 msgstr "创建用户组"
 
-#: users/templates/users/user_group_list.html:82
+#: users/templates/users/user_group_list.html:83
 msgid "This will delete the selected groups !!!"
 msgstr "删除选择组"
 
-#: users/templates/users/user_group_list.html:91
+#: users/templates/users/user_group_list.html:92
 msgid "UserGroups Deleted."
 msgstr "用户组删除"
 
-#: users/templates/users/user_group_list.html:92
-#: users/templates/users/user_group_list.html:97
+#: users/templates/users/user_group_list.html:93
+#: users/templates/users/user_group_list.html:98
 msgid "UserGroups Delete"
 msgstr "用户组删除"
 
-#: users/templates/users/user_group_list.html:96
+#: users/templates/users/user_group_list.html:97
 msgid "UserGroup Deleting failed."
 msgstr "用户组删除失败"
 
@@ -4019,52 +4027,52 @@ msgstr "用户组授权资产"
 msgid "Please enable cookies and try again."
 msgstr "设置你的浏览器支持cookie"
 
-#: users/views/login.py:176 users/views/user.py:526 users/views/user.py:551
+#: users/views/login.py:179 users/views/user.py:526 users/views/user.py:551
 msgid "MFA code invalid, or ntp sync server time"
 msgstr "MFA验证码不正确,或者服务器端时间不对"
 
-#: users/views/login.py:205
+#: users/views/login.py:208
 msgid "Logout success"
 msgstr "退出登录成功"
 
-#: users/views/login.py:206
+#: users/views/login.py:209
 msgid "Logout success, return login page"
 msgstr "退出登录成功,返回到登录页面"
 
-#: users/views/login.py:222
+#: users/views/login.py:225
 msgid "Email address invalid, please input again"
 msgstr "邮箱地址错误,重新输入"
 
-#: users/views/login.py:235
+#: users/views/login.py:238
 msgid "Send reset password message"
 msgstr "发送重置密码邮件"
 
-#: users/views/login.py:236
+#: users/views/login.py:239
 msgid "Send reset password mail success, login your mail box and follow it "
 msgstr ""
 "发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)"
 
-#: users/views/login.py:249
+#: users/views/login.py:252
 msgid "Reset password success"
 msgstr "重置密码成功"
 
-#: users/views/login.py:250
+#: users/views/login.py:253
 msgid "Reset password success, return to login page"
 msgstr "重置密码成功,返回到登录页面"
 
-#: users/views/login.py:271 users/views/login.py:284
+#: users/views/login.py:274 users/views/login.py:287
 msgid "Token invalid or expired"
 msgstr "Token错误或失效"
 
-#: users/views/login.py:280
+#: users/views/login.py:283
 msgid "Password not same"
 msgstr "密码不一致"
 
-#: users/views/login.py:290 users/views/user.py:127 users/views/user.py:422
+#: users/views/login.py:293 users/views/user.py:127 users/views/user.py:422
 msgid "* Your password does not meet the requirements"
 msgstr "* 您的密码不符合要求"
 
-#: users/views/login.py:328
+#: users/views/login.py:331
 msgid "First login"
 msgstr "首次登陆"
 
diff --git a/apps/users/api/auth.py b/apps/users/api/auth.py
index 4abd2839b..9de2be6b2 100644
--- a/apps/users/api/auth.py
+++ b/apps/users/api/auth.py
@@ -43,10 +43,13 @@ class UserAuthApi(RootOrgViewMixin, APIView):
 
         user, msg = self.check_user_valid(request)
         if not user:
+            username = request.data.get('username', '')
+            exist = User.objects.filter(username=username).first()
+            reason = LoginLog.REASON_PASSWORD if exist else LoginLog.REASON_NOT_EXIST
             data = {
-                'username': request.data.get('username', ''),
+                'username': username,
                 'mfa': LoginLog.MFA_UNKNOWN,
-                'reason': LoginLog.REASON_PASSWORD,
+                'reason': reason,
                 'status': False
             }
             self.write_login_log(request, data)
diff --git a/apps/users/models/authentication.py b/apps/users/models/authentication.py
index cb0b7d85f..5377b0f75 100644
--- a/apps/users/models/authentication.py
+++ b/apps/users/models/authentication.py
@@ -55,11 +55,13 @@ class LoginLog(models.Model):
     REASON_NOTHING = 0
     REASON_PASSWORD = 1
     REASON_MFA = 2
+    REASON_NOT_EXIST = 3
 
     REASON_CHOICE = (
         (REASON_NOTHING, _('-')),
         (REASON_PASSWORD, _('Username/password check failed')),
         (REASON_MFA, _('MFA authentication failed')),
+        (REASON_NOT_EXIST, _("Username does not exist")),
     )
 
     STATUS_CHOICE = (
@@ -67,7 +69,7 @@ class LoginLog(models.Model):
         (False, _('Failed'))
     )
     id = models.UUIDField(default=uuid.uuid4, primary_key=True)
-    username = models.CharField(max_length=20, verbose_name=_('Username'))
+    username = models.CharField(max_length=128, verbose_name=_('Username'))
     type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2, verbose_name=_('Login type'))
     ip = models.GenericIPAddressField(verbose_name=_('Login ip'))
     city = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('Login city'))
diff --git a/apps/users/views/login.py b/apps/users/views/login.py
index 31021d75e..4f2308d04 100644
--- a/apps/users/views/login.py
+++ b/apps/users/views/login.py
@@ -79,12 +79,15 @@ class UserLoginView(FormView):
     def form_invalid(self, form):
         # write login failed log
         username = form.cleaned_data.get('username')
+        exist = User.objects.filter(username=username).first()
+        reason = LoginLog.REASON_PASSWORD if exist else LoginLog.REASON_NOT_EXIST
         data = {
             'username': username,
             'mfa': LoginLog.MFA_UNKNOWN,
-            'reason': LoginLog.REASON_PASSWORD,
+            'reason': reason,
             'status': False
         }
+
         self.write_login_log(data)
 
         # limit user login failed count
diff --git a/requirements/deb_requirements.txt b/requirements/deb_requirements.txt
index f4131a3ea..d4cf29941 100644
--- a/requirements/deb_requirements.txt
+++ b/requirements/deb_requirements.txt
@@ -1 +1 @@
-libtiff5-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev python-tk python-dev openssl libssl-dev libldap2-dev libsasl2-dev sqlite gcc automake libkrb5-dev
+libtiff5-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev python-tk python-dev openssl libssl-dev libldap2-dev libsasl2-dev sqlite gcc automake libkrb5-dev sshpass

From 1e5387ef47b7eff5c6356c8a501a41719500c076 Mon Sep 17 00:00:00 2001
From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com>
Date: Fri, 2 Nov 2018 14:38:44 +0800
Subject: [PATCH 16/17] =?UTF-8?q?[Update]=20=E6=9B=B4=E6=96=B0=E7=BB=84?=
 =?UTF-8?q?=E7=BB=87=E7=AE=A1=E7=90=86api=20(#1986)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* [Update] 更新组织管理api

* [Update] 重写-组织管理员/用户API,采用through类

* [Update] 修改OrgMembershipSerializerMixin目录

* [Update] 修改组织管理API,限制http method

* [Update] 修改rpm依赖
---
 apps/orgs/api.py                  | 60 ++++++++++++++++++++++++--
 apps/orgs/mixins.py               | 29 ++++++++++++-
 apps/orgs/serializers.py          | 71 +++++++++++++++++++++++++++++++
 apps/orgs/urls/api_urls.py        |  8 ++++
 apps/static/js/jumpserver.js      |  2 +-
 requirements/rpm_requirements.txt |  2 +-
 6 files changed, 165 insertions(+), 7 deletions(-)

diff --git a/apps/orgs/api.py b/apps/orgs/api.py
index 7da77cfce..a99715d47 100644
--- a/apps/orgs/api.py
+++ b/apps/orgs/api.py
@@ -1,14 +1,68 @@
 # -*- coding: utf-8 -*-
 #
 
-from rest_framework import viewsets
+from rest_framework import status
+from rest_framework.views import Response
+from rest_framework_bulk import BulkModelViewSet
 
 from common.permissions import IsSuperUserOrAppUser
 from .models import Organization
-from .serializers import OrgSerializer
+from .serializers import OrgSerializer, OrgReadSerializer, \
+    OrgMembershipUserSerializer, OrgMembershipAdminSerializer
+from users.models import User, UserGroup
+from assets.models import Asset, Domain, AdminUser, SystemUser, Label
+from perms.models import AssetPermission
+from orgs.utils import current_org
+from common.utils import get_logger
+from .mixins import OrgMembershipModelViewSetMixin
+
+logger = get_logger(__file__)
 
 
-class OrgViewSet(viewsets.ModelViewSet):
+class OrgViewSet(BulkModelViewSet):
     queryset = Organization.objects.all()
     serializer_class = OrgSerializer
     permission_classes = (IsSuperUserOrAppUser,)
+    org = None
+
+    def get_serializer_class(self):
+        if self.action in ('list', 'retrieve'):
+            return OrgReadSerializer
+        else:
+            return super().get_serializer_class()
+
+    def get_data_from_model(self, model):
+        if model == User:
+            data = model.objects.filter(orgs__id=self.org.id)
+        else:
+            data = model.objects.filter(org_id=self.org.id)
+        return data
+
+    def destroy(self, request, *args, **kwargs):
+        self.org = self.get_object()
+        models = [
+            User, UserGroup,
+            Asset, Domain, AdminUser, SystemUser, Label,
+            AssetPermission,
+        ]
+        for model in models:
+            data = self.get_data_from_model(model)
+            if data:
+                return Response(status=status.HTTP_400_BAD_REQUEST)
+        else:
+            if str(current_org) == str(self.org):
+                return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
+            self.org.delete()
+            return Response({'msg': True}, status=status.HTTP_200_OK)
+
+
+class OrgMembershipAdminsViewSet(OrgMembershipModelViewSetMixin, BulkModelViewSet):
+    serializer_class = OrgMembershipAdminSerializer
+    membership_class = Organization.admins.through
+    permission_classes = (IsSuperUserOrAppUser, )
+
+
+class OrgMembershipUsersViewSet(OrgMembershipModelViewSetMixin, BulkModelViewSet):
+    serializer_class = OrgMembershipUserSerializer
+    membership_class = Organization.users.through
+    permission_classes = (IsSuperUserOrAppUser, )
diff --git a/apps/orgs/mixins.py b/apps/orgs/mixins.py
index 917a220bf..497377520 100644
--- a/apps/orgs/mixins.py
+++ b/apps/orgs/mixins.py
@@ -9,7 +9,6 @@ from django.forms import ModelForm
 from django.http.response import HttpResponseForbidden
 from django.core.exceptions import ValidationError
 
-
 from common.utils import get_logger
 from .utils import current_org, set_current_org, set_to_root_org
 from .models import Organization
@@ -19,7 +18,7 @@ tl = Local()
 
 __all__ = [
     'OrgManager', 'OrgViewGenericMixin', 'OrgModelMixin', 'OrgModelForm',
-    'RootOrgViewMixin',
+    'RootOrgViewMixin', 'OrgMembershipSerializerMixin', 'OrgMembershipModelViewSetMixin'
 ]
 
 
@@ -176,3 +175,29 @@ class OrgModelForm(ModelForm):
                 continue
             model = field.queryset.model
             field.queryset = model.objects.all()
+
+
+class OrgMembershipSerializerMixin:
+    def run_validation(self, initial_data=None):
+        initial_data['organization'] = str(self.context['org'].id)
+        return super().run_validation(initial_data)
+
+
+class OrgMembershipModelViewSetMixin:
+    org = None
+    membership_class = None
+    lookup_field = 'user'
+    lookup_url_kwarg = 'user_id'
+    http_method_names = ['get', 'post', 'delete', 'head', 'options']
+
+    def dispatch(self, request, *args, **kwargs):
+        self.org = Organization.objects.get(pk=kwargs.get('org_id'))
+        return super().dispatch(request, *args, **kwargs)
+
+    def get_serializer_context(self):
+        context = super().get_serializer_context()
+        context['org'] = self.org
+        return context
+
+    def get_queryset(self):
+        return self.membership_class.objects.filter(organization=self.org)
diff --git a/apps/orgs/serializers.py b/apps/orgs/serializers.py
index 8a2de4582..679125195 100644
--- a/apps/orgs/serializers.py
+++ b/apps/orgs/serializers.py
@@ -1,10 +1,81 @@
 
 from rest_framework.serializers import ModelSerializer
+from rest_framework import serializers
+from rest_framework_bulk import BulkListSerializer
+
+from users.models import User, UserGroup
+from assets.models import Asset, Domain, AdminUser, SystemUser, Label
+from perms.models import AssetPermission
+from .utils import set_current_org, get_current_org
 from .models import Organization
+from .mixins import OrgMembershipSerializerMixin
 
 
 class OrgSerializer(ModelSerializer):
     class Meta:
         model = Organization
+        list_serializer_class = BulkListSerializer
         fields = '__all__'
         read_only_fields = ['id', 'created_by', 'date_created']
+
+
+class OrgReadSerializer(ModelSerializer):
+    admins = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True)
+    users = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True)
+    user_groups = serializers.SerializerMethodField()
+    assets = serializers.SerializerMethodField()
+    domains = serializers.SerializerMethodField()
+    admin_users = serializers.SerializerMethodField()
+    system_users = serializers.SerializerMethodField()
+    labels = serializers.SerializerMethodField()
+    perms = serializers.SerializerMethodField()
+
+    class Meta:
+        model = Organization
+        fields = '__all__'
+
+    @staticmethod
+    def get_data_from_model(obj, model):
+        current_org = get_current_org()
+        set_current_org(Organization.root())
+        if model == Asset:
+            data = [o.hostname for o in model.objects.filter(org_id=obj.id)]
+        else:
+            data = [o.name for o in model.objects.filter(org_id=obj.id)]
+        set_current_org(current_org)
+        return data
+
+    def get_user_groups(self, obj):
+        return self.get_data_from_model(obj, UserGroup)
+
+    def get_assets(self, obj):
+        return self.get_data_from_model(obj, Asset)
+
+    def get_domains(self, obj):
+        return self.get_data_from_model(obj, Domain)
+
+    def get_admin_users(self, obj):
+        return self.get_data_from_model(obj, AdminUser)
+
+    def get_system_users(self, obj):
+        return self.get_data_from_model(obj, SystemUser)
+
+    def get_labels(self, obj):
+        return self.get_data_from_model(obj, Label)
+
+    def get_perms(self, obj):
+        return self.get_data_from_model(obj, AssetPermission)
+
+
+class OrgMembershipAdminSerializer(OrgMembershipSerializerMixin, ModelSerializer):
+    class Meta:
+        model = Organization.admins.through
+        list_serializer_class = BulkListSerializer
+        fields = '__all__'
+
+
+class OrgMembershipUserSerializer(OrgMembershipSerializerMixin, ModelSerializer):
+    class Meta:
+        model = Organization.users.through
+        list_serializer_class = BulkListSerializer
+        fields = '__all__'
diff --git a/apps/orgs/urls/api_urls.py b/apps/orgs/urls/api_urls.py
index a90d911e2..deca429ed 100644
--- a/apps/orgs/urls/api_urls.py
+++ b/apps/orgs/urls/api_urls.py
@@ -1,12 +1,20 @@
 # -*- coding: utf-8 -*-
 #
 
+from django.urls import path
 from rest_framework.routers import DefaultRouter
 from .. import api
 
 
 app_name = 'orgs'
 router = DefaultRouter()
+
+router.register(r'org/(?P<org_id>[0-9a-zA-Z\-]{36})/membership/admins',
+                api.OrgMembershipAdminsViewSet, 'membership-admins')
+
+router.register(r'org/(?P<org_id>[0-9a-zA-Z\-]{36})/membership/users',
+                api.OrgMembershipUsersViewSet, 'membership-users'),
+
 router.register(r'orgs', api.OrgViewSet, 'org')
 
 
diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js
index 207733618..3feedbd54 100644
--- a/apps/static/js/jumpserver.js
+++ b/apps/static/js/jumpserver.js
@@ -146,7 +146,7 @@ function activeNav() {
     if (app === ''){
         $('#index').addClass('active');
     }
-    else if (app === 'xpack') {
+    else if (app === 'xpack' && resource === 'cloud') {
         var item = url_array[3];
         $("#" + app).addClass('active');
         $('#' + app + ' #' + resource).addClass('active');
diff --git a/requirements/rpm_requirements.txt b/requirements/rpm_requirements.txt
index 2721d614d..be3ce68e3 100644
--- a/requirements/rpm_requirements.txt
+++ b/requirements/rpm_requirements.txt
@@ -1 +1 @@
-libtiff-devel libjpeg-devel libzip-devel freetype-devel lcms2-devel libwebp-devel tcl-devel tk-devel sshpass openldap-devel mysql-devel libffi-devel openssh-clients
+libtiff-devel libjpeg-devel libzip-devel freetype-devel lcms2-devel libwebp-devel tcl-devel tk-devel sshpass openldap-devel mariadb-devel libffi-devel openssh-clients

From f9e9bf0b2d33909f5d8e88adbce87fe4e234660d Mon Sep 17 00:00:00 2001
From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com>
Date: Mon, 5 Nov 2018 11:11:20 +0800
Subject: [PATCH 17/17] =?UTF-8?q?[Update]=20=E6=9B=B4=E6=96=B0=E7=94=A8?=
 =?UTF-8?q?=E6=88=B7/=E6=8E=88=E6=9D=83=E8=A7=84=E5=88=99=E8=BF=87?=
 =?UTF-8?q?=E6=9C=9F=E6=97=B6=E9=97=B4=E7=B2=BE=E7=A1=AE=E5=88=B0min=20(#1?=
 =?UTF-8?q?993)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../perms/asset_permission_create_update.html |  31 +-
 .../daterangepicker/daterangepicker.css       | 388 ++++++++++++++++++
 .../daterangepicker/daterangepicker.min.js    |   8 +
 .../js/plugins/daterangepicker/moment.min.js  |   7 +
 apps/users/templates/users/_user.html         |  26 +-
 5 files changed, 439 insertions(+), 21 deletions(-)
 create mode 100644 apps/static/css/plugins/daterangepicker/daterangepicker.css
 create mode 100644 apps/static/js/plugins/daterangepicker/daterangepicker.min.js
 create mode 100644 apps/static/js/plugins/daterangepicker/moment.min.js

diff --git a/apps/perms/templates/perms/asset_permission_create_update.html b/apps/perms/templates/perms/asset_permission_create_update.html
index 0ec3ae639..2d79890f6 100644
--- a/apps/perms/templates/perms/asset_permission_create_update.html
+++ b/apps/perms/templates/perms/asset_permission_create_update.html
@@ -54,9 +54,9 @@
                                 <div class="col-sm-9">
                                     <div class="input-daterange input-group" id="datepicker">
                                         <span class="input-group-addon"><i class="fa fa-calendar"></i></span>
-                                        <input type="text" class="input-sm form-control" name="date_start" value="{{ form.date_start.value|date:'Y-m-d' }}">
+                                        <input type="text" class="input-sm form-control" id="date_start" name="date_start" value="{{ form.date_start.value|date:'Y-m-d H:i' }}">
                                         <span class="input-group-addon">to</span>
-                                        <input type="text" class="input-sm form-control" name="date_expired" value="{{ form.date_expired.value|date:'Y-m-d' }}">
+                                        <input type="text" class="input-sm form-control" id="date_expired" name="date_expired" value="{{ form.date_expired.value|date:'Y-m-d H:i' }}">
                                     </div>
                                     <span class="help-block ">{{ form.date_expired.errors }}</span>
                                     <span class="help-block ">{{ form.date_start.errors }}</span>
@@ -70,6 +70,7 @@
                                     <button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
                                 </div>
                             </div>
+
                         </form>
                     </div>
                 </div>
@@ -80,19 +81,27 @@
 {% endblock %}
 {% block custom_foot_js %}
 <script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
+<script type="text/javascript" src='{% static "js/plugins/daterangepicker/moment.min.js" %}'></script>
+<script type="text/javascript" src='{% static "js/plugins/daterangepicker/daterangepicker.min.js" %}'></script>
+<link rel="stylesheet" type="text/css" href={% static "css/plugins/daterangepicker/daterangepicker.css" %} />
+
 <script>
+var dateOptions = {
+    singleDatePicker: true,
+    showDropdowns: true,
+    timePicker: true,
+    timePicker24Hour: true,
+    autoApply: true,
+    locale: {
+        format: 'YYYY-MM-DD HH:mm'
+    }
+};
 $(document).ready(function () {
     $('.select2').select2({
         closeOnSelect: false
     });
-    $('#datepicker').datepicker({
-        format: "yyyy-mm-dd",
-        todayBtn: "linked",
-        keyboardNavigation: false,
-        forceParse: false,
-        calendarWeeks: true,
-        autoclose: true
-    });
+    $('#date_start').daterangepicker(dateOptions);
+    $('#date_expired').daterangepicker(dateOptions);
     $("#id_assets").parent().find(".select2-selection").on('click', function (e) {
         if ($(e.target).attr('class') !== 'select2-selection__choice__remove'){
             e.preventDefault();
@@ -110,6 +119,6 @@ $(document).ready(function () {
         $('.select2').val(assets).trigger('change');
     });
     $("#asset_list_modal").modal('hide');
-})
+});
 </script>
 {% endblock %}
\ No newline at end of file
diff --git a/apps/static/css/plugins/daterangepicker/daterangepicker.css b/apps/static/css/plugins/daterangepicker/daterangepicker.css
new file mode 100644
index 000000000..d96809fa1
--- /dev/null
+++ b/apps/static/css/plugins/daterangepicker/daterangepicker.css
@@ -0,0 +1,388 @@
+.daterangepicker {
+  position: absolute;
+  color: inherit;
+  background-color: #fff;
+  border-radius: 4px;
+  border: 1px solid #ddd;
+  width: 278px;
+  max-width: none;
+  padding: 0;
+  margin-top: 7px;
+  top: 100px;
+  left: 20px;
+  z-index: 3001;
+  display: none;
+  font-family: arial;
+  font-size: 15px;
+  line-height: 1em;
+}
+
+.daterangepicker:before, .daterangepicker:after {
+  position: absolute;
+  display: inline-block;
+  border-bottom-color: rgba(0, 0, 0, 0.2);
+  content: '';
+}
+
+.daterangepicker:before {
+  top: -7px;
+  border-right: 7px solid transparent;
+  border-left: 7px solid transparent;
+  border-bottom: 7px solid #ccc;
+}
+
+.daterangepicker:after {
+  top: -6px;
+  border-right: 6px solid transparent;
+  border-bottom: 6px solid #fff;
+  border-left: 6px solid transparent;
+}
+
+.daterangepicker.opensleft:before {
+  right: 9px;
+}
+
+.daterangepicker.opensleft:after {
+  right: 10px;
+}
+
+.daterangepicker.openscenter:before {
+  left: 0;
+  right: 0;
+  width: 0;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+.daterangepicker.openscenter:after {
+  left: 0;
+  right: 0;
+  width: 0;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+.daterangepicker.opensright:before {
+  left: 9px;
+}
+
+.daterangepicker.opensright:after {
+  left: 10px;
+}
+
+.daterangepicker.drop-up {
+  margin-top: -7px;
+}
+
+.daterangepicker.drop-up:before {
+  top: initial;
+  bottom: -7px;
+  border-bottom: initial;
+  border-top: 7px solid #ccc;
+}
+
+.daterangepicker.drop-up:after {
+  top: initial;
+  bottom: -6px;
+  border-bottom: initial;
+  border-top: 6px solid #fff;
+}
+
+.daterangepicker.single .daterangepicker .ranges, .daterangepicker.single .drp-calendar {
+  float: none;
+}
+
+.daterangepicker.single .drp-selected {
+  display: none;
+}
+
+.daterangepicker.show-calendar .drp-calendar {
+  display: block;
+}
+
+.daterangepicker.show-calendar .drp-buttons {
+  display: block;
+}
+
+.daterangepicker.auto-apply .drp-buttons {
+  display: none;
+}
+
+.daterangepicker .drp-calendar {
+  display: none;
+  max-width: 270px;
+}
+
+.daterangepicker .drp-calendar.left {
+  padding: 8px 0 8px 8px;
+}
+
+.daterangepicker .drp-calendar.right {
+  padding: 8px;
+}
+
+.daterangepicker .drp-calendar.single .calendar-table {
+  border: none;
+}
+
+.daterangepicker .calendar-table .next span, .daterangepicker .calendar-table .prev span {
+  color: #fff;
+  border: solid black;
+  border-width: 0 2px 2px 0;
+  border-radius: 0;
+  display: inline-block;
+  padding: 3px;
+}
+
+.daterangepicker .calendar-table .next span {
+  transform: rotate(-45deg);
+  -webkit-transform: rotate(-45deg);
+}
+
+.daterangepicker .calendar-table .prev span {
+  transform: rotate(135deg);
+  -webkit-transform: rotate(135deg);
+}
+
+.daterangepicker .calendar-table th, .daterangepicker .calendar-table td {
+  white-space: nowrap;
+  text-align: center;
+  vertical-align: middle;
+  min-width: 32px;
+  width: 32px;
+  height: 24px;
+  line-height: 24px;
+  font-size: 12px;
+  border-radius: 4px;
+  border: 1px solid transparent;
+  white-space: nowrap;
+  cursor: pointer;
+}
+
+.daterangepicker .calendar-table {
+  border: 1px solid #fff;
+  border-radius: 4px;
+  background-color: #fff;
+}
+
+.daterangepicker .calendar-table table {
+  width: 100%;
+  margin: 0;
+  border-spacing: 0;
+  border-collapse: collapse;
+}
+
+.daterangepicker td.available:hover, .daterangepicker th.available:hover {
+  background-color: #eee;
+  border-color: transparent;
+  color: inherit;
+}
+
+.daterangepicker td.week, .daterangepicker th.week {
+  font-size: 80%;
+  color: #ccc;
+}
+
+.daterangepicker td.off, .daterangepicker td.off.in-range, .daterangepicker td.off.start-date, .daterangepicker td.off.end-date {
+  background-color: #fff;
+  border-color: transparent;
+  color: #999;
+}
+
+.daterangepicker td.in-range {
+  background-color: #ebf4f8;
+  border-color: transparent;
+  color: #000;
+  border-radius: 0;
+}
+
+.daterangepicker td.start-date {
+  border-radius: 4px 0 0 4px;
+}
+
+.daterangepicker td.end-date {
+  border-radius: 0 4px 4px 0;
+}
+
+.daterangepicker td.start-date.end-date {
+  border-radius: 4px;
+}
+
+.daterangepicker td.active, .daterangepicker td.active:hover {
+  background-color: #357ebd;
+  border-color: transparent;
+  color: #fff;
+}
+
+.daterangepicker th.month {
+  width: auto;
+}
+
+.daterangepicker td.disabled, .daterangepicker option.disabled {
+  color: #999;
+  cursor: not-allowed;
+  text-decoration: line-through;
+}
+
+.daterangepicker select.monthselect, .daterangepicker select.yearselect {
+  font-size: 12px;
+  padding: 1px;
+  height: auto;
+  margin: 0;
+  cursor: default;
+}
+
+.daterangepicker select.monthselect {
+  margin-right: 2%;
+  width: 56%;
+}
+
+.daterangepicker select.yearselect {
+  width: 40%;
+}
+
+.daterangepicker select.hourselect, .daterangepicker select.minuteselect, .daterangepicker select.secondselect, .daterangepicker select.ampmselect {
+  width: 50px;
+  margin: 0 auto;
+  background: #eee;
+  border: 1px solid #eee;
+  padding: 2px;
+  outline: 0;
+  font-size: 12px;
+}
+
+.daterangepicker .calendar-time {
+  text-align: center;
+  margin: 4px auto 0 auto;
+  line-height: 30px;
+  position: relative;
+}
+
+.daterangepicker .calendar-time select.disabled {
+  color: #ccc;
+  cursor: not-allowed;
+}
+
+.daterangepicker .drp-buttons {
+  clear: both;
+  text-align: right;
+  padding: 8px;
+  border-top: 1px solid #ddd;
+  display: none;
+  line-height: 12px;
+  vertical-align: middle;
+}
+
+.daterangepicker .drp-selected {
+  display: inline-block;
+  font-size: 12px;
+  padding-right: 8px;
+}
+
+.daterangepicker .drp-buttons .btn {
+  margin-left: 8px;
+  font-size: 12px;
+  font-weight: bold;
+  padding: 4px 8px;
+}
+
+.daterangepicker.show-ranges .drp-calendar.left {
+  border-left: 1px solid #ddd;
+}
+
+.daterangepicker .ranges {
+  float: none;
+  text-align: left;
+  margin: 0;
+}
+
+.daterangepicker.show-calendar .ranges {
+  margin-top: 8px;
+}
+
+.daterangepicker .ranges ul {
+  list-style: none;
+  margin: 0 auto;
+  padding: 0;
+  width: 100%;
+}
+
+.daterangepicker .ranges li {
+  font-size: 12px;
+  padding: 8px 12px;
+  cursor: pointer;
+}
+
+.daterangepicker .ranges li:hover {
+  background-color: #eee;
+}
+
+.daterangepicker .ranges li.active {
+  background-color: #08c;
+  color: #fff;
+}
+
+/*  Larger Screen Styling */
+@media (min-width: 564px) {
+  .daterangepicker {
+    width: auto; }
+    .daterangepicker .ranges ul {
+      width: 140px; }
+    .daterangepicker.single .ranges ul {
+      width: 100%; }
+    .daterangepicker.single .drp-calendar.left {
+      clear: none; }
+    .daterangepicker.single.ltr .ranges, .daterangepicker.single.ltr .drp-calendar {
+      float: left; }
+    .daterangepicker.single.rtl .ranges, .daterangepicker.single.rtl .drp-calendar {
+      float: right; }
+    .daterangepicker.ltr {
+      direction: ltr;
+      text-align: left; }
+      .daterangepicker.ltr .drp-calendar.left {
+        clear: left;
+        margin-right: 0; }
+        .daterangepicker.ltr .drp-calendar.left .calendar-table {
+          border-right: none;
+          border-top-right-radius: 0;
+          border-bottom-right-radius: 0; }
+      .daterangepicker.ltr .drp-calendar.right {
+        margin-left: 0; }
+        .daterangepicker.ltr .drp-calendar.right .calendar-table {
+          border-left: none;
+          border-top-left-radius: 0;
+          border-bottom-left-radius: 0; }
+      .daterangepicker.ltr .drp-calendar.left .calendar-table {
+        padding-right: 8px; }
+      .daterangepicker.ltr .ranges, .daterangepicker.ltr .drp-calendar {
+        float: left; }
+    .daterangepicker.rtl {
+      direction: rtl;
+      text-align: right; }
+      .daterangepicker.rtl .drp-calendar.left {
+        clear: right;
+        margin-left: 0; }
+        .daterangepicker.rtl .drp-calendar.left .calendar-table {
+          border-left: none;
+          border-top-left-radius: 0;
+          border-bottom-left-radius: 0; }
+      .daterangepicker.rtl .drp-calendar.right {
+        margin-right: 0; }
+        .daterangepicker.rtl .drp-calendar.right .calendar-table {
+          border-right: none;
+          border-top-right-radius: 0;
+          border-bottom-right-radius: 0; }
+      .daterangepicker.rtl .drp-calendar.left .calendar-table {
+        padding-left: 12px; }
+      .daterangepicker.rtl .ranges, .daterangepicker.rtl .drp-calendar {
+        text-align: right;
+        float: right; } }
+@media (min-width: 730px) {
+  .daterangepicker .ranges {
+    width: auto; }
+  .daterangepicker.ltr .ranges {
+    float: left; }
+  .daterangepicker.rtl .ranges {
+    float: right; }
+  .daterangepicker .drp-calendar.left {
+    clear: none !important; } }
\ No newline at end of file
diff --git a/apps/static/js/plugins/daterangepicker/daterangepicker.min.js b/apps/static/js/plugins/daterangepicker/daterangepicker.min.js
new file mode 100644
index 000000000..32b0e7571
--- /dev/null
+++ b/apps/static/js/plugins/daterangepicker/daterangepicker.min.js
@@ -0,0 +1,8 @@
+/**
+ * Minified by jsDelivr using UglifyJS v3.4.5.
+ * Original file: /npm/daterangepicker@3.0.3/daterangepicker.js
+ *
+ * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
+ */
+!function(t,a){if("function"==typeof define&&define.amd)define(["moment","jquery"],function(t,e){return e.fn||(e.fn={}),a(t,e)});else if("object"==typeof module&&module.exports){var e="undefined"!=typeof window?window.jQuery:void 0;e||(e=require("jquery")).fn||(e.fn={});var i="undefined"!=typeof window&&void 0!==window.moment?window.moment:require("moment");module.exports=a(i,e)}else t.daterangepicker=a(t.moment,t.jQuery)}(this,function(H,R){var i=function(t,e,a){if(this.parentEl="body",this.element=R(t),this.startDate=H().startOf("day"),this.endDate=H().endOf("day"),this.minDate=!1,this.maxDate=!1,this.maxSpan=!1,this.autoApply=!1,this.singleDatePicker=!1,this.showDropdowns=!1,this.minYear=H().subtract(100,"year").format("YYYY"),this.maxYear=H().add(100,"year").format("YYYY"),this.showWeekNumbers=!1,this.showISOWeekNumbers=!1,this.showCustomRangeLabel=!0,this.timePicker=!1,this.timePicker24Hour=!1,this.timePickerIncrement=1,this.timePickerSeconds=!1,this.linkedCalendars=!0,this.autoUpdateInput=!0,this.alwaysShowCalendars=!1,this.ranges={},this.opens="right",this.element.hasClass("pull-right")&&(this.opens="left"),this.drops="down",this.element.hasClass("dropup")&&(this.drops="up"),this.buttonClasses="btn btn-sm",this.applyButtonClasses="btn-primary",this.cancelButtonClasses="btn-default",this.locale={direction:"ltr",format:H.localeData().longDateFormat("L"),separator:" - ",applyLabel:"Apply",cancelLabel:"Cancel",weekLabel:"W",customRangeLabel:"Custom Range",daysOfWeek:H.weekdaysMin(),monthNames:H.monthsShort(),firstDay:H.localeData().firstDayOfWeek()},this.callback=function(){},this.isShowing=!1,this.leftCalendar={},this.rightCalendar={},"object"==typeof e&&null!==e||(e={}),"string"==typeof(e=R.extend(this.element.data(),e)).template||e.template instanceof R||(e.template='<div class="daterangepicker"><div class="ranges"></div><div class="drp-calendar left"><div class="calendar-table"></div><div class="calendar-time"></div></div><div class="drp-calendar right"><div class="calendar-table"></div><div class="calendar-time"></div></div><div class="drp-buttons"><span class="drp-selected"></span><button class="cancelBtn" type="button"></button><button class="applyBtn" disabled="disabled" type="button"></button> </div></div>'),this.parentEl=e.parentEl&&R(e.parentEl).length?R(e.parentEl):R(this.parentEl),this.container=R(e.template).appendTo(this.parentEl),"object"==typeof e.locale&&("string"==typeof e.locale.direction&&(this.locale.direction=e.locale.direction),"string"==typeof e.locale.format&&(this.locale.format=e.locale.format),"string"==typeof e.locale.separator&&(this.locale.separator=e.locale.separator),"object"==typeof e.locale.daysOfWeek&&(this.locale.daysOfWeek=e.locale.daysOfWeek.slice()),"object"==typeof e.locale.monthNames&&(this.locale.monthNames=e.locale.monthNames.slice()),"number"==typeof e.locale.firstDay&&(this.locale.firstDay=e.locale.firstDay),"string"==typeof e.locale.applyLabel&&(this.locale.applyLabel=e.locale.applyLabel),"string"==typeof e.locale.cancelLabel&&(this.locale.cancelLabel=e.locale.cancelLabel),"string"==typeof e.locale.weekLabel&&(this.locale.weekLabel=e.locale.weekLabel),"string"==typeof e.locale.customRangeLabel)){(d=document.createElement("textarea")).innerHTML=e.locale.customRangeLabel;var i=d.value;this.locale.customRangeLabel=i}if(this.container.addClass(this.locale.direction),"string"==typeof e.startDate&&(this.startDate=H(e.startDate,this.locale.format)),"string"==typeof e.endDate&&(this.endDate=H(e.endDate,this.locale.format)),"string"==typeof e.minDate&&(this.minDate=H(e.minDate,this.locale.format)),"string"==typeof e.maxDate&&(this.maxDate=H(e.maxDate,this.locale.format)),"object"==typeof e.startDate&&(this.startDate=H(e.startDate)),"object"==typeof e.endDate&&(this.endDate=H(e.endDate)),"object"==typeof e.minDate&&(this.minDate=H(e.minDate)),"object"==typeof e.maxDate&&(this.maxDate=H(e.maxDate)),this.minDate&&this.startDate.isBefore(this.minDate)&&(this.startDate=this.minDate.clone()),this.maxDate&&this.endDate.isAfter(this.maxDate)&&(this.endDate=this.maxDate.clone()),"string"==typeof e.applyButtonClasses&&(this.applyButtonClasses=e.applyButtonClasses),"string"==typeof e.applyClass&&(this.applyButtonClasses=e.applyClass),"string"==typeof e.cancelButtonClasses&&(this.cancelButtonClasses=e.cancelButtonClasses),"string"==typeof e.cancelClass&&(this.cancelButtonClasses=e.cancelClass),"object"==typeof e.maxSpan&&(this.maxSpan=e.maxSpan),"object"==typeof e.dateLimit&&(this.maxSpan=e.dateLimit),"string"==typeof e.opens&&(this.opens=e.opens),"string"==typeof e.drops&&(this.drops=e.drops),"boolean"==typeof e.showWeekNumbers&&(this.showWeekNumbers=e.showWeekNumbers),"boolean"==typeof e.showISOWeekNumbers&&(this.showISOWeekNumbers=e.showISOWeekNumbers),"string"==typeof e.buttonClasses&&(this.buttonClasses=e.buttonClasses),"object"==typeof e.buttonClasses&&(this.buttonClasses=e.buttonClasses.join(" ")),"boolean"==typeof e.showDropdowns&&(this.showDropdowns=e.showDropdowns),"number"==typeof e.minYear&&(this.minYear=e.minYear),"number"==typeof e.maxYear&&(this.maxYear=e.maxYear),"boolean"==typeof e.showCustomRangeLabel&&(this.showCustomRangeLabel=e.showCustomRangeLabel),"boolean"==typeof e.singleDatePicker&&(this.singleDatePicker=e.singleDatePicker,this.singleDatePicker&&(this.endDate=this.startDate.clone())),"boolean"==typeof e.timePicker&&(this.timePicker=e.timePicker),"boolean"==typeof e.timePickerSeconds&&(this.timePickerSeconds=e.timePickerSeconds),"number"==typeof e.timePickerIncrement&&(this.timePickerIncrement=e.timePickerIncrement),"boolean"==typeof e.timePicker24Hour&&(this.timePicker24Hour=e.timePicker24Hour),"boolean"==typeof e.autoApply&&(this.autoApply=e.autoApply),"boolean"==typeof e.autoUpdateInput&&(this.autoUpdateInput=e.autoUpdateInput),"boolean"==typeof e.linkedCalendars&&(this.linkedCalendars=e.linkedCalendars),"function"==typeof e.isInvalidDate&&(this.isInvalidDate=e.isInvalidDate),"function"==typeof e.isCustomDate&&(this.isCustomDate=e.isCustomDate),"boolean"==typeof e.alwaysShowCalendars&&(this.alwaysShowCalendars=e.alwaysShowCalendars),0!=this.locale.firstDay)for(var s=this.locale.firstDay;0<s;)this.locale.daysOfWeek.push(this.locale.daysOfWeek.shift()),s--;var n,r,o;if(void 0===e.startDate&&void 0===e.endDate&&R(this.element).is(":text")){var h=R(this.element).val(),l=h.split(this.locale.separator);n=r=null,2==l.length?(n=H(l[0],this.locale.format),r=H(l[1],this.locale.format)):this.singleDatePicker&&""!==h&&(n=H(h,this.locale.format),r=H(h,this.locale.format)),null!==n&&null!==r&&(this.setStartDate(n),this.setEndDate(r))}if("object"==typeof e.ranges){for(o in e.ranges){n="string"==typeof e.ranges[o][0]?H(e.ranges[o][0],this.locale.format):H(e.ranges[o][0]),r="string"==typeof e.ranges[o][1]?H(e.ranges[o][1],this.locale.format):H(e.ranges[o][1]),this.minDate&&n.isBefore(this.minDate)&&(n=this.minDate.clone());var c=this.maxDate;if(this.maxSpan&&c&&n.clone().add(this.maxSpan).isAfter(c)&&(c=n.clone().add(this.maxSpan)),c&&r.isAfter(c)&&(r=c.clone()),!(this.minDate&&r.isBefore(this.minDate,this.timepicker?"minute":"day")||c&&n.isAfter(c,this.timepicker?"minute":"day"))){var d;(d=document.createElement("textarea")).innerHTML=o;i=d.value;this.ranges[i]=[n,r]}}var m="<ul>";for(o in this.ranges)m+='<li data-range-key="'+o+'">'+o+"</li>";this.showCustomRangeLabel&&(m+='<li data-range-key="'+this.locale.customRangeLabel+'">'+this.locale.customRangeLabel+"</li>"),m+="</ul>",this.container.find(".ranges").prepend(m)}"function"==typeof a&&(this.callback=a),this.timePicker||(this.startDate=this.startDate.startOf("day"),this.endDate=this.endDate.endOf("day"),this.container.find(".calendar-time").hide()),this.timePicker&&this.autoApply&&(this.autoApply=!1),this.autoApply&&this.container.addClass("auto-apply"),"object"==typeof e.ranges&&this.container.addClass("show-ranges"),this.singleDatePicker&&(this.container.addClass("single"),this.container.find(".drp-calendar.left").addClass("single"),this.container.find(".drp-calendar.left").show(),this.container.find(".drp-calendar.right").hide(),this.timePicker||this.container.addClass("auto-apply")),(void 0===e.ranges&&!this.singleDatePicker||this.alwaysShowCalendars)&&this.container.addClass("show-calendar"),this.container.addClass("opens"+this.opens),this.container.find(".applyBtn, .cancelBtn").addClass(this.buttonClasses),this.applyButtonClasses.length&&this.container.find(".applyBtn").addClass(this.applyButtonClasses),this.cancelButtonClasses.length&&this.container.find(".cancelBtn").addClass(this.cancelButtonClasses),this.container.find(".applyBtn").html(this.locale.applyLabel),this.container.find(".cancelBtn").html(this.locale.cancelLabel),this.container.find(".drp-calendar").on("click.daterangepicker",".prev",R.proxy(this.clickPrev,this)).on("click.daterangepicker",".next",R.proxy(this.clickNext,this)).on("mousedown.daterangepicker","td.available",R.proxy(this.clickDate,this)).on("mouseenter.daterangepicker","td.available",R.proxy(this.hoverDate,this)).on("change.daterangepicker","select.yearselect",R.proxy(this.monthOrYearChanged,this)).on("change.daterangepicker","select.monthselect",R.proxy(this.monthOrYearChanged,this)).on("change.daterangepicker","select.hourselect,select.minuteselect,select.secondselect,select.ampmselect",R.proxy(this.timeChanged,this)),this.container.find(".ranges").on("click.daterangepicker","li",R.proxy(this.clickRange,this)),this.container.find(".drp-buttons").on("click.daterangepicker","button.applyBtn",R.proxy(this.clickApply,this)).on("click.daterangepicker","button.cancelBtn",R.proxy(this.clickCancel,this)),this.element.is("input")||this.element.is("button")?this.element.on({"click.daterangepicker":R.proxy(this.show,this),"focus.daterangepicker":R.proxy(this.show,this),"keyup.daterangepicker":R.proxy(this.elementChanged,this),"keydown.daterangepicker":R.proxy(this.keydown,this)}):(this.element.on("click.daterangepicker",R.proxy(this.toggle,this)),this.element.on("keydown.daterangepicker",R.proxy(this.toggle,this))),this.updateElement()};return i.prototype={constructor:i,setStartDate:function(t){"string"==typeof t&&(this.startDate=H(t,this.locale.format)),"object"==typeof t&&(this.startDate=H(t)),this.timePicker||(this.startDate=this.startDate.startOf("day")),this.timePicker&&this.timePickerIncrement&&this.startDate.minute(Math.round(this.startDate.minute()/this.timePickerIncrement)*this.timePickerIncrement),this.minDate&&this.startDate.isBefore(this.minDate)&&(this.startDate=this.minDate.clone(),this.timePicker&&this.timePickerIncrement&&this.startDate.minute(Math.round(this.startDate.minute()/this.timePickerIncrement)*this.timePickerIncrement)),this.maxDate&&this.startDate.isAfter(this.maxDate)&&(this.startDate=this.maxDate.clone(),this.timePicker&&this.timePickerIncrement&&this.startDate.minute(Math.floor(this.startDate.minute()/this.timePickerIncrement)*this.timePickerIncrement)),this.isShowing||this.updateElement(),this.updateMonthsInView()},setEndDate:function(t){"string"==typeof t&&(this.endDate=H(t,this.locale.format)),"object"==typeof t&&(this.endDate=H(t)),this.timePicker||(this.endDate=this.endDate.add(1,"d").startOf("day").subtract(1,"second")),this.timePicker&&this.timePickerIncrement&&this.endDate.minute(Math.round(this.endDate.minute()/this.timePickerIncrement)*this.timePickerIncrement),this.endDate.isBefore(this.startDate)&&(this.endDate=this.startDate.clone()),this.maxDate&&this.endDate.isAfter(this.maxDate)&&(this.endDate=this.maxDate.clone()),this.maxSpan&&this.startDate.clone().add(this.maxSpan).isBefore(this.endDate)&&(this.endDate=this.startDate.clone().add(this.maxSpan)),this.previousRightTime=this.endDate.clone(),this.container.find(".drp-selected").html(this.startDate.format(this.locale.format)+this.locale.separator+this.endDate.format(this.locale.format)),this.isShowing||this.updateElement(),this.updateMonthsInView()},isInvalidDate:function(){return!1},isCustomDate:function(){return!1},updateView:function(){this.timePicker&&(this.renderTimePicker("left"),this.renderTimePicker("right"),this.endDate?this.container.find(".right .calendar-time select").removeAttr("disabled").removeClass("disabled"):this.container.find(".right .calendar-time select").attr("disabled","disabled").addClass("disabled")),this.endDate&&this.container.find(".drp-selected").html(this.startDate.format(this.locale.format)+this.locale.separator+this.endDate.format(this.locale.format)),this.updateMonthsInView(),this.updateCalendars(),this.updateFormInputs()},updateMonthsInView:function(){if(this.endDate){if(!this.singleDatePicker&&this.leftCalendar.month&&this.rightCalendar.month&&(this.startDate.format("YYYY-MM")==this.leftCalendar.month.format("YYYY-MM")||this.startDate.format("YYYY-MM")==this.rightCalendar.month.format("YYYY-MM"))&&(this.endDate.format("YYYY-MM")==this.leftCalendar.month.format("YYYY-MM")||this.endDate.format("YYYY-MM")==this.rightCalendar.month.format("YYYY-MM")))return;this.leftCalendar.month=this.startDate.clone().date(2),this.linkedCalendars||this.endDate.month()==this.startDate.month()&&this.endDate.year()==this.startDate.year()?this.rightCalendar.month=this.startDate.clone().date(2).add(1,"month"):this.rightCalendar.month=this.endDate.clone().date(2)}else this.leftCalendar.month.format("YYYY-MM")!=this.startDate.format("YYYY-MM")&&this.rightCalendar.month.format("YYYY-MM")!=this.startDate.format("YYYY-MM")&&(this.leftCalendar.month=this.startDate.clone().date(2),this.rightCalendar.month=this.startDate.clone().date(2).add(1,"month"));this.maxDate&&this.linkedCalendars&&!this.singleDatePicker&&this.rightCalendar.month>this.maxDate&&(this.rightCalendar.month=this.maxDate.clone().date(2),this.leftCalendar.month=this.maxDate.clone().date(2).subtract(1,"month"))},updateCalendars:function(){if(this.timePicker){var t,e,a,i;if(this.endDate){if(t=parseInt(this.container.find(".left .hourselect").val(),10),e=parseInt(this.container.find(".left .minuteselect").val(),10),a=this.timePickerSeconds?parseInt(this.container.find(".left .secondselect").val(),10):0,!this.timePicker24Hour)"PM"===(i=this.container.find(".left .ampmselect").val())&&t<12&&(t+=12),"AM"===i&&12===t&&(t=0)}else if(t=parseInt(this.container.find(".right .hourselect").val(),10),e=parseInt(this.container.find(".right .minuteselect").val(),10),a=this.timePickerSeconds?parseInt(this.container.find(".right .secondselect").val(),10):0,!this.timePicker24Hour)"PM"===(i=this.container.find(".right .ampmselect").val())&&t<12&&(t+=12),"AM"===i&&12===t&&(t=0);this.leftCalendar.month.hour(t).minute(e).second(a),this.rightCalendar.month.hour(t).minute(e).second(a)}this.renderCalendar("left"),this.renderCalendar("right"),this.container.find(".ranges li").removeClass("active"),null!=this.endDate&&this.calculateChosenLabel()},renderCalendar:function(t){var e,a=(e="left"==t?this.leftCalendar:this.rightCalendar).month.month(),i=e.month.year(),s=e.month.hour(),n=e.month.minute(),r=e.month.second(),o=H([i,a]).daysInMonth(),h=H([i,a,1]),l=H([i,a,o]),c=H(h).subtract(1,"month").month(),d=H(h).subtract(1,"month").year(),m=H([d,c]).daysInMonth(),f=h.day();(e=[]).firstDay=h,e.lastDay=l;for(var p=0;p<6;p++)e[p]=[];var u=m-f+this.locale.firstDay+1;m<u&&(u-=7),f==this.locale.firstDay&&(u=m-6);for(var D=H([d,c,u,12,n,r]),g=(p=0,0),y=0;p<42;p++,g++,D=H(D).add(24,"hour"))0<p&&g%7==0&&(g=0,y++),e[y][g]=D.clone().hour(s).minute(n).second(r),D.hour(12),this.minDate&&e[y][g].format("YYYY-MM-DD")==this.minDate.format("YYYY-MM-DD")&&e[y][g].isBefore(this.minDate)&&"left"==t&&(e[y][g]=this.minDate.clone()),this.maxDate&&e[y][g].format("YYYY-MM-DD")==this.maxDate.format("YYYY-MM-DD")&&e[y][g].isAfter(this.maxDate)&&"right"==t&&(e[y][g]=this.maxDate.clone());"left"==t?this.leftCalendar.calendar=e:this.rightCalendar.calendar=e;var k="left"==t?this.minDate:this.startDate,b=this.maxDate,C=("left"==t?this.startDate:this.endDate,this.locale.direction,'<table class="table-condensed">');C+="<thead>",C+="<tr>",(this.showWeekNumbers||this.showISOWeekNumbers)&&(C+="<th></th>"),k&&!k.isBefore(e.firstDay)||this.linkedCalendars&&"left"!=t?C+="<th></th>":C+='<th class="prev available"><span></span></th>';var v=this.locale.monthNames[e[1][1].month()]+e[1][1].format(" YYYY");if(this.showDropdowns){for(var Y=e[1][1].month(),w=e[1][1].year(),P=b&&b.year()||this.maxYear,x=k&&k.year()||this.minYear,M=w==x,S=w==P,I='<select class="monthselect">',B=0;B<12;B++)(!M||B>=k.month())&&(!S||B<=b.month())?I+="<option value='"+B+"'"+(B===Y?" selected='selected'":"")+">"+this.locale.monthNames[B]+"</option>":I+="<option value='"+B+"'"+(B===Y?" selected='selected'":"")+" disabled='disabled'>"+this.locale.monthNames[B]+"</option>";I+="</select>";for(var A='<select class="yearselect">',L=x;L<=P;L++)A+='<option value="'+L+'"'+(L===w?' selected="selected"':"")+">"+L+"</option>";v=I+(A+="</select>")}if(C+='<th colspan="5" class="month">'+v+"</th>",b&&!b.isAfter(e.lastDay)||this.linkedCalendars&&"right"!=t&&!this.singleDatePicker?C+="<th></th>":C+='<th class="next available"><span></span></th>',C+="</tr>",C+="<tr>",(this.showWeekNumbers||this.showISOWeekNumbers)&&(C+='<th class="week">'+this.locale.weekLabel+"</th>"),R.each(this.locale.daysOfWeek,function(t,e){C+="<th>"+e+"</th>"}),C+="</tr>",C+="</thead>",C+="<tbody>",null==this.endDate&&this.maxSpan){var E=this.startDate.clone().add(this.maxSpan).endOf("day");b&&!E.isBefore(b)||(b=E)}for(y=0;y<6;y++){C+="<tr>",this.showWeekNumbers?C+='<td class="week">'+e[y][0].week()+"</td>":this.showISOWeekNumbers&&(C+='<td class="week">'+e[y][0].isoWeek()+"</td>");for(g=0;g<7;g++){var W=[];e[y][g].isSame(new Date,"day")&&W.push("today"),5<e[y][g].isoWeekday()&&W.push("weekend"),e[y][g].month()!=e[1][1].month()&&W.push("off"),this.minDate&&e[y][g].isBefore(this.minDate,"day")&&W.push("off","disabled"),b&&e[y][g].isAfter(b,"day")&&W.push("off","disabled"),this.isInvalidDate(e[y][g])&&W.push("off","disabled"),e[y][g].format("YYYY-MM-DD")==this.startDate.format("YYYY-MM-DD")&&W.push("active","start-date"),null!=this.endDate&&e[y][g].format("YYYY-MM-DD")==this.endDate.format("YYYY-MM-DD")&&W.push("active","end-date"),null!=this.endDate&&e[y][g]>this.startDate&&e[y][g]<this.endDate&&W.push("in-range");var O=this.isCustomDate(e[y][g]);!1!==O&&("string"==typeof O?W.push(O):Array.prototype.push.apply(W,O));var N="",j=!1;for(p=0;p<W.length;p++)N+=W[p]+" ","disabled"==W[p]&&(j=!0);j||(N+="available"),C+='<td class="'+N.replace(/^\s+|\s+$/g,"")+'" data-title="r'+y+"c"+g+'">'+e[y][g].date()+"</td>"}C+="</tr>"}C+="</tbody>",C+="</table>",this.container.find(".drp-calendar."+t+" .calendar-table").html(C)},renderTimePicker:function(t){if("right"!=t||this.endDate){var e,a,i,s=this.maxDate;if(!this.maxSpan||this.maxDate&&!this.startDate.clone().add(this.maxSpan).isAfter(this.maxDate)||(s=this.startDate.clone().add(this.maxSpan)),"left"==t)a=this.startDate.clone(),i=this.minDate;else if("right"==t){a=this.endDate.clone(),i=this.startDate;var n=this.container.find(".drp-calendar.right .calendar-time");if(""!=n.html()&&(a.hour(a.hour()||n.find(".hourselect option:selected").val()),a.minute(a.minute()||n.find(".minuteselect option:selected").val()),a.second(a.second()||n.find(".secondselect option:selected").val()),!this.timePicker24Hour)){var r=n.find(".ampmselect option:selected").val();"PM"===r&&a.hour()<12&&a.hour(a.hour()+12),"AM"===r&&12===a.hour()&&a.hour(0)}a.isBefore(this.startDate)&&(a=this.startDate.clone()),s&&a.isAfter(s)&&(a=s.clone())}e='<select class="hourselect">';for(var o=this.timePicker24Hour?0:1,h=this.timePicker24Hour?23:12,l=o;l<=h;l++){var c=l;this.timePicker24Hour||(c=12<=a.hour()?12==l?12:l+12:12==l?0:l);var d=a.clone().hour(c),m=!1;i&&d.minute(59).isBefore(i)&&(m=!0),s&&d.minute(0).isAfter(s)&&(m=!0),c!=a.hour()||m?e+=m?'<option value="'+l+'" disabled="disabled" class="disabled">'+l+"</option>":'<option value="'+l+'">'+l+"</option>":e+='<option value="'+l+'" selected="selected">'+l+"</option>"}e+="</select> ",e+=': <select class="minuteselect">';for(l=0;l<60;l+=this.timePickerIncrement){var f=l<10?"0"+l:l;d=a.clone().minute(l),m=!1;i&&d.second(59).isBefore(i)&&(m=!0),s&&d.second(0).isAfter(s)&&(m=!0),a.minute()!=l||m?e+=m?'<option value="'+l+'" disabled="disabled" class="disabled">'+f+"</option>":'<option value="'+l+'">'+f+"</option>":e+='<option value="'+l+'" selected="selected">'+f+"</option>"}if(e+="</select> ",this.timePickerSeconds){e+=': <select class="secondselect">';for(l=0;l<60;l++){f=l<10?"0"+l:l,d=a.clone().second(l),m=!1;i&&d.isBefore(i)&&(m=!0),s&&d.isAfter(s)&&(m=!0),a.second()!=l||m?e+=m?'<option value="'+l+'" disabled="disabled" class="disabled">'+f+"</option>":'<option value="'+l+'">'+f+"</option>":e+='<option value="'+l+'" selected="selected">'+f+"</option>"}e+="</select> "}if(!this.timePicker24Hour){e+='<select class="ampmselect">';var p="",u="";i&&a.clone().hour(12).minute(0).second(0).isBefore(i)&&(p=' disabled="disabled" class="disabled"'),s&&a.clone().hour(0).minute(0).second(0).isAfter(s)&&(u=' disabled="disabled" class="disabled"'),12<=a.hour()?e+='<option value="AM"'+p+'>AM</option><option value="PM" selected="selected"'+u+">PM</option>":e+='<option value="AM" selected="selected"'+p+'>AM</option><option value="PM"'+u+">PM</option>",e+="</select>"}this.container.find(".drp-calendar."+t+" .calendar-time").html(e)}},updateFormInputs:function(){this.singleDatePicker||this.endDate&&(this.startDate.isBefore(this.endDate)||this.startDate.isSame(this.endDate))?this.container.find("button.applyBtn").removeAttr("disabled"):this.container.find("button.applyBtn").attr("disabled","disabled")},move:function(){var t,e={top:0,left:0},a=R(window).width();this.parentEl.is("body")||(e={top:this.parentEl.offset().top-this.parentEl.scrollTop(),left:this.parentEl.offset().left-this.parentEl.scrollLeft()},a=this.parentEl[0].clientWidth+this.parentEl.offset().left),t="up"==this.drops?this.element.offset().top-this.container.outerHeight()-e.top:this.element.offset().top+this.element.outerHeight()-e.top,this.container["up"==this.drops?"addClass":"removeClass"]("drop-up"),"left"==this.opens?(this.container.css({top:t,right:a-this.element.offset().left-this.element.outerWidth(),left:"auto"}),this.container.offset().left<0&&this.container.css({right:"auto",left:9})):"center"==this.opens?(this.container.css({top:t,left:this.element.offset().left-e.left+this.element.outerWidth()/2-this.container.outerWidth()/2,right:"auto"}),this.container.offset().left<0&&this.container.css({right:"auto",left:9})):(this.container.css({top:t,left:this.element.offset().left-e.left,right:"auto"}),this.container.offset().left+this.container.outerWidth()>R(window).width()&&this.container.css({left:"auto",right:0}))},show:function(t){this.isShowing||(this._outsideClickProxy=R.proxy(function(t){this.outsideClick(t)},this),R(document).on("mousedown.daterangepicker",this._outsideClickProxy).on("touchend.daterangepicker",this._outsideClickProxy).on("click.daterangepicker","[data-toggle=dropdown]",this._outsideClickProxy).on("focusin.daterangepicker",this._outsideClickProxy),R(window).on("resize.daterangepicker",R.proxy(function(t){this.move(t)},this)),this.oldStartDate=this.startDate.clone(),this.oldEndDate=this.endDate.clone(),this.previousRightTime=this.endDate.clone(),this.updateView(),this.container.show(),this.move(),this.element.trigger("show.daterangepicker",this),this.isShowing=!0)},hide:function(t){this.isShowing&&(this.endDate||(this.startDate=this.oldStartDate.clone(),this.endDate=this.oldEndDate.clone()),this.startDate.isSame(this.oldStartDate)&&this.endDate.isSame(this.oldEndDate)||this.callback(this.startDate.clone(),this.endDate.clone(),this.chosenLabel),this.updateElement(),R(document).off(".daterangepicker"),R(window).off(".daterangepicker"),this.container.hide(),this.element.trigger("hide.daterangepicker",this),this.isShowing=!1)},toggle:function(t){this.isShowing?this.hide():this.show()},outsideClick:function(t){var e=R(t.target);"focusin"==t.type||e.closest(this.element).length||e.closest(this.container).length||e.closest(".calendar-table").length||(this.hide(),this.element.trigger("outsideClick.daterangepicker",this))},showCalendars:function(){this.container.addClass("show-calendar"),this.move(),this.element.trigger("showCalendar.daterangepicker",this)},hideCalendars:function(){this.container.removeClass("show-calendar"),this.element.trigger("hideCalendar.daterangepicker",this)},clickRange:function(t){var e=t.target.getAttribute("data-range-key");if((this.chosenLabel=e)==this.locale.customRangeLabel)this.showCalendars();else{var a=this.ranges[e];this.startDate=a[0],this.endDate=a[1],this.timePicker||(this.startDate.startOf("day"),this.endDate.endOf("day")),this.alwaysShowCalendars||this.hideCalendars(),this.clickApply()}},clickPrev:function(t){R(t.target).parents(".drp-calendar").hasClass("left")?(this.leftCalendar.month.subtract(1,"month"),this.linkedCalendars&&this.rightCalendar.month.subtract(1,"month")):this.rightCalendar.month.subtract(1,"month"),this.updateCalendars()},clickNext:function(t){R(t.target).parents(".drp-calendar").hasClass("left")?this.leftCalendar.month.add(1,"month"):(this.rightCalendar.month.add(1,"month"),this.linkedCalendars&&this.leftCalendar.month.add(1,"month")),this.updateCalendars()},hoverDate:function(t){if(R(t.target).hasClass("available")){var e=R(t.target).attr("data-title"),a=e.substr(1,1),i=e.substr(3,1),r=R(t.target).parents(".drp-calendar").hasClass("left")?this.leftCalendar.calendar[a][i]:this.rightCalendar.calendar[a][i],o=this.leftCalendar,h=this.rightCalendar,l=this.startDate;this.endDate||this.container.find(".drp-calendar tbody td").each(function(t,e){if(!R(e).hasClass("week")){var a=R(e).attr("data-title"),i=a.substr(1,1),s=a.substr(3,1),n=R(e).parents(".drp-calendar").hasClass("left")?o.calendar[i][s]:h.calendar[i][s];n.isAfter(l)&&n.isBefore(r)||n.isSame(r,"day")?R(e).addClass("in-range"):R(e).removeClass("in-range")}})}},clickDate:function(t){if(R(t.target).hasClass("available")){var e=R(t.target).attr("data-title"),a=e.substr(1,1),i=e.substr(3,1),s=R(t.target).parents(".drp-calendar").hasClass("left")?this.leftCalendar.calendar[a][i]:this.rightCalendar.calendar[a][i];if(this.endDate||s.isBefore(this.startDate,"day")){if(this.timePicker){var n=parseInt(this.container.find(".left .hourselect").val(),10);if(!this.timePicker24Hour)"PM"===(h=this.container.find(".left .ampmselect").val())&&n<12&&(n+=12),"AM"===h&&12===n&&(n=0);var r=parseInt(this.container.find(".left .minuteselect").val(),10),o=this.timePickerSeconds?parseInt(this.container.find(".left .secondselect").val(),10):0;s=s.clone().hour(n).minute(r).second(o)}this.endDate=null,this.setStartDate(s.clone())}else if(!this.endDate&&s.isBefore(this.startDate))this.setEndDate(this.startDate.clone());else{if(this.timePicker){var h;n=parseInt(this.container.find(".right .hourselect").val(),10);if(!this.timePicker24Hour)"PM"===(h=this.container.find(".right .ampmselect").val())&&n<12&&(n+=12),"AM"===h&&12===n&&(n=0);r=parseInt(this.container.find(".right .minuteselect").val(),10),o=this.timePickerSeconds?parseInt(this.container.find(".right .secondselect").val(),10):0;s=s.clone().hour(n).minute(r).second(o)}this.setEndDate(s.clone()),this.autoApply&&(this.calculateChosenLabel(),this.clickApply())}this.singleDatePicker&&(this.setEndDate(this.startDate),this.timePicker||this.clickApply()),this.updateView(),t.stopPropagation()}},calculateChosenLabel:function(){var t=!0,e=0;for(var a in this.ranges){if(this.timePicker){var i=this.timePickerSeconds?"YYYY-MM-DD hh:mm:ss":"YYYY-MM-DD hh:mm";if(this.startDate.format(i)==this.ranges[a][0].format(i)&&this.endDate.format(i)==this.ranges[a][1].format(i)){t=!1,this.chosenLabel=this.container.find(".ranges li:eq("+e+")").addClass("active").attr("data-range-key");break}}else if(this.startDate.format("YYYY-MM-DD")==this.ranges[a][0].format("YYYY-MM-DD")&&this.endDate.format("YYYY-MM-DD")==this.ranges[a][1].format("YYYY-MM-DD")){t=!1,this.chosenLabel=this.container.find(".ranges li:eq("+e+")").addClass("active").attr("data-range-key");break}e++}t&&(this.showCustomRangeLabel?this.chosenLabel=this.container.find(".ranges li:last").addClass("active").attr("data-range-key"):this.chosenLabel=null,this.showCalendars())},clickApply:function(t){this.hide(),this.element.trigger("apply.daterangepicker",this)},clickCancel:function(t){this.startDate=this.oldStartDate,this.endDate=this.oldEndDate,this.hide(),this.element.trigger("cancel.daterangepicker",this)},monthOrYearChanged:function(t){var e=R(t.target).closest(".drp-calendar").hasClass("left"),a=e?"left":"right",i=this.container.find(".drp-calendar."+a),s=parseInt(i.find(".monthselect").val(),10),n=i.find(".yearselect").val();e||(n<this.startDate.year()||n==this.startDate.year()&&s<this.startDate.month())&&(s=this.startDate.month(),n=this.startDate.year()),this.minDate&&(n<this.minDate.year()||n==this.minDate.year()&&s<this.minDate.month())&&(s=this.minDate.month(),n=this.minDate.year()),this.maxDate&&(n>this.maxDate.year()||n==this.maxDate.year()&&s>this.maxDate.month())&&(s=this.maxDate.month(),n=this.maxDate.year()),e?(this.leftCalendar.month.month(s).year(n),this.linkedCalendars&&(this.rightCalendar.month=this.leftCalendar.month.clone().add(1,"month"))):(this.rightCalendar.month.month(s).year(n),this.linkedCalendars&&(this.leftCalendar.month=this.rightCalendar.month.clone().subtract(1,"month"))),this.updateCalendars()},timeChanged:function(t){var e=R(t.target).closest(".drp-calendar"),a=e.hasClass("left"),i=parseInt(e.find(".hourselect").val(),10),s=parseInt(e.find(".minuteselect").val(),10),n=this.timePickerSeconds?parseInt(e.find(".secondselect").val(),10):0;if(!this.timePicker24Hour){var r=e.find(".ampmselect").val();"PM"===r&&i<12&&(i+=12),"AM"===r&&12===i&&(i=0)}if(a){var o=this.startDate.clone();o.hour(i),o.minute(s),o.second(n),this.setStartDate(o),this.singleDatePicker?this.endDate=this.startDate.clone():this.endDate&&this.endDate.format("YYYY-MM-DD")==o.format("YYYY-MM-DD")&&this.endDate.isBefore(o)&&this.setEndDate(o.clone())}else if(this.endDate){var h=this.endDate.clone();h.hour(i),h.minute(s),h.second(n),this.setEndDate(h)}this.updateCalendars(),this.updateFormInputs(),this.renderTimePicker("left"),this.renderTimePicker("right")},elementChanged:function(){if(this.element.is("input")&&this.element.val().length){var t=this.element.val().split(this.locale.separator),e=null,a=null;2===t.length&&(e=H(t[0],this.locale.format),a=H(t[1],this.locale.format)),(this.singleDatePicker||null===e||null===a)&&(a=e=H(this.element.val(),this.locale.format)),e.isValid()&&a.isValid()&&(this.setStartDate(e),this.setEndDate(a),this.updateView())}},keydown:function(t){9!==t.keyCode&&13!==t.keyCode||this.hide(),27===t.keyCode&&(t.preventDefault(),t.stopPropagation(),this.hide())},updateElement:function(){if(this.element.is("input")&&this.autoUpdateInput){var t=this.startDate.format(this.locale.format);this.singleDatePicker||(t+=this.locale.separator+this.endDate.format(this.locale.format)),t!==this.element.val()&&this.element.val(t).trigger("change")}},remove:function(){this.container.remove(),this.element.off(".daterangepicker"),this.element.removeData()}},R.fn.daterangepicker=function(t,e){var a=R.extend(!0,{},R.fn.daterangepicker.defaultOptions,t);return this.each(function(){var t=R(this);t.data("daterangepicker")&&t.data("daterangepicker").remove(),t.data("daterangepicker",new i(t,a,e))}),this},i});
+//# sourceMappingURL=/sm/8cfffddf058dc09b67d92f8d849675e6b459dfb8ede5136cf5c98d10acf78cc3.map
\ No newline at end of file
diff --git a/apps/static/js/plugins/daterangepicker/moment.min.js b/apps/static/js/plugins/daterangepicker/moment.min.js
new file mode 100644
index 000000000..770f8bc54
--- /dev/null
+++ b/apps/static/js/plugins/daterangepicker/moment.min.js
@@ -0,0 +1,7 @@
+//! moment.js
+//! version : 2.18.1
+//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
+//! license : MIT
+//! momentjs.com
+!function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.moment=b()}(this,function(){"use strict";function a(){return sd.apply(null,arguments)}function b(a){sd=a}function c(a){return a instanceof Array||"[object Array]"===Object.prototype.toString.call(a)}function d(a){return null!=a&&"[object Object]"===Object.prototype.toString.call(a)}function e(a){var b;for(b in a)return!1;return!0}function f(a){return void 0===a}function g(a){return"number"==typeof a||"[object Number]"===Object.prototype.toString.call(a)}function h(a){return a instanceof Date||"[object Date]"===Object.prototype.toString.call(a)}function i(a,b){var c,d=[];for(c=0;c<a.length;++c)d.push(b(a[c],c));return d}function j(a,b){return Object.prototype.hasOwnProperty.call(a,b)}function k(a,b){for(var c in b)j(b,c)&&(a[c]=b[c]);return j(b,"toString")&&(a.toString=b.toString),j(b,"valueOf")&&(a.valueOf=b.valueOf),a}function l(a,b,c,d){return sb(a,b,c,d,!0).utc()}function m(){return{empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1,parsedDateParts:[],meridiem:null,rfc2822:!1,weekdayMismatch:!1}}function n(a){return null==a._pf&&(a._pf=m()),a._pf}function o(a){if(null==a._isValid){var b=n(a),c=ud.call(b.parsedDateParts,function(a){return null!=a}),d=!isNaN(a._d.getTime())&&b.overflow<0&&!b.empty&&!b.invalidMonth&&!b.invalidWeekday&&!b.nullInput&&!b.invalidFormat&&!b.userInvalidated&&(!b.meridiem||b.meridiem&&c);if(a._strict&&(d=d&&0===b.charsLeftOver&&0===b.unusedTokens.length&&void 0===b.bigHour),null!=Object.isFrozen&&Object.isFrozen(a))return d;a._isValid=d}return a._isValid}function p(a){var b=l(NaN);return null!=a?k(n(b),a):n(b).userInvalidated=!0,b}function q(a,b){var c,d,e;if(f(b._isAMomentObject)||(a._isAMomentObject=b._isAMomentObject),f(b._i)||(a._i=b._i),f(b._f)||(a._f=b._f),f(b._l)||(a._l=b._l),f(b._strict)||(a._strict=b._strict),f(b._tzm)||(a._tzm=b._tzm),f(b._isUTC)||(a._isUTC=b._isUTC),f(b._offset)||(a._offset=b._offset),f(b._pf)||(a._pf=n(b)),f(b._locale)||(a._locale=b._locale),vd.length>0)for(c=0;c<vd.length;c++)d=vd[c],e=b[d],f(e)||(a[d]=e);return a}function r(b){q(this,b),this._d=new Date(null!=b._d?b._d.getTime():NaN),this.isValid()||(this._d=new Date(NaN)),wd===!1&&(wd=!0,a.updateOffset(this),wd=!1)}function s(a){return a instanceof r||null!=a&&null!=a._isAMomentObject}function t(a){return a<0?Math.ceil(a)||0:Math.floor(a)}function u(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=t(b)),c}function v(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;d<e;d++)(c&&a[d]!==b[d]||!c&&u(a[d])!==u(b[d]))&&g++;return g+f}function w(b){a.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+b)}function x(b,c){var d=!0;return k(function(){if(null!=a.deprecationHandler&&a.deprecationHandler(null,b),d){for(var e,f=[],g=0;g<arguments.length;g++){if(e="","object"==typeof arguments[g]){e+="\n["+g+"] ";for(var h in arguments[0])e+=h+": "+arguments[0][h]+", ";e=e.slice(0,-2)}else e=arguments[g];f.push(e)}w(b+"\nArguments: "+Array.prototype.slice.call(f).join("")+"\n"+(new Error).stack),d=!1}return c.apply(this,arguments)},c)}function y(b,c){null!=a.deprecationHandler&&a.deprecationHandler(b,c),xd[b]||(w(c),xd[b]=!0)}function z(a){return a instanceof Function||"[object Function]"===Object.prototype.toString.call(a)}function A(a){var b,c;for(c in a)b=a[c],z(b)?this[c]=b:this["_"+c]=b;this._config=a,this._dayOfMonthOrdinalParseLenient=new RegExp((this._dayOfMonthOrdinalParse.source||this._ordinalParse.source)+"|"+/\d{1,2}/.source)}function B(a,b){var c,e=k({},a);for(c in b)j(b,c)&&(d(a[c])&&d(b[c])?(e[c]={},k(e[c],a[c]),k(e[c],b[c])):null!=b[c]?e[c]=b[c]:delete e[c]);for(c in a)j(a,c)&&!j(b,c)&&d(a[c])&&(e[c]=k({},e[c]));return e}function C(a){null!=a&&this.set(a)}function D(a,b,c){var d=this._calendar[a]||this._calendar.sameElse;return z(d)?d.call(b,c):d}function E(a){var b=this._longDateFormat[a],c=this._longDateFormat[a.toUpperCase()];return b||!c?b:(this._longDateFormat[a]=c.replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a])}function F(){return this._invalidDate}function G(a){return this._ordinal.replace("%d",a)}function H(a,b,c,d){var e=this._relativeTime[c];return z(e)?e(a,b,c,d):e.replace(/%d/i,a)}function I(a,b){var c=this._relativeTime[a>0?"future":"past"];return z(c)?c(b):c.replace(/%s/i,b)}function J(a,b){var c=a.toLowerCase();Hd[c]=Hd[c+"s"]=Hd[b]=a}function K(a){return"string"==typeof a?Hd[a]||Hd[a.toLowerCase()]:void 0}function L(a){var b,c,d={};for(c in a)j(a,c)&&(b=K(c),b&&(d[b]=a[c]));return d}function M(a,b){Id[a]=b}function N(a){var b=[];for(var c in a)b.push({unit:c,priority:Id[c]});return b.sort(function(a,b){return a.priority-b.priority}),b}function O(b,c){return function(d){return null!=d?(Q(this,b,d),a.updateOffset(this,c),this):P(this,b)}}function P(a,b){return a.isValid()?a._d["get"+(a._isUTC?"UTC":"")+b]():NaN}function Q(a,b,c){a.isValid()&&a._d["set"+(a._isUTC?"UTC":"")+b](c)}function R(a){return a=K(a),z(this[a])?this[a]():this}function S(a,b){if("object"==typeof a){a=L(a);for(var c=N(a),d=0;d<c.length;d++)this[c[d].unit](a[c[d].unit])}else if(a=K(a),z(this[a]))return this[a](b);return this}function T(a,b,c){var d=""+Math.abs(a),e=b-d.length,f=a>=0;return(f?c?"+":"":"-")+Math.pow(10,Math.max(0,e)).toString().substr(1)+d}function U(a,b,c,d){var e=d;"string"==typeof d&&(e=function(){return this[d]()}),a&&(Md[a]=e),b&&(Md[b[0]]=function(){return T(e.apply(this,arguments),b[1],b[2])}),c&&(Md[c]=function(){return this.localeData().ordinal(e.apply(this,arguments),a)})}function V(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function W(a){var b,c,d=a.match(Jd);for(b=0,c=d.length;b<c;b++)Md[d[b]]?d[b]=Md[d[b]]:d[b]=V(d[b]);return function(b){var e,f="";for(e=0;e<c;e++)f+=z(d[e])?d[e].call(b,a):d[e];return f}}function X(a,b){return a.isValid()?(b=Y(b,a.localeData()),Ld[b]=Ld[b]||W(b),Ld[b](a)):a.localeData().invalidDate()}function Y(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(Kd.lastIndex=0;d>=0&&Kd.test(a);)a=a.replace(Kd,c),Kd.lastIndex=0,d-=1;return a}function Z(a,b,c){ce[a]=z(b)?b:function(a,d){return a&&c?c:b}}function $(a,b){return j(ce,a)?ce[a](b._strict,b._locale):new RegExp(_(a))}function _(a){return aa(a.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e}))}function aa(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function ba(a,b){var c,d=b;for("string"==typeof a&&(a=[a]),g(b)&&(d=function(a,c){c[b]=u(a)}),c=0;c<a.length;c++)de[a[c]]=d}function ca(a,b){ba(a,function(a,c,d,e){d._w=d._w||{},b(a,d._w,d,e)})}function da(a,b,c){null!=b&&j(de,a)&&de[a](b,c._a,c,a)}function ea(a,b){return new Date(Date.UTC(a,b+1,0)).getUTCDate()}function fa(a,b){return a?c(this._months)?this._months[a.month()]:this._months[(this._months.isFormat||oe).test(b)?"format":"standalone"][a.month()]:c(this._months)?this._months:this._months.standalone}function ga(a,b){return a?c(this._monthsShort)?this._monthsShort[a.month()]:this._monthsShort[oe.test(b)?"format":"standalone"][a.month()]:c(this._monthsShort)?this._monthsShort:this._monthsShort.standalone}function ha(a,b,c){var d,e,f,g=a.toLocaleLowerCase();if(!this._monthsParse)for(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[],d=0;d<12;++d)f=l([2e3,d]),this._shortMonthsParse[d]=this.monthsShort(f,"").toLocaleLowerCase(),this._longMonthsParse[d]=this.months(f,"").toLocaleLowerCase();return c?"MMM"===b?(e=ne.call(this._shortMonthsParse,g),e!==-1?e:null):(e=ne.call(this._longMonthsParse,g),e!==-1?e:null):"MMM"===b?(e=ne.call(this._shortMonthsParse,g),e!==-1?e:(e=ne.call(this._longMonthsParse,g),e!==-1?e:null)):(e=ne.call(this._longMonthsParse,g),e!==-1?e:(e=ne.call(this._shortMonthsParse,g),e!==-1?e:null))}function ia(a,b,c){var d,e,f;if(this._monthsParseExact)return ha.call(this,a,b,c);for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),d=0;d<12;d++){if(e=l([2e3,d]),c&&!this._longMonthsParse[d]&&(this._longMonthsParse[d]=new RegExp("^"+this.months(e,"").replace(".","")+"$","i"),this._shortMonthsParse[d]=new RegExp("^"+this.monthsShort(e,"").replace(".","")+"$","i")),c||this._monthsParse[d]||(f="^"+this.months(e,"")+"|^"+this.monthsShort(e,""),this._monthsParse[d]=new RegExp(f.replace(".",""),"i")),c&&"MMMM"===b&&this._longMonthsParse[d].test(a))return d;if(c&&"MMM"===b&&this._shortMonthsParse[d].test(a))return d;if(!c&&this._monthsParse[d].test(a))return d}}function ja(a,b){var c;if(!a.isValid())return a;if("string"==typeof b)if(/^\d+$/.test(b))b=u(b);else if(b=a.localeData().monthsParse(b),!g(b))return a;return c=Math.min(a.date(),ea(a.year(),b)),a._d["set"+(a._isUTC?"UTC":"")+"Month"](b,c),a}function ka(b){return null!=b?(ja(this,b),a.updateOffset(this,!0),this):P(this,"Month")}function la(){return ea(this.year(),this.month())}function ma(a){return this._monthsParseExact?(j(this,"_monthsRegex")||oa.call(this),a?this._monthsShortStrictRegex:this._monthsShortRegex):(j(this,"_monthsShortRegex")||(this._monthsShortRegex=re),this._monthsShortStrictRegex&&a?this._monthsShortStrictRegex:this._monthsShortRegex)}function na(a){return this._monthsParseExact?(j(this,"_monthsRegex")||oa.call(this),a?this._monthsStrictRegex:this._monthsRegex):(j(this,"_monthsRegex")||(this._monthsRegex=se),this._monthsStrictRegex&&a?this._monthsStrictRegex:this._monthsRegex)}function oa(){function a(a,b){return b.length-a.length}var b,c,d=[],e=[],f=[];for(b=0;b<12;b++)c=l([2e3,b]),d.push(this.monthsShort(c,"")),e.push(this.months(c,"")),f.push(this.months(c,"")),f.push(this.monthsShort(c,""));for(d.sort(a),e.sort(a),f.sort(a),b=0;b<12;b++)d[b]=aa(d[b]),e[b]=aa(e[b]);for(b=0;b<24;b++)f[b]=aa(f[b]);this._monthsRegex=new RegExp("^("+f.join("|")+")","i"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp("^("+e.join("|")+")","i"),this._monthsShortStrictRegex=new RegExp("^("+d.join("|")+")","i")}function pa(a){return qa(a)?366:365}function qa(a){return a%4===0&&a%100!==0||a%400===0}function ra(){return qa(this.year())}function sa(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return a<100&&a>=0&&isFinite(h.getFullYear())&&h.setFullYear(a),h}function ta(a){var b=new Date(Date.UTC.apply(null,arguments));return a<100&&a>=0&&isFinite(b.getUTCFullYear())&&b.setUTCFullYear(a),b}function ua(a,b,c){var d=7+b-c,e=(7+ta(a,0,d).getUTCDay()-b)%7;return-e+d-1}function va(a,b,c,d,e){var f,g,h=(7+c-d)%7,i=ua(a,d,e),j=1+7*(b-1)+h+i;return j<=0?(f=a-1,g=pa(f)+j):j>pa(a)?(f=a+1,g=j-pa(a)):(f=a,g=j),{year:f,dayOfYear:g}}function wa(a,b,c){var d,e,f=ua(a.year(),b,c),g=Math.floor((a.dayOfYear()-f-1)/7)+1;return g<1?(e=a.year()-1,d=g+xa(e,b,c)):g>xa(a.year(),b,c)?(d=g-xa(a.year(),b,c),e=a.year()+1):(e=a.year(),d=g),{week:d,year:e}}function xa(a,b,c){var d=ua(a,b,c),e=ua(a+1,b,c);return(pa(a)-d+e)/7}function ya(a){return wa(a,this._week.dow,this._week.doy).week}function za(){return this._week.dow}function Aa(){return this._week.doy}function Ba(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")}function Ca(a){var b=wa(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")}function Da(a,b){return"string"!=typeof a?a:isNaN(a)?(a=b.weekdaysParse(a),"number"==typeof a?a:null):parseInt(a,10)}function Ea(a,b){return"string"==typeof a?b.weekdaysParse(a)%7||7:isNaN(a)?null:a}function Fa(a,b){return a?c(this._weekdays)?this._weekdays[a.day()]:this._weekdays[this._weekdays.isFormat.test(b)?"format":"standalone"][a.day()]:c(this._weekdays)?this._weekdays:this._weekdays.standalone}function Ga(a){return a?this._weekdaysShort[a.day()]:this._weekdaysShort}function Ha(a){return a?this._weekdaysMin[a.day()]:this._weekdaysMin}function Ia(a,b,c){var d,e,f,g=a.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],d=0;d<7;++d)f=l([2e3,1]).day(d),this._minWeekdaysParse[d]=this.weekdaysMin(f,"").toLocaleLowerCase(),this._shortWeekdaysParse[d]=this.weekdaysShort(f,"").toLocaleLowerCase(),this._weekdaysParse[d]=this.weekdays(f,"").toLocaleLowerCase();return c?"dddd"===b?(e=ne.call(this._weekdaysParse,g),e!==-1?e:null):"ddd"===b?(e=ne.call(this._shortWeekdaysParse,g),e!==-1?e:null):(e=ne.call(this._minWeekdaysParse,g),e!==-1?e:null):"dddd"===b?(e=ne.call(this._weekdaysParse,g),e!==-1?e:(e=ne.call(this._shortWeekdaysParse,g),e!==-1?e:(e=ne.call(this._minWeekdaysParse,g),e!==-1?e:null))):"ddd"===b?(e=ne.call(this._shortWeekdaysParse,g),e!==-1?e:(e=ne.call(this._weekdaysParse,g),e!==-1?e:(e=ne.call(this._minWeekdaysParse,g),e!==-1?e:null))):(e=ne.call(this._minWeekdaysParse,g),e!==-1?e:(e=ne.call(this._weekdaysParse,g),e!==-1?e:(e=ne.call(this._shortWeekdaysParse,g),e!==-1?e:null)))}function Ja(a,b,c){var d,e,f;if(this._weekdaysParseExact)return Ia.call(this,a,b,c);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),d=0;d<7;d++){if(e=l([2e3,1]).day(d),c&&!this._fullWeekdaysParse[d]&&(this._fullWeekdaysParse[d]=new RegExp("^"+this.weekdays(e,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[d]=new RegExp("^"+this.weekdaysShort(e,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[d]=new RegExp("^"+this.weekdaysMin(e,"").replace(".",".?")+"$","i")),this._weekdaysParse[d]||(f="^"+this.weekdays(e,"")+"|^"+this.weekdaysShort(e,"")+"|^"+this.weekdaysMin(e,""),this._weekdaysParse[d]=new RegExp(f.replace(".",""),"i")),c&&"dddd"===b&&this._fullWeekdaysParse[d].test(a))return d;if(c&&"ddd"===b&&this._shortWeekdaysParse[d].test(a))return d;if(c&&"dd"===b&&this._minWeekdaysParse[d].test(a))return d;if(!c&&this._weekdaysParse[d].test(a))return d}}function Ka(a){if(!this.isValid())return null!=a?this:NaN;var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=Da(a,this.localeData()),this.add(a-b,"d")):b}function La(a){if(!this.isValid())return null!=a?this:NaN;var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")}function Ma(a){if(!this.isValid())return null!=a?this:NaN;if(null!=a){var b=Ea(a,this.localeData());return this.day(this.day()%7?b:b-7)}return this.day()||7}function Na(a){return this._weekdaysParseExact?(j(this,"_weekdaysRegex")||Qa.call(this),a?this._weekdaysStrictRegex:this._weekdaysRegex):(j(this,"_weekdaysRegex")||(this._weekdaysRegex=ye),this._weekdaysStrictRegex&&a?this._weekdaysStrictRegex:this._weekdaysRegex)}function Oa(a){return this._weekdaysParseExact?(j(this,"_weekdaysRegex")||Qa.call(this),a?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(j(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=ze),this._weekdaysShortStrictRegex&&a?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)}function Pa(a){return this._weekdaysParseExact?(j(this,"_weekdaysRegex")||Qa.call(this),a?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(j(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=Ae),this._weekdaysMinStrictRegex&&a?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)}function Qa(){function a(a,b){return b.length-a.length}var b,c,d,e,f,g=[],h=[],i=[],j=[];for(b=0;b<7;b++)c=l([2e3,1]).day(b),d=this.weekdaysMin(c,""),e=this.weekdaysShort(c,""),f=this.weekdays(c,""),g.push(d),h.push(e),i.push(f),j.push(d),j.push(e),j.push(f);for(g.sort(a),h.sort(a),i.sort(a),j.sort(a),b=0;b<7;b++)h[b]=aa(h[b]),i[b]=aa(i[b]),j[b]=aa(j[b]);this._weekdaysRegex=new RegExp("^("+j.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+i.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+h.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+g.join("|")+")","i")}function Ra(){return this.hours()%12||12}function Sa(){return this.hours()||24}function Ta(a,b){U(a,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),b)})}function Ua(a,b){return b._meridiemParse}function Va(a){return"p"===(a+"").toLowerCase().charAt(0)}function Wa(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"}function Xa(a){return a?a.toLowerCase().replace("_","-"):a}function Ya(a){for(var b,c,d,e,f=0;f<a.length;){for(e=Xa(a[f]).split("-"),b=e.length,c=Xa(a[f+1]),c=c?c.split("-"):null;b>0;){if(d=Za(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&v(e,c,!0)>=b-1)break;b--}f++}return null}function Za(a){var b=null;if(!Fe[a]&&"undefined"!=typeof module&&module&&module.exports)try{b=Be._abbr,require("./locale/"+a),$a(b)}catch(a){}return Fe[a]}function $a(a,b){var c;return a&&(c=f(b)?bb(a):_a(a,b),c&&(Be=c)),Be._abbr}function _a(a,b){if(null!==b){var c=Ee;if(b.abbr=a,null!=Fe[a])y("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),c=Fe[a]._config;else if(null!=b.parentLocale){if(null==Fe[b.parentLocale])return Ge[b.parentLocale]||(Ge[b.parentLocale]=[]),Ge[b.parentLocale].push({name:a,config:b}),null;c=Fe[b.parentLocale]._config}return Fe[a]=new C(B(c,b)),Ge[a]&&Ge[a].forEach(function(a){_a(a.name,a.config)}),$a(a),Fe[a]}return delete Fe[a],null}function ab(a,b){if(null!=b){var c,d=Ee;null!=Fe[a]&&(d=Fe[a]._config),b=B(d,b),c=new C(b),c.parentLocale=Fe[a],Fe[a]=c,$a(a)}else null!=Fe[a]&&(null!=Fe[a].parentLocale?Fe[a]=Fe[a].parentLocale:null!=Fe[a]&&delete Fe[a]);return Fe[a]}function bb(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return Be;if(!c(a)){if(b=Za(a))return b;a=[a]}return Ya(a)}function cb(){return Ad(Fe)}function db(a){var b,c=a._a;return c&&n(a).overflow===-2&&(b=c[fe]<0||c[fe]>11?fe:c[ge]<1||c[ge]>ea(c[ee],c[fe])?ge:c[he]<0||c[he]>24||24===c[he]&&(0!==c[ie]||0!==c[je]||0!==c[ke])?he:c[ie]<0||c[ie]>59?ie:c[je]<0||c[je]>59?je:c[ke]<0||c[ke]>999?ke:-1,n(a)._overflowDayOfYear&&(b<ee||b>ge)&&(b=ge),n(a)._overflowWeeks&&b===-1&&(b=le),n(a)._overflowWeekday&&b===-1&&(b=me),n(a).overflow=b),a}function eb(a){var b,c,d,e,f,g,h=a._i,i=He.exec(h)||Ie.exec(h);if(i){for(n(a).iso=!0,b=0,c=Ke.length;b<c;b++)if(Ke[b][1].exec(i[1])){e=Ke[b][0],d=Ke[b][2]!==!1;break}if(null==e)return void(a._isValid=!1);if(i[3]){for(b=0,c=Le.length;b<c;b++)if(Le[b][1].exec(i[3])){f=(i[2]||" ")+Le[b][0];break}if(null==f)return void(a._isValid=!1)}if(!d&&null!=f)return void(a._isValid=!1);if(i[4]){if(!Je.exec(i[4]))return void(a._isValid=!1);g="Z"}a._f=e+(f||"")+(g||""),lb(a)}else a._isValid=!1}function fb(a){var b,c,d,e,f,g,h,i,j={" GMT":" +0000"," EDT":" -0400"," EST":" -0500"," CDT":" -0500"," CST":" -0600"," MDT":" -0600"," MST":" -0700"," PDT":" -0700"," PST":" -0800"},k="YXWVUTSRQPONZABCDEFGHIKLM";if(b=a._i.replace(/\([^\)]*\)|[\n\t]/g," ").replace(/(\s\s+)/g," ").replace(/^\s|\s$/g,""),c=Ne.exec(b)){if(d=c[1]?"ddd"+(5===c[1].length?", ":" "):"",e="D MMM "+(c[2].length>10?"YYYY ":"YY "),f="HH:mm"+(c[4]?":ss":""),c[1]){var l=new Date(c[2]),m=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][l.getDay()];if(c[1].substr(0,3)!==m)return n(a).weekdayMismatch=!0,void(a._isValid=!1)}switch(c[5].length){case 2:0===i?h=" +0000":(i=k.indexOf(c[5][1].toUpperCase())-12,h=(i<0?" -":" +")+(""+i).replace(/^-?/,"0").match(/..$/)[0]+"00");break;case 4:h=j[c[5]];break;default:h=j[" GMT"]}c[5]=h,a._i=c.splice(1).join(""),g=" ZZ",a._f=d+e+f+g,lb(a),n(a).rfc2822=!0}else a._isValid=!1}function gb(b){var c=Me.exec(b._i);return null!==c?void(b._d=new Date(+c[1])):(eb(b),void(b._isValid===!1&&(delete b._isValid,fb(b),b._isValid===!1&&(delete b._isValid,a.createFromInputFallback(b)))))}function hb(a,b,c){return null!=a?a:null!=b?b:c}function ib(b){var c=new Date(a.now());return b._useUTC?[c.getUTCFullYear(),c.getUTCMonth(),c.getUTCDate()]:[c.getFullYear(),c.getMonth(),c.getDate()]}function jb(a){var b,c,d,e,f=[];if(!a._d){for(d=ib(a),a._w&&null==a._a[ge]&&null==a._a[fe]&&kb(a),null!=a._dayOfYear&&(e=hb(a._a[ee],d[ee]),(a._dayOfYear>pa(e)||0===a._dayOfYear)&&(n(a)._overflowDayOfYear=!0),c=ta(e,0,a._dayOfYear),a._a[fe]=c.getUTCMonth(),a._a[ge]=c.getUTCDate()),b=0;b<3&&null==a._a[b];++b)a._a[b]=f[b]=d[b];for(;b<7;b++)a._a[b]=f[b]=null==a._a[b]?2===b?1:0:a._a[b];24===a._a[he]&&0===a._a[ie]&&0===a._a[je]&&0===a._a[ke]&&(a._nextDay=!0,a._a[he]=0),a._d=(a._useUTC?ta:sa).apply(null,f),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[he]=24)}}function kb(a){var b,c,d,e,f,g,h,i;if(b=a._w,null!=b.GG||null!=b.W||null!=b.E)f=1,g=4,c=hb(b.GG,a._a[ee],wa(tb(),1,4).year),d=hb(b.W,1),e=hb(b.E,1),(e<1||e>7)&&(i=!0);else{f=a._locale._week.dow,g=a._locale._week.doy;var j=wa(tb(),f,g);c=hb(b.gg,a._a[ee],j.year),d=hb(b.w,j.week),null!=b.d?(e=b.d,(e<0||e>6)&&(i=!0)):null!=b.e?(e=b.e+f,(b.e<0||b.e>6)&&(i=!0)):e=f}d<1||d>xa(c,f,g)?n(a)._overflowWeeks=!0:null!=i?n(a)._overflowWeekday=!0:(h=va(c,d,e,f,g),a._a[ee]=h.year,a._dayOfYear=h.dayOfYear)}function lb(b){if(b._f===a.ISO_8601)return void eb(b);if(b._f===a.RFC_2822)return void fb(b);b._a=[],n(b).empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,j=0;for(e=Y(b._f,b._locale).match(Jd)||[],c=0;c<e.length;c++)f=e[c],d=(h.match($(f,b))||[])[0],d&&(g=h.substr(0,h.indexOf(d)),g.length>0&&n(b).unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),j+=d.length),Md[f]?(d?n(b).empty=!1:n(b).unusedTokens.push(f),da(f,d,b)):b._strict&&!d&&n(b).unusedTokens.push(f);n(b).charsLeftOver=i-j,h.length>0&&n(b).unusedInput.push(h),b._a[he]<=12&&n(b).bigHour===!0&&b._a[he]>0&&(n(b).bigHour=void 0),n(b).parsedDateParts=b._a.slice(0),n(b).meridiem=b._meridiem,b._a[he]=mb(b._locale,b._a[he],b._meridiem),jb(b),db(b)}function mb(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&b<12&&(b+=12),d||12!==b||(b=0),b):b}function nb(a){var b,c,d,e,f;if(0===a._f.length)return n(a).invalidFormat=!0,void(a._d=new Date(NaN));for(e=0;e<a._f.length;e++)f=0,b=q({},a),null!=a._useUTC&&(b._useUTC=a._useUTC),b._f=a._f[e],lb(b),o(b)&&(f+=n(b).charsLeftOver,f+=10*n(b).unusedTokens.length,n(b).score=f,(null==d||f<d)&&(d=f,c=b));k(a,c||b)}function ob(a){if(!a._d){var b=L(a._i);a._a=i([b.year,b.month,b.day||b.date,b.hour,b.minute,b.second,b.millisecond],function(a){return a&&parseInt(a,10)}),jb(a)}}function pb(a){var b=new r(db(qb(a)));return b._nextDay&&(b.add(1,"d"),b._nextDay=void 0),b}function qb(a){var b=a._i,d=a._f;return a._locale=a._locale||bb(a._l),null===b||void 0===d&&""===b?p({nullInput:!0}):("string"==typeof b&&(a._i=b=a._locale.preparse(b)),s(b)?new r(db(b)):(h(b)?a._d=b:c(d)?nb(a):d?lb(a):rb(a),o(a)||(a._d=null),a))}function rb(b){var e=b._i;f(e)?b._d=new Date(a.now()):h(e)?b._d=new Date(e.valueOf()):"string"==typeof e?gb(b):c(e)?(b._a=i(e.slice(0),function(a){return parseInt(a,10)}),jb(b)):d(e)?ob(b):g(e)?b._d=new Date(e):a.createFromInputFallback(b)}function sb(a,b,f,g,h){var i={};return f!==!0&&f!==!1||(g=f,f=void 0),(d(a)&&e(a)||c(a)&&0===a.length)&&(a=void 0),i._isAMomentObject=!0,i._useUTC=i._isUTC=h,i._l=f,i._i=a,i._f=b,i._strict=g,pb(i)}function tb(a,b,c,d){return sb(a,b,c,d,!1)}function ub(a,b){var d,e;if(1===b.length&&c(b[0])&&(b=b[0]),!b.length)return tb();for(d=b[0],e=1;e<b.length;++e)b[e].isValid()&&!b[e][a](d)||(d=b[e]);return d}function vb(){var a=[].slice.call(arguments,0);return ub("isBefore",a)}function wb(){var a=[].slice.call(arguments,0);return ub("isAfter",a)}function xb(a){for(var b in a)if(Re.indexOf(b)===-1||null!=a[b]&&isNaN(a[b]))return!1;for(var c=!1,d=0;d<Re.length;++d)if(a[Re[d]]){if(c)return!1;parseFloat(a[Re[d]])!==u(a[Re[d]])&&(c=!0)}return!0}function yb(){return this._isValid}function zb(){return Sb(NaN)}function Ab(a){var b=L(a),c=b.year||0,d=b.quarter||0,e=b.month||0,f=b.week||0,g=b.day||0,h=b.hour||0,i=b.minute||0,j=b.second||0,k=b.millisecond||0;this._isValid=xb(b),this._milliseconds=+k+1e3*j+6e4*i+1e3*h*60*60,this._days=+g+7*f,this._months=+e+3*d+12*c,this._data={},this._locale=bb(),this._bubble()}function Bb(a){return a instanceof Ab}function Cb(a){return a<0?Math.round(-1*a)*-1:Math.round(a)}function Db(a,b){U(a,0,0,function(){var a=this.utcOffset(),c="+";return a<0&&(a=-a,c="-"),c+T(~~(a/60),2)+b+T(~~a%60,2)})}function Eb(a,b){var c=(b||"").match(a);if(null===c)return null;var d=c[c.length-1]||[],e=(d+"").match(Se)||["-",0,0],f=+(60*e[1])+u(e[2]);return 0===f?0:"+"===e[0]?f:-f}function Fb(b,c){var d,e;return c._isUTC?(d=c.clone(),e=(s(b)||h(b)?b.valueOf():tb(b).valueOf())-d.valueOf(),d._d.setTime(d._d.valueOf()+e),a.updateOffset(d,!1),d):tb(b).local()}function Gb(a){return 15*-Math.round(a._d.getTimezoneOffset()/15)}function Hb(b,c,d){var e,f=this._offset||0;if(!this.isValid())return null!=b?this:NaN;if(null!=b){if("string"==typeof b){if(b=Eb(_d,b),null===b)return this}else Math.abs(b)<16&&!d&&(b=60*b);return!this._isUTC&&c&&(e=Gb(this)),this._offset=b,this._isUTC=!0,null!=e&&this.add(e,"m"),f!==b&&(!c||this._changeInProgress?Xb(this,Sb(b-f,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this}return this._isUTC?f:Gb(this)}function Ib(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}function Jb(a){return this.utcOffset(0,a)}function Kb(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(Gb(this),"m")),this}function Lb(){if(null!=this._tzm)this.utcOffset(this._tzm,!1,!0);else if("string"==typeof this._i){var a=Eb($d,this._i);null!=a?this.utcOffset(a):this.utcOffset(0,!0)}return this}function Mb(a){return!!this.isValid()&&(a=a?tb(a).utcOffset():0,(this.utcOffset()-a)%60===0)}function Nb(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Ob(){if(!f(this._isDSTShifted))return this._isDSTShifted;var a={};if(q(a,this),a=qb(a),a._a){var b=a._isUTC?l(a._a):tb(a._a);this._isDSTShifted=this.isValid()&&v(a._a,b.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Pb(){return!!this.isValid()&&!this._isUTC}function Qb(){return!!this.isValid()&&this._isUTC}function Rb(){return!!this.isValid()&&(this._isUTC&&0===this._offset)}function Sb(a,b){var c,d,e,f=a,h=null;return Bb(a)?f={ms:a._milliseconds,d:a._days,M:a._months}:g(a)?(f={},b?f[b]=a:f.milliseconds=a):(h=Te.exec(a))?(c="-"===h[1]?-1:1,f={y:0,d:u(h[ge])*c,h:u(h[he])*c,m:u(h[ie])*c,s:u(h[je])*c,ms:u(Cb(1e3*h[ke]))*c}):(h=Ue.exec(a))?(c="-"===h[1]?-1:1,f={y:Tb(h[2],c),M:Tb(h[3],c),w:Tb(h[4],c),d:Tb(h[5],c),h:Tb(h[6],c),m:Tb(h[7],c),s:Tb(h[8],c)}):null==f?f={}:"object"==typeof f&&("from"in f||"to"in f)&&(e=Vb(tb(f.from),tb(f.to)),f={},f.ms=e.milliseconds,f.M=e.months),d=new Ab(f),Bb(a)&&j(a,"_locale")&&(d._locale=a._locale),d}function Tb(a,b){var c=a&&parseFloat(a.replace(",","."));return(isNaN(c)?0:c)*b}function Ub(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function Vb(a,b){var c;return a.isValid()&&b.isValid()?(b=Fb(b,a),a.isBefore(b)?c=Ub(a,b):(c=Ub(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c):{milliseconds:0,months:0}}function Wb(a,b){return function(c,d){var e,f;return null===d||isNaN(+d)||(y(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=Sb(c,d),Xb(this,e,a),this}}function Xb(b,c,d,e){var f=c._milliseconds,g=Cb(c._days),h=Cb(c._months);b.isValid()&&(e=null==e||e,f&&b._d.setTime(b._d.valueOf()+f*d),g&&Q(b,"Date",P(b,"Date")+g*d),h&&ja(b,P(b,"Month")+h*d),e&&a.updateOffset(b,g||h))}function Yb(a,b){var c=a.diff(b,"days",!0);return c<-6?"sameElse":c<-1?"lastWeek":c<0?"lastDay":c<1?"sameDay":c<2?"nextDay":c<7?"nextWeek":"sameElse"}function Zb(b,c){var d=b||tb(),e=Fb(d,this).startOf("day"),f=a.calendarFormat(this,e)||"sameElse",g=c&&(z(c[f])?c[f].call(this,d):c[f]);return this.format(g||this.localeData().calendar(f,this,tb(d)))}function $b(){return new r(this)}function _b(a,b){var c=s(a)?a:tb(a);return!(!this.isValid()||!c.isValid())&&(b=K(f(b)?"millisecond":b),"millisecond"===b?this.valueOf()>c.valueOf():c.valueOf()<this.clone().startOf(b).valueOf())}function ac(a,b){var c=s(a)?a:tb(a);return!(!this.isValid()||!c.isValid())&&(b=K(f(b)?"millisecond":b),"millisecond"===b?this.valueOf()<c.valueOf():this.clone().endOf(b).valueOf()<c.valueOf())}function bc(a,b,c,d){return d=d||"()",("("===d[0]?this.isAfter(a,c):!this.isBefore(a,c))&&(")"===d[1]?this.isBefore(b,c):!this.isAfter(b,c))}function cc(a,b){var c,d=s(a)?a:tb(a);return!(!this.isValid()||!d.isValid())&&(b=K(b||"millisecond"),"millisecond"===b?this.valueOf()===d.valueOf():(c=d.valueOf(),this.clone().startOf(b).valueOf()<=c&&c<=this.clone().endOf(b).valueOf()))}function dc(a,b){return this.isSame(a,b)||this.isAfter(a,b)}function ec(a,b){return this.isSame(a,b)||this.isBefore(a,b)}function fc(a,b,c){var d,e,f,g;return this.isValid()?(d=Fb(a,this),d.isValid()?(e=6e4*(d.utcOffset()-this.utcOffset()),b=K(b),"year"===b||"month"===b||"quarter"===b?(g=gc(this,d),"quarter"===b?g/=3:"year"===b&&(g/=12)):(f=this-d,g="second"===b?f/1e3:"minute"===b?f/6e4:"hour"===b?f/36e5:"day"===b?(f-e)/864e5:"week"===b?(f-e)/6048e5:f),c?g:t(g)):NaN):NaN}function gc(a,b){var c,d,e=12*(b.year()-a.year())+(b.month()-a.month()),f=a.clone().add(e,"months");return b-f<0?(c=a.clone().add(e-1,"months"),d=(b-f)/(f-c)):(c=a.clone().add(e+1,"months"),d=(b-f)/(c-f)),-(e+d)||0}function hc(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function ic(){if(!this.isValid())return null;var a=this.clone().utc();return a.year()<0||a.year()>9999?X(a,"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]"):z(Date.prototype.toISOString)?this.toDate().toISOString():X(a,"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]")}function jc(){if(!this.isValid())return"moment.invalid(/* "+this._i+" */)";var a="moment",b="";this.isLocal()||(a=0===this.utcOffset()?"moment.utc":"moment.parseZone",b="Z");var c="["+a+'("]',d=0<=this.year()&&this.year()<=9999?"YYYY":"YYYYYY",e="-MM-DD[T]HH:mm:ss.SSS",f=b+'[")]';return this.format(c+d+e+f)}function kc(b){b||(b=this.isUtc()?a.defaultFormatUtc:a.defaultFormat);var c=X(this,b);return this.localeData().postformat(c)}function lc(a,b){return this.isValid()&&(s(a)&&a.isValid()||tb(a).isValid())?Sb({to:this,from:a}).locale(this.locale()).humanize(!b):this.localeData().invalidDate()}function mc(a){return this.from(tb(),a)}function nc(a,b){return this.isValid()&&(s(a)&&a.isValid()||tb(a).isValid())?Sb({from:this,to:a}).locale(this.locale()).humanize(!b):this.localeData().invalidDate()}function oc(a){return this.to(tb(),a)}function pc(a){var b;return void 0===a?this._locale._abbr:(b=bb(a),null!=b&&(this._locale=b),this)}function qc(){return this._locale}function rc(a){switch(a=K(a)){case"year":this.month(0);case"quarter":case"month":this.date(1);case"week":case"isoWeek":case"day":case"date":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}return"week"===a&&this.weekday(0),"isoWeek"===a&&this.isoWeekday(1),"quarter"===a&&this.month(3*Math.floor(this.month()/3)),this}function sc(a){return a=K(a),void 0===a||"millisecond"===a?this:("date"===a&&(a="day"),this.startOf(a).add(1,"isoWeek"===a?"week":a).subtract(1,"ms"))}function tc(){return this._d.valueOf()-6e4*(this._offset||0)}function uc(){return Math.floor(this.valueOf()/1e3)}function vc(){return new Date(this.valueOf())}function wc(){var a=this;return[a.year(),a.month(),a.date(),a.hour(),a.minute(),a.second(),a.millisecond()]}function xc(){var a=this;return{years:a.year(),months:a.month(),date:a.date(),hours:a.hours(),minutes:a.minutes(),seconds:a.seconds(),milliseconds:a.milliseconds()}}function yc(){return this.isValid()?this.toISOString():null}function zc(){return o(this)}function Ac(){
+return k({},n(this))}function Bc(){return n(this).overflow}function Cc(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}}function Dc(a,b){U(0,[a,a.length],0,b)}function Ec(a){return Ic.call(this,a,this.week(),this.weekday(),this.localeData()._week.dow,this.localeData()._week.doy)}function Fc(a){return Ic.call(this,a,this.isoWeek(),this.isoWeekday(),1,4)}function Gc(){return xa(this.year(),1,4)}function Hc(){var a=this.localeData()._week;return xa(this.year(),a.dow,a.doy)}function Ic(a,b,c,d,e){var f;return null==a?wa(this,d,e).year:(f=xa(a,d,e),b>f&&(b=f),Jc.call(this,a,b,c,d,e))}function Jc(a,b,c,d,e){var f=va(a,b,c,d,e),g=ta(f.year,0,f.dayOfYear);return this.year(g.getUTCFullYear()),this.month(g.getUTCMonth()),this.date(g.getUTCDate()),this}function Kc(a){return null==a?Math.ceil((this.month()+1)/3):this.month(3*(a-1)+this.month()%3)}function Lc(a){var b=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")}function Mc(a,b){b[ke]=u(1e3*("0."+a))}function Nc(){return this._isUTC?"UTC":""}function Oc(){return this._isUTC?"Coordinated Universal Time":""}function Pc(a){return tb(1e3*a)}function Qc(){return tb.apply(null,arguments).parseZone()}function Rc(a){return a}function Sc(a,b,c,d){var e=bb(),f=l().set(d,b);return e[c](f,a)}function Tc(a,b,c){if(g(a)&&(b=a,a=void 0),a=a||"",null!=b)return Sc(a,b,c,"month");var d,e=[];for(d=0;d<12;d++)e[d]=Sc(a,d,c,"month");return e}function Uc(a,b,c,d){"boolean"==typeof a?(g(b)&&(c=b,b=void 0),b=b||""):(b=a,c=b,a=!1,g(b)&&(c=b,b=void 0),b=b||"");var e=bb(),f=a?e._week.dow:0;if(null!=c)return Sc(b,(c+f)%7,d,"day");var h,i=[];for(h=0;h<7;h++)i[h]=Sc(b,(h+f)%7,d,"day");return i}function Vc(a,b){return Tc(a,b,"months")}function Wc(a,b){return Tc(a,b,"monthsShort")}function Xc(a,b,c){return Uc(a,b,c,"weekdays")}function Yc(a,b,c){return Uc(a,b,c,"weekdaysShort")}function Zc(a,b,c){return Uc(a,b,c,"weekdaysMin")}function $c(){var a=this._data;return this._milliseconds=df(this._milliseconds),this._days=df(this._days),this._months=df(this._months),a.milliseconds=df(a.milliseconds),a.seconds=df(a.seconds),a.minutes=df(a.minutes),a.hours=df(a.hours),a.months=df(a.months),a.years=df(a.years),this}function _c(a,b,c,d){var e=Sb(b,c);return a._milliseconds+=d*e._milliseconds,a._days+=d*e._days,a._months+=d*e._months,a._bubble()}function ad(a,b){return _c(this,a,b,1)}function bd(a,b){return _c(this,a,b,-1)}function cd(a){return a<0?Math.floor(a):Math.ceil(a)}function dd(){var a,b,c,d,e,f=this._milliseconds,g=this._days,h=this._months,i=this._data;return f>=0&&g>=0&&h>=0||f<=0&&g<=0&&h<=0||(f+=864e5*cd(fd(h)+g),g=0,h=0),i.milliseconds=f%1e3,a=t(f/1e3),i.seconds=a%60,b=t(a/60),i.minutes=b%60,c=t(b/60),i.hours=c%24,g+=t(c/24),e=t(ed(g)),h+=e,g-=cd(fd(e)),d=t(h/12),h%=12,i.days=g,i.months=h,i.years=d,this}function ed(a){return 4800*a/146097}function fd(a){return 146097*a/4800}function gd(a){if(!this.isValid())return NaN;var b,c,d=this._milliseconds;if(a=K(a),"month"===a||"year"===a)return b=this._days+d/864e5,c=this._months+ed(b),"month"===a?c:c/12;switch(b=this._days+Math.round(fd(this._months)),a){case"week":return b/7+d/6048e5;case"day":return b+d/864e5;case"hour":return 24*b+d/36e5;case"minute":return 1440*b+d/6e4;case"second":return 86400*b+d/1e3;case"millisecond":return Math.floor(864e5*b)+d;default:throw new Error("Unknown unit "+a)}}function hd(){return this.isValid()?this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*u(this._months/12):NaN}function id(a){return function(){return this.as(a)}}function jd(a){return a=K(a),this.isValid()?this[a+"s"]():NaN}function kd(a){return function(){return this.isValid()?this._data[a]:NaN}}function ld(){return t(this.days()/7)}function md(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function nd(a,b,c){var d=Sb(a).abs(),e=uf(d.as("s")),f=uf(d.as("m")),g=uf(d.as("h")),h=uf(d.as("d")),i=uf(d.as("M")),j=uf(d.as("y")),k=e<=vf.ss&&["s",e]||e<vf.s&&["ss",e]||f<=1&&["m"]||f<vf.m&&["mm",f]||g<=1&&["h"]||g<vf.h&&["hh",g]||h<=1&&["d"]||h<vf.d&&["dd",h]||i<=1&&["M"]||i<vf.M&&["MM",i]||j<=1&&["y"]||["yy",j];return k[2]=b,k[3]=+a>0,k[4]=c,md.apply(null,k)}function od(a){return void 0===a?uf:"function"==typeof a&&(uf=a,!0)}function pd(a,b){return void 0!==vf[a]&&(void 0===b?vf[a]:(vf[a]=b,"s"===a&&(vf.ss=b-1),!0))}function qd(a){if(!this.isValid())return this.localeData().invalidDate();var b=this.localeData(),c=nd(this,!a,b);return a&&(c=b.pastFuture(+this,c)),b.postformat(c)}function rd(){if(!this.isValid())return this.localeData().invalidDate();var a,b,c,d=wf(this._milliseconds)/1e3,e=wf(this._days),f=wf(this._months);a=t(d/60),b=t(a/60),d%=60,a%=60,c=t(f/12),f%=12;var g=c,h=f,i=e,j=b,k=a,l=d,m=this.asSeconds();return m?(m<0?"-":"")+"P"+(g?g+"Y":"")+(h?h+"M":"")+(i?i+"D":"")+(j||k||l?"T":"")+(j?j+"H":"")+(k?k+"M":"")+(l?l+"S":""):"P0D"}var sd,td;td=Array.prototype.some?Array.prototype.some:function(a){for(var b=Object(this),c=b.length>>>0,d=0;d<c;d++)if(d in b&&a.call(this,b[d],d,b))return!0;return!1};var ud=td,vd=a.momentProperties=[],wd=!1,xd={};a.suppressDeprecationWarnings=!1,a.deprecationHandler=null;var yd;yd=Object.keys?Object.keys:function(a){var b,c=[];for(b in a)j(a,b)&&c.push(b);return c};var zd,Ad=yd,Bd={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},Cd={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},Dd="Invalid date",Ed="%d",Fd=/\d{1,2}/,Gd={future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},Hd={},Id={},Jd=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,Kd=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,Ld={},Md={},Nd=/\d/,Od=/\d\d/,Pd=/\d{3}/,Qd=/\d{4}/,Rd=/[+-]?\d{6}/,Sd=/\d\d?/,Td=/\d\d\d\d?/,Ud=/\d\d\d\d\d\d?/,Vd=/\d{1,3}/,Wd=/\d{1,4}/,Xd=/[+-]?\d{1,6}/,Yd=/\d+/,Zd=/[+-]?\d+/,$d=/Z|[+-]\d\d:?\d\d/gi,_d=/Z|[+-]\d\d(?::?\d\d)?/gi,ae=/[+-]?\d+(\.\d{1,3})?/,be=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,ce={},de={},ee=0,fe=1,ge=2,he=3,ie=4,je=5,ke=6,le=7,me=8;zd=Array.prototype.indexOf?Array.prototype.indexOf:function(a){var b;for(b=0;b<this.length;++b)if(this[b]===a)return b;return-1};var ne=zd;U("M",["MM",2],"Mo",function(){return this.month()+1}),U("MMM",0,0,function(a){return this.localeData().monthsShort(this,a)}),U("MMMM",0,0,function(a){return this.localeData().months(this,a)}),J("month","M"),M("month",8),Z("M",Sd),Z("MM",Sd,Od),Z("MMM",function(a,b){return b.monthsShortRegex(a)}),Z("MMMM",function(a,b){return b.monthsRegex(a)}),ba(["M","MM"],function(a,b){b[fe]=u(a)-1}),ba(["MMM","MMMM"],function(a,b,c,d){var e=c._locale.monthsParse(a,d,c._strict);null!=e?b[fe]=e:n(c).invalidMonth=a});var oe=/D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/,pe="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),qe="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),re=be,se=be;U("Y",0,0,function(){var a=this.year();return a<=9999?""+a:"+"+a}),U(0,["YY",2],0,function(){return this.year()%100}),U(0,["YYYY",4],0,"year"),U(0,["YYYYY",5],0,"year"),U(0,["YYYYYY",6,!0],0,"year"),J("year","y"),M("year",1),Z("Y",Zd),Z("YY",Sd,Od),Z("YYYY",Wd,Qd),Z("YYYYY",Xd,Rd),Z("YYYYYY",Xd,Rd),ba(["YYYYY","YYYYYY"],ee),ba("YYYY",function(b,c){c[ee]=2===b.length?a.parseTwoDigitYear(b):u(b)}),ba("YY",function(b,c){c[ee]=a.parseTwoDigitYear(b)}),ba("Y",function(a,b){b[ee]=parseInt(a,10)}),a.parseTwoDigitYear=function(a){return u(a)+(u(a)>68?1900:2e3)};var te=O("FullYear",!0);U("w",["ww",2],"wo","week"),U("W",["WW",2],"Wo","isoWeek"),J("week","w"),J("isoWeek","W"),M("week",5),M("isoWeek",5),Z("w",Sd),Z("ww",Sd,Od),Z("W",Sd),Z("WW",Sd,Od),ca(["w","ww","W","WW"],function(a,b,c,d){b[d.substr(0,1)]=u(a)});var ue={dow:0,doy:6};U("d",0,"do","day"),U("dd",0,0,function(a){return this.localeData().weekdaysMin(this,a)}),U("ddd",0,0,function(a){return this.localeData().weekdaysShort(this,a)}),U("dddd",0,0,function(a){return this.localeData().weekdays(this,a)}),U("e",0,0,"weekday"),U("E",0,0,"isoWeekday"),J("day","d"),J("weekday","e"),J("isoWeekday","E"),M("day",11),M("weekday",11),M("isoWeekday",11),Z("d",Sd),Z("e",Sd),Z("E",Sd),Z("dd",function(a,b){return b.weekdaysMinRegex(a)}),Z("ddd",function(a,b){return b.weekdaysShortRegex(a)}),Z("dddd",function(a,b){return b.weekdaysRegex(a)}),ca(["dd","ddd","dddd"],function(a,b,c,d){var e=c._locale.weekdaysParse(a,d,c._strict);null!=e?b.d=e:n(c).invalidWeekday=a}),ca(["d","e","E"],function(a,b,c,d){b[d]=u(a)});var ve="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),we="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),xe="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),ye=be,ze=be,Ae=be;U("H",["HH",2],0,"hour"),U("h",["hh",2],0,Ra),U("k",["kk",2],0,Sa),U("hmm",0,0,function(){return""+Ra.apply(this)+T(this.minutes(),2)}),U("hmmss",0,0,function(){return""+Ra.apply(this)+T(this.minutes(),2)+T(this.seconds(),2)}),U("Hmm",0,0,function(){return""+this.hours()+T(this.minutes(),2)}),U("Hmmss",0,0,function(){return""+this.hours()+T(this.minutes(),2)+T(this.seconds(),2)}),Ta("a",!0),Ta("A",!1),J("hour","h"),M("hour",13),Z("a",Ua),Z("A",Ua),Z("H",Sd),Z("h",Sd),Z("k",Sd),Z("HH",Sd,Od),Z("hh",Sd,Od),Z("kk",Sd,Od),Z("hmm",Td),Z("hmmss",Ud),Z("Hmm",Td),Z("Hmmss",Ud),ba(["H","HH"],he),ba(["k","kk"],function(a,b,c){var d=u(a);b[he]=24===d?0:d}),ba(["a","A"],function(a,b,c){c._isPm=c._locale.isPM(a),c._meridiem=a}),ba(["h","hh"],function(a,b,c){b[he]=u(a),n(c).bigHour=!0}),ba("hmm",function(a,b,c){var d=a.length-2;b[he]=u(a.substr(0,d)),b[ie]=u(a.substr(d)),n(c).bigHour=!0}),ba("hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[he]=u(a.substr(0,d)),b[ie]=u(a.substr(d,2)),b[je]=u(a.substr(e)),n(c).bigHour=!0}),ba("Hmm",function(a,b,c){var d=a.length-2;b[he]=u(a.substr(0,d)),b[ie]=u(a.substr(d))}),ba("Hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[he]=u(a.substr(0,d)),b[ie]=u(a.substr(d,2)),b[je]=u(a.substr(e))});var Be,Ce=/[ap]\.?m?\.?/i,De=O("Hours",!0),Ee={calendar:Bd,longDateFormat:Cd,invalidDate:Dd,ordinal:Ed,dayOfMonthOrdinalParse:Fd,relativeTime:Gd,months:pe,monthsShort:qe,week:ue,weekdays:ve,weekdaysMin:xe,weekdaysShort:we,meridiemParse:Ce},Fe={},Ge={},He=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,Ie=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,Je=/Z|[+-]\d\d(?::?\d\d)?/,Ke=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],Le=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],Me=/^\/?Date\((\-?\d+)/i,Ne=/^((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d?\d\s(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(?:\d\d)?\d\d\s)(\d\d:\d\d)(\:\d\d)?(\s(?:UT|GMT|[ECMP][SD]T|[A-IK-Za-ik-z]|[+-]\d{4}))$/;a.createFromInputFallback=x("value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are discouraged and will be removed in an upcoming major release. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),a.ISO_8601=function(){},a.RFC_2822=function(){};var Oe=x("moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/",function(){var a=tb.apply(null,arguments);return this.isValid()&&a.isValid()?a<this?this:a:p()}),Pe=x("moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/",function(){var a=tb.apply(null,arguments);return this.isValid()&&a.isValid()?a>this?this:a:p()}),Qe=function(){return Date.now?Date.now():+new Date},Re=["year","quarter","month","week","day","hour","minute","second","millisecond"];Db("Z",":"),Db("ZZ",""),Z("Z",_d),Z("ZZ",_d),ba(["Z","ZZ"],function(a,b,c){c._useUTC=!0,c._tzm=Eb(_d,a)});var Se=/([\+\-]|\d\d)/gi;a.updateOffset=function(){};var Te=/^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/,Ue=/^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/;Sb.fn=Ab.prototype,Sb.invalid=zb;var Ve=Wb(1,"add"),We=Wb(-1,"subtract");a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",a.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var Xe=x("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(a){return void 0===a?this.localeData():this.locale(a)});U(0,["gg",2],0,function(){return this.weekYear()%100}),U(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Dc("gggg","weekYear"),Dc("ggggg","weekYear"),Dc("GGGG","isoWeekYear"),Dc("GGGGG","isoWeekYear"),J("weekYear","gg"),J("isoWeekYear","GG"),M("weekYear",1),M("isoWeekYear",1),Z("G",Zd),Z("g",Zd),Z("GG",Sd,Od),Z("gg",Sd,Od),Z("GGGG",Wd,Qd),Z("gggg",Wd,Qd),Z("GGGGG",Xd,Rd),Z("ggggg",Xd,Rd),ca(["gggg","ggggg","GGGG","GGGGG"],function(a,b,c,d){b[d.substr(0,2)]=u(a)}),ca(["gg","GG"],function(b,c,d,e){c[e]=a.parseTwoDigitYear(b)}),U("Q",0,"Qo","quarter"),J("quarter","Q"),M("quarter",7),Z("Q",Nd),ba("Q",function(a,b){b[fe]=3*(u(a)-1)}),U("D",["DD",2],"Do","date"),J("date","D"),M("date",9),Z("D",Sd),Z("DD",Sd,Od),Z("Do",function(a,b){return a?b._dayOfMonthOrdinalParse||b._ordinalParse:b._dayOfMonthOrdinalParseLenient}),ba(["D","DD"],ge),ba("Do",function(a,b){b[ge]=u(a.match(Sd)[0],10)});var Ye=O("Date",!0);U("DDD",["DDDD",3],"DDDo","dayOfYear"),J("dayOfYear","DDD"),M("dayOfYear",4),Z("DDD",Vd),Z("DDDD",Pd),ba(["DDD","DDDD"],function(a,b,c){c._dayOfYear=u(a)}),U("m",["mm",2],0,"minute"),J("minute","m"),M("minute",14),Z("m",Sd),Z("mm",Sd,Od),ba(["m","mm"],ie);var Ze=O("Minutes",!1);U("s",["ss",2],0,"second"),J("second","s"),M("second",15),Z("s",Sd),Z("ss",Sd,Od),ba(["s","ss"],je);var $e=O("Seconds",!1);U("S",0,0,function(){return~~(this.millisecond()/100)}),U(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),U(0,["SSS",3],0,"millisecond"),U(0,["SSSS",4],0,function(){return 10*this.millisecond()}),U(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),U(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),U(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),U(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),U(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),J("millisecond","ms"),M("millisecond",16),Z("S",Vd,Nd),Z("SS",Vd,Od),Z("SSS",Vd,Pd);var _e;for(_e="SSSS";_e.length<=9;_e+="S")Z(_e,Yd);for(_e="S";_e.length<=9;_e+="S")ba(_e,Mc);var af=O("Milliseconds",!1);U("z",0,0,"zoneAbbr"),U("zz",0,0,"zoneName");var bf=r.prototype;bf.add=Ve,bf.calendar=Zb,bf.clone=$b,bf.diff=fc,bf.endOf=sc,bf.format=kc,bf.from=lc,bf.fromNow=mc,bf.to=nc,bf.toNow=oc,bf.get=R,bf.invalidAt=Bc,bf.isAfter=_b,bf.isBefore=ac,bf.isBetween=bc,bf.isSame=cc,bf.isSameOrAfter=dc,bf.isSameOrBefore=ec,bf.isValid=zc,bf.lang=Xe,bf.locale=pc,bf.localeData=qc,bf.max=Pe,bf.min=Oe,bf.parsingFlags=Ac,bf.set=S,bf.startOf=rc,bf.subtract=We,bf.toArray=wc,bf.toObject=xc,bf.toDate=vc,bf.toISOString=ic,bf.inspect=jc,bf.toJSON=yc,bf.toString=hc,bf.unix=uc,bf.valueOf=tc,bf.creationData=Cc,bf.year=te,bf.isLeapYear=ra,bf.weekYear=Ec,bf.isoWeekYear=Fc,bf.quarter=bf.quarters=Kc,bf.month=ka,bf.daysInMonth=la,bf.week=bf.weeks=Ba,bf.isoWeek=bf.isoWeeks=Ca,bf.weeksInYear=Hc,bf.isoWeeksInYear=Gc,bf.date=Ye,bf.day=bf.days=Ka,bf.weekday=La,bf.isoWeekday=Ma,bf.dayOfYear=Lc,bf.hour=bf.hours=De,bf.minute=bf.minutes=Ze,bf.second=bf.seconds=$e,bf.millisecond=bf.milliseconds=af,bf.utcOffset=Hb,bf.utc=Jb,bf.local=Kb,bf.parseZone=Lb,bf.hasAlignedHourOffset=Mb,bf.isDST=Nb,bf.isLocal=Pb,bf.isUtcOffset=Qb,bf.isUtc=Rb,bf.isUTC=Rb,bf.zoneAbbr=Nc,bf.zoneName=Oc,bf.dates=x("dates accessor is deprecated. Use date instead.",Ye),bf.months=x("months accessor is deprecated. Use month instead",ka),bf.years=x("years accessor is deprecated. Use year instead",te),bf.zone=x("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",Ib),bf.isDSTShifted=x("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",Ob);var cf=C.prototype;cf.calendar=D,cf.longDateFormat=E,cf.invalidDate=F,cf.ordinal=G,cf.preparse=Rc,cf.postformat=Rc,cf.relativeTime=H,cf.pastFuture=I,cf.set=A,cf.months=fa,cf.monthsShort=ga,cf.monthsParse=ia,cf.monthsRegex=na,cf.monthsShortRegex=ma,cf.week=ya,cf.firstDayOfYear=Aa,cf.firstDayOfWeek=za,cf.weekdays=Fa,cf.weekdaysMin=Ha,cf.weekdaysShort=Ga,cf.weekdaysParse=Ja,cf.weekdaysRegex=Na,cf.weekdaysShortRegex=Oa,cf.weekdaysMinRegex=Pa,cf.isPM=Va,cf.meridiem=Wa,$a("en",{dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===u(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),a.lang=x("moment.lang is deprecated. Use moment.locale instead.",$a),a.langData=x("moment.langData is deprecated. Use moment.localeData instead.",bb);var df=Math.abs,ef=id("ms"),ff=id("s"),gf=id("m"),hf=id("h"),jf=id("d"),kf=id("w"),lf=id("M"),mf=id("y"),nf=kd("milliseconds"),of=kd("seconds"),pf=kd("minutes"),qf=kd("hours"),rf=kd("days"),sf=kd("months"),tf=kd("years"),uf=Math.round,vf={ss:44,s:45,m:45,h:22,d:26,M:11},wf=Math.abs,xf=Ab.prototype;return xf.isValid=yb,xf.abs=$c,xf.add=ad,xf.subtract=bd,xf.as=gd,xf.asMilliseconds=ef,xf.asSeconds=ff,xf.asMinutes=gf,xf.asHours=hf,xf.asDays=jf,xf.asWeeks=kf,xf.asMonths=lf,xf.asYears=mf,xf.valueOf=hd,xf._bubble=dd,xf.get=jd,xf.milliseconds=nf,xf.seconds=of,xf.minutes=pf,xf.hours=qf,xf.days=rf,xf.weeks=ld,xf.months=sf,xf.years=tf,xf.humanize=qd,xf.toISOString=rd,xf.toString=rd,xf.toJSON=rd,xf.locale=pc,xf.localeData=qc,xf.toIsoString=x("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",rd),xf.lang=Xe,U("X",0,0,"unix"),U("x",0,0,"valueOf"),Z("x",Zd),Z("X",ae),ba("X",function(a,b,c){c._d=new Date(1e3*parseFloat(a,10))}),ba("x",function(a,b,c){c._d=new Date(u(a))}),a.version="2.18.1",b(tb),a.fn=bf,a.min=vb,a.max=wb,a.now=Qe,a.utc=l,a.unix=Pc,a.months=Vc,a.isDate=h,a.locale=$a,a.invalid=p,a.duration=Sb,a.isMoment=s,a.weekdays=Xc,a.parseZone=Qc,a.localeData=bb,a.isDuration=Bb,a.monthsShort=Wc,a.weekdaysMin=Zc,a.defineLocale=_a,a.updateLocale=ab,a.locales=cb,a.weekdaysShort=Yc,a.normalizeUnits=K,a.relativeTimeRounding=od,a.relativeTimeThreshold=pd,a.calendarFormat=Yb,a.prototype=bf,a});
\ No newline at end of file
diff --git a/apps/users/templates/users/_user.html b/apps/users/templates/users/_user.html
index 5090e825f..9c6a0c06f 100644
--- a/apps/users/templates/users/_user.html
+++ b/apps/users/templates/users/_user.html
@@ -30,7 +30,7 @@
             <div class="col-sm-9">
                 <div class="input-group date">
                     <span class="input-group-addon"><i class="fa fa-calendar"></i></span>
-                    <input id="{{ form.date_expired.id_for_label }}" name="{{ form.date_expired.html_name }}" type="text" class="form-control" value="{{ form.date_expired.value|date:'Y-m-d' }}">
+                    <input id="{{ form.date_expired.id_for_label }}" name="{{ form.date_expired.html_name }}" type="text" class="form-control" value="{{ form.date_expired.value|date:'Y-m-d H:i' }}">
                 </div>
                 <span class="help-block ">{{ form.date_expired.errors }}</span>
             </div>
@@ -52,18 +52,24 @@
 {% endblock %}
 {% block custom_foot_js %}
     <script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
+    <script type="text/javascript" src='{% static "js/plugins/daterangepicker/moment.min.js" %}'></script>
+    <script type="text/javascript" src='{% static "js/plugins/daterangepicker/daterangepicker.min.js" %}'></script>
+    <link rel="stylesheet" type="text/css" href={% static "css/plugins/daterangepicker/daterangepicker.css" %} />
+
     <script>
+        var dateOptions = {
+            singleDatePicker: true,
+            showDropdowns: true,
+            timePicker: true,
+            timePicker24Hour: true,
+            autoApply: true,
+            locale: {
+                format: 'YYYY-MM-DD HH:mm'
+            }
+        };
         $(document).ready(function () {
             $('.select2').select2();
-
-            $('.input-group.date').datepicker({
-                format: "yyyy-mm-dd",
-                todayBtn: "linked",
-                keyboardNavigation: false,
-                forceParse: false,
-                calendarWeeks: true,
-                autoclose: true
-            });
+            $('#id_date_expired').daterangepicker(dateOptions);
         })
     </script>
 {% endblock %}