From 284e8be45c9f9d2f539e4737a7d7f68cf208420a Mon Sep 17 00:00:00 2001 From: BaiJiangJie Date: Tue, 23 Oct 2018 19:22:18 +0800 Subject: [PATCH 01/27] =?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 %} +
+
+
+
+
+
{{ action }}
+ +
+ +
+
+
+ +
+ +
+
+ +
+ +
+ +
* required
+
+
+
+ + + +{# #} + + + + + +
+
+
+ + {% trans 'Submit' %} +
+
+
+
+
+
+
+
+{% endblock %} + +{% block custom_foot_js %} + +{% 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 %} +
+
+
+
+
+
{{ action }}
+ +
+ +
+
+
+ +
+ +
+
+ +
+ +
+ +
* required
+{# #} +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + {% trans 'Submit' %} +
+
+
+
+
+
+
+
+{% endblock %} + +{% block custom_foot_js %} + +{% 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 %} +
+
+ + +
+
+

{% trans "Command storage" %}

@@ -71,6 +79,7 @@ {% trans 'Name' %} {% trans 'Type' %} + {% trans 'Action' %} @@ -78,10 +87,13 @@ {{ name }} {{ setting.TYPE }} + {% trans 'Delete' %} {% endfor %} + {% trans 'Add' %} +

{% trans "Replay storage" %}

@@ -89,6 +101,7 @@ + @@ -96,18 +109,14 @@ + {% endfor %}
{% trans 'Name' %} {% trans 'Type' %}{% trans 'Action' %}
{{ name }} {{ setting.TYPE }}{% trans 'Delete' %}
+ {% trans 'Add' %} +
-
-
- - -
-
@@ -116,40 +125,63 @@ - {% endblock %} {% block custom_foot_js %} - + {% 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 @@
{% if form.errors.all %}
- {{ form.errors.all }} -
+ {{ form.errors.all }} +
{% 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 Date: Tue, 23 Oct 2018 20:41:01 +0800 Subject: [PATCH 02/27] =?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 @@
-
如果有多台主机,请使用逗号 ( , ) 进行分割
+
{% trans 'Tips: If there are multiple hosts, separate them with a comma (,)' %}
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 @@
* required
-{# #}
@@ -53,77 +52,84 @@ 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 \n" "Language-Team: Jumpserver team\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 "%(name)s 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 "" "
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 Date: Wed, 24 Oct 2018 10:11:38 +0800 Subject: [PATCH 03/27] =?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 Date: Wed, 24 Oct 2018 10:29:40 +0800 Subject: [PATCH 04/27] =?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 Date: Wed, 24 Oct 2018 10:48:03 +0800 Subject: [PATCH 05/27] =?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 %} {% 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 4f580e0df81f27125f2c78b90795659a4b753c85 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 24 Oct 2018 10:53:00 +0800 Subject: [PATCH 06/27] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jms | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/jms b/jms index 244fff826..9cbf020e8 100755 --- a/jms +++ b/jms @@ -42,10 +42,26 @@ try: except: pass +def check_database_connection(): + os.chdir(os.path.join(BASE_DIR, 'apps')) + for i in range(60): + print("Check database connection ...") + code = subprocess.call("python manage.py showmigrations users ", shell=True) + if code == 0: + print("Database connect success") + return + time.sleep(1) + print("Connection database failed, exist") + sys.exit(10) + def make_migrations(): print("Check database structure change ...") os.chdir(os.path.join(BASE_DIR, 'apps')) + if len(os.listdir('assets/migrations')) < 4: + print("Make database migrations ...") + subprocess.call('python3 manage.py makemigrations', shell=True) + print("Migrate model change to database ...") subprocess.call('python3 manage.py migrate', shell=True) @@ -56,6 +72,7 @@ def collect_static(): def prepare(): + check_database_connection() make_migrations() collect_static() @@ -112,7 +129,6 @@ def parse_service(s): def start_gunicorn(): print("\n- Start Gunicorn WSGI HTTP Server") - prepare() service = 'gunicorn' bind = '{}:{}'.format(HTTP_HOST, HTTP_PORT) log_format = '%(h)s %(t)s "%(r)s" %(s)s %(b)s ' @@ -205,6 +221,7 @@ def start_service(s): print(time.ctime()) print('Jumpserver version {}, more see https://www.jumpserver.org'.format( __version__)) + prepare() services_handler = { "gunicorn": start_gunicorn, From 6278900201a26b06e86b0f187bae4a9851dabee6 Mon Sep 17 00:00:00 2001 From: BaiJiangJie Date: Wed, 24 Oct 2018 10:57:55 +0800 Subject: [PATCH 07/27] =?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 @@
{% trans 'Tips: If there are multiple hosts, separate them with a comma (,)' %}
+
eg: http://www.jumpserver.a.com, http://www.jumpserver.b.com
From ebd92c79c7fcc5330b60d4eb9247a543032cbeec Mon Sep 17 00:00:00 2001 From: BaiJiangJie Date: Wed, 24 Oct 2018 11:05:39 +0800 Subject: [PATCH 08/27] =?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 @@
eg: http://www.jumpserver.a.com, http://www.jumpserver.b.com
- {# #} - 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 17/27] =?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 %} + + + + {% 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='
'),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'+o+"";this.showCustomRangeLabel&&(m+='
  • '+this.locale.customRangeLabel+"
  • "),m+="",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');C+="",C+="",(this.showWeekNumbers||this.showISOWeekNumbers)&&(C+=""),k&&!k.isBefore(e.firstDay)||this.linkedCalendars&&"left"!=t?C+="":C+='';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='";for(var A='")}if(C+=''+v+"",b&&!b.isAfter(e.lastDay)||this.linkedCalendars&&"right"!=t&&!this.singleDatePicker?C+="":C+='',C+="",C+="",(this.showWeekNumbers||this.showISOWeekNumbers)&&(C+=''+this.locale.weekLabel+""),R.each(this.locale.daysOfWeek,function(t,e){C+=""+e+""}),C+="",C+="",C+="",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+="",this.showWeekNumbers?C+=''+e[y][0].week()+"":this.showISOWeekNumbers&&(C+=''+e[y][0].isoWeek()+"");for(g=0;g<7;g++){var W=[];e[y][g].isSame(new Date,"day")&&W.push("today"),5this.startDate&&e[y][g]'+e[y][g].date()+""}C+=""}C+="",C+="",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=' ",e+=': ",this.timePickerSeconds){e+=': "}if(!this.timePicker24Hour){e+='"}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||(nthis.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;c0)for(c=0;c0?"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=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=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=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;f0;){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&&(bge)&&(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;b10?"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;c0&&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;ethis.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()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]||e0,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;d68?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()?athis?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 @@
    - +
    {{ form.date_expired.errors }}
    @@ -52,18 +52,24 @@ {% endblock %} {% block custom_foot_js %} + + + + {% endblock %} From 9bb498f7b3f7b9e426f019a2296121505abbde4b Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 5 Nov 2018 11:25:22 +0800 Subject: [PATCH 20/27] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9sdk=E7=89=88?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 399e382f3..6ca94846f 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -61,7 +61,7 @@ pytz==2018.3 PyYAML==3.12 redis==2.10.6 requests==2.18.4 -jms-storage==0.0.19 +jms-storage==0.0.20 s3transfer==0.1.13 simplejson==3.13.2 six==1.11.0 From b8874e18552e241f5fce8d26ecd1b41416c2eb11 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 5 Nov 2018 11:52:44 +0800 Subject: [PATCH 21/27] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements/requirements.txt | 2 +- requirements/rpm_requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 6ca94846f..d4c53e0bd 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -35,7 +35,7 @@ eventlet==0.24.1 ForgeryPy==0.1 greenlet==0.4.14 gunicorn==19.9.0 -idna==2.7 +idna==2.6 itsdangerous==0.24 itypes==1.1.0 Jinja2==2.10 diff --git a/requirements/rpm_requirements.txt b/requirements/rpm_requirements.txt index be3ce68e3..e66bae32f 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 mariadb-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 mysql-devel libffi-devel openssh-clients From 2e4e5503ccdef908d5a1c10d1ec1d5aec2f6c603 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 7 Nov 2018 11:26:39 +0800 Subject: [PATCH 22/27] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8D=E8=8E=B7?= =?UTF-8?q?=E5=8F=96common=20settings=E6=97=B6=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/utils.py | 10 ++++++++-- apps/terminal/backends/__init__.py | 1 - 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/common/utils.py b/apps/common/utils.py index 5b870c088..7daa449a4 100644 --- a/apps/common/utils.py +++ b/apps/common/utils.py @@ -392,7 +392,10 @@ 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 + try: + command_storage = common_settings.TERMINAL_COMMAND_STORAGE + except Exception: + return default if command_storage is None: obj = Setting() obj.name = name @@ -413,7 +416,10 @@ 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 + try: + replay_storage = common_settings.TERMINAL_REPLAY_STORAGE + except Exception: + return default if replay_storage is None: obj = Setting() obj.name = name diff --git a/apps/terminal/backends/__init__.py b/apps/terminal/backends/__init__.py index 1c454a32d..0f0ec0cc5 100644 --- a/apps/terminal/backends/__init__.py +++ b/apps/terminal/backends/__init__.py @@ -3,7 +3,6 @@ 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', From b577c626f7cf36683d93e7d896dbe030909ec295 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 9 Nov 2018 11:23:41 +0800 Subject: [PATCH 23/27] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8Dluna?= =?UTF-8?q?=E5=8F=96=E5=88=B0=E5=8D=8F=E8=AE=AE=E4=B8=8D=E5=90=8C=E7=9A=84?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E7=94=A8=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/perms/api.py b/apps/perms/api.py index 1488c21e6..08b8a3642 100644 --- a/apps/perms/api.py +++ b/apps/perms/api.py @@ -437,6 +437,7 @@ class UserGrantedNodeChildrenApi(ListAPIView): for asset, system_users in nodes_granted[node].items(): fake_node = asset.as_node() fake_node.assets_amount = 0 + system_users = [s for s in system_users if s.protocol == asset.protocol] fake_node.asset.system_users_granted = system_users fake_node.key = node.key + ':0' fake_nodes.append(fake_node) @@ -459,6 +460,8 @@ class UserGrantedNodeChildrenApi(ListAPIView): asset_has_matched = True fake_node = asset.as_node() fake_node.assets_amount = 0 + system_users = [s for s in system_users if + s.protocol == asset.protocol] fake_node.asset.system_users_granted = system_users fake_node.key = node.key + ':0' matched_assets.append(fake_node) From 1fcb272ddcbe7492a460eb2432e7afd9a2603f83 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 9 Nov 2018 12:33:45 +0800 Subject: [PATCH 24/27] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8Dcoco=20tree?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE=E9=A2=98=EF=BC=8C=E4=B8=8D?= =?UTF-8?q?=E5=BA=94=E8=AF=A5=E8=BF=87=E6=BB=A4=E6=B2=A1=E6=9C=89asset?= =?UTF-8?q?=E7=9A=84node?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/perms/api.py b/apps/perms/api.py index 08b8a3642..e90a1a272 100644 --- a/apps/perms/api.py +++ b/apps/perms/api.py @@ -172,8 +172,6 @@ class UserGrantedNodesWithAssetsApi(AssetsFilterMixin, ListAPIView): system_users_granted = [s for s in v if s.protocol == k.protocol] k.system_users_granted = system_users_granted node.assets_granted = assets - if not node.assets_granted: - continue queryset.append(node) return queryset From e09f3ca4fd1f7937fe381725d5043793f7e2fc10 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Fri, 9 Nov 2018 14:54:38 +0800 Subject: [PATCH 25/27] =?UTF-8?q?[Update]=20=E6=94=AF=E6=8C=81=20OpenID=20?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=20(#2008)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] core支持openid登录,coco还不支持 * [Update] coco支持openid登录 * [Update] 修改注释 * [Update] 修改 OpenID Auth Code Backend 用户认证失败返回None, 不是Anonymoususer * [Update] 修改OpenID Code用户认证异常捕获 * [Update] 修改OpenID Auth Middleware, check用户是否单点退出的异常捕获 * [Update] 修改config_example Auth OpenID 配置 * [Update] 登录页面添加 更多登录方式 * [Update] 重构OpenID认证架构 * [Update] 修改小细节 * [Update] OpenID用户认证成功后,更新用户来源 * [update] 添加OpenID用户登录成功日志 --- apps/authentication/__init__.py | 0 apps/authentication/admin.py | 1 + apps/authentication/apps.py | 10 ++ apps/authentication/models.py | 1 + apps/authentication/openid/__init__.py | 20 +++ apps/authentication/openid/backends.py | 90 +++++++++++++ apps/authentication/openid/middleware.py | 42 ++++++ apps/authentication/openid/models.py | 159 +++++++++++++++++++++++ apps/authentication/openid/tests.py | 0 apps/authentication/openid/views.py | 102 +++++++++++++++ apps/authentication/signals.py | 4 + apps/authentication/signals_handlers.py | 33 +++++ apps/authentication/tests.py | 1 + apps/authentication/urls/api_urls.py | 1 + apps/authentication/urls/view_urls.py | 16 +++ apps/authentication/views.py | 1 + apps/jumpserver/settings.py | 20 +++ apps/jumpserver/urls.py | 1 + apps/locale/zh/LC_MESSAGES/django.mo | Bin 55705 -> 55767 bytes apps/locale/zh/LC_MESSAGES/django.po | 54 ++++---- apps/users/models/user.py | 2 + apps/users/templates/users/login.html | 20 ++- apps/users/views/login.py | 4 + config_example.py | 8 ++ 24 files changed, 564 insertions(+), 26 deletions(-) create mode 100644 apps/authentication/__init__.py create mode 100644 apps/authentication/admin.py create mode 100644 apps/authentication/apps.py create mode 100644 apps/authentication/models.py create mode 100644 apps/authentication/openid/__init__.py create mode 100644 apps/authentication/openid/backends.py create mode 100644 apps/authentication/openid/middleware.py create mode 100644 apps/authentication/openid/models.py create mode 100644 apps/authentication/openid/tests.py create mode 100644 apps/authentication/openid/views.py create mode 100644 apps/authentication/signals.py create mode 100644 apps/authentication/signals_handlers.py create mode 100644 apps/authentication/tests.py create mode 100644 apps/authentication/urls/api_urls.py create mode 100644 apps/authentication/urls/view_urls.py create mode 100644 apps/authentication/views.py diff --git a/apps/authentication/__init__.py b/apps/authentication/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/authentication/admin.py b/apps/authentication/admin.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/authentication/admin.py @@ -0,0 +1 @@ + diff --git a/apps/authentication/apps.py b/apps/authentication/apps.py new file mode 100644 index 000000000..0869b64fd --- /dev/null +++ b/apps/authentication/apps.py @@ -0,0 +1,10 @@ +from django.apps import AppConfig + + +class AuthenticationConfig(AppConfig): + name = 'authentication' + + def ready(self): + from . import signals_handlers + super().ready() + diff --git a/apps/authentication/models.py b/apps/authentication/models.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/authentication/models.py @@ -0,0 +1 @@ + diff --git a/apps/authentication/openid/__init__.py b/apps/authentication/openid/__init__.py new file mode 100644 index 000000000..bc4c753ca --- /dev/null +++ b/apps/authentication/openid/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# + +from django.conf import settings +from .models import Client + + +def new_client(): + """ + :return: authentication.models.Client + """ + return Client( + server_url=settings.AUTH_OPENID_SERVER_URL, + realm_name=settings.AUTH_OPENID_REALM_NAME, + client_id=settings.AUTH_OPENID_CLIENT_ID, + client_secret=settings.AUTH_OPENID_CLIENT_SECRET + ) + + +client = new_client() diff --git a/apps/authentication/openid/backends.py b/apps/authentication/openid/backends.py new file mode 100644 index 000000000..15a758acc --- /dev/null +++ b/apps/authentication/openid/backends.py @@ -0,0 +1,90 @@ +# coding:utf-8 +# + +from django.contrib.auth import get_user_model +from django.conf import settings + +from . import client +from common.utils import get_logger +from authentication.openid.models import OIDT_ACCESS_TOKEN + +UserModel = get_user_model() + +logger = get_logger(__file__) + +BACKEND_OPENID_AUTH_CODE = \ + 'authentication.openid.backends.OpenIDAuthorizationCodeBackend' + + +class BaseOpenIDAuthorizationBackend(object): + + @staticmethod + def user_can_authenticate(user): + """ + Reject users with is_active=False. Custom user models that don't have + that attribute are allowed. + """ + is_active = getattr(user, 'is_active', None) + return is_active or is_active is None + + def get_user(self, user_id): + try: + user = UserModel._default_manager.get(pk=user_id) + except UserModel.DoesNotExist: + return None + + return user if self.user_can_authenticate(user) else None + + +class OpenIDAuthorizationCodeBackend(BaseOpenIDAuthorizationBackend): + + def authenticate(self, request, **kwargs): + logger.info('1.openid code backend') + + code = kwargs.get('code') + redirect_uri = kwargs.get('redirect_uri') + + if not code or not redirect_uri: + return None + + try: + oidt_profile = client.update_or_create_from_code( + code=code, + redirect_uri=redirect_uri + ) + + except Exception as e: + logger.error(e) + + else: + # Check openid user single logout or not with access_token + request.session[OIDT_ACCESS_TOKEN] = oidt_profile.access_token + + user = oidt_profile.user + + return user if self.user_can_authenticate(user) else None + + +class OpenIDAuthorizationPasswordBackend(BaseOpenIDAuthorizationBackend): + + def authenticate(self, request, username=None, password=None, **kwargs): + logger.info('2.openid password backend') + + if not settings.AUTH_OPENID: + return None + + elif not username: + return None + + try: + oidt_profile = client.update_or_create_from_password( + username=username, password=password + ) + + except Exception as e: + logger.error(e) + + else: + user = oidt_profile.user + return user if self.user_can_authenticate(user) else None + diff --git a/apps/authentication/openid/middleware.py b/apps/authentication/openid/middleware.py new file mode 100644 index 000000000..128b20984 --- /dev/null +++ b/apps/authentication/openid/middleware.py @@ -0,0 +1,42 @@ +# coding:utf-8 +# + +from django.conf import settings +from django.contrib.auth import logout +from django.utils.deprecation import MiddlewareMixin +from django.contrib.auth import BACKEND_SESSION_KEY + +from . import client +from common.utils import get_logger +from .backends import BACKEND_OPENID_AUTH_CODE +from authentication.openid.models import OIDT_ACCESS_TOKEN + +logger = get_logger(__file__) + + +class OpenIDAuthenticationMiddleware(MiddlewareMixin): + """ + Check openid user single logout (with access_token) + """ + + def process_request(self, request): + + # Don't need openid auth if AUTH_OPENID is False + if not settings.AUTH_OPENID: + return + + # Don't need check single logout if user not authenticated + if not request.user.is_authenticated: + return + + elif request.session[BACKEND_SESSION_KEY] != BACKEND_OPENID_AUTH_CODE: + return + + # Check openid user single logout or not with access_token + try: + client.openid_connect_client.userinfo( + token=request.session.get(OIDT_ACCESS_TOKEN)) + + except Exception as e: + logout(request) + logger.error(e) diff --git a/apps/authentication/openid/models.py b/apps/authentication/openid/models.py new file mode 100644 index 000000000..e3c0a4842 --- /dev/null +++ b/apps/authentication/openid/models.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- +# + +from django.db import transaction +from django.contrib.auth import get_user_model +from keycloak.realm import KeycloakRealm +from keycloak.keycloak_openid import KeycloakOpenID +from ..signals import post_create_openid_user + +OIDT_ACCESS_TOKEN = 'oidt_access_token' + + +class OpenIDTokenProfile(object): + + def __init__(self, user, access_token, refresh_token): + """ + :param user: User object + :param access_token: + :param refresh_token: + """ + self.user = user + self.access_token = access_token + self.refresh_token = refresh_token + + def __str__(self): + return "{}'s OpenID token profile".format(self.user.username) + + +class Client(object): + + def __init__(self, server_url, realm_name, client_id, client_secret): + self.server_url = server_url + self.realm_name = realm_name + self.client_id = client_id + self.client_secret = client_secret + self.realm = self.new_realm() + self.openid_client = self.new_openid_client() + self.openid_connect_client = self.new_openid_connect_client() + + def new_realm(self): + """ + :param authentication.openid.models.Realm realm: + :return keycloak.realm.Realm: + """ + return KeycloakRealm( + server_url=self.server_url, + realm_name=self.realm_name, + headers={} + ) + + def new_openid_connect_client(self): + """ + :rtype: keycloak.openid_connect.KeycloakOpenidConnect + """ + openid_connect = self.realm.open_id_connect( + client_id=self.client_id, + client_secret=self.client_secret + ) + return openid_connect + + def new_openid_client(self): + """ + :rtype: keycloak.keycloak_openid.KeycloakOpenID + """ + + return KeycloakOpenID( + server_url='%sauth/' % self.server_url, + realm_name=self.realm_name, + client_id=self.client_id, + client_secret_key=self.client_secret, + ) + + def update_or_create_from_password(self, username, password): + """ + Update or create an user based on an authentication username and password. + + :param str username: authentication username + :param str password: authentication password + :return: authentication.models.OpenIDTokenProfile + """ + token_response = self.openid_client.token( + username=username, password=password + ) + + return self._update_or_create(token_response=token_response) + + def update_or_create_from_code(self, code, redirect_uri): + """ + Update or create an user based on an authentication code. + Response as specified in: + + https://tools.ietf.org/html/rfc6749#section-4.1.4 + + :param str code: authentication code + :param str redirect_uri: + :rtype: authentication.models.OpenIDTokenProfile + """ + + token_response = self.openid_connect_client.authorization_code( + code=code, redirect_uri=redirect_uri) + + return self._update_or_create(token_response=token_response) + + def _update_or_create(self, token_response): + """ + Update or create an user based on a token response. + + `token_response` contains the items returned by the OpenIDConnect Token API + end-point: + - id_token + - access_token + - expires_in + - refresh_token + - refresh_expires_in + + :param dict token_response: + :rtype: authentication.openid.models.OpenIDTokenProfile + """ + + userinfo = self.openid_connect_client.userinfo( + token=token_response['access_token']) + + with transaction.atomic(): + user, _ = get_user_model().objects.update_or_create( + username=userinfo.get('preferred_username', ''), + defaults={ + 'email': userinfo.get('email', ''), + 'first_name': userinfo.get('given_name', ''), + 'last_name': userinfo.get('family_name', '') + } + ) + + oidt_profile = OpenIDTokenProfile( + user=user, + access_token=token_response['access_token'], + refresh_token=token_response['refresh_token'], + ) + + if user: + post_create_openid_user.send(sender=user.__class__, user=user) + + return oidt_profile + + def __str__(self): + return self.client_id + + +class Nonce(object): + """ + The openid-login is stored in cache as a temporary object, recording the + user's redirect_uri and next_pat + """ + + def __init__(self, redirect_uri, next_path): + import uuid + self.state = uuid.uuid4() + self.redirect_uri = redirect_uri + self.next_path = next_path + diff --git a/apps/authentication/openid/tests.py b/apps/authentication/openid/tests.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/authentication/openid/views.py b/apps/authentication/openid/views.py new file mode 100644 index 000000000..9aeb0bf7b --- /dev/null +++ b/apps/authentication/openid/views.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +# + +import logging + +from django.urls import reverse +from django.conf import settings +from django.core.cache import cache +from django.views.generic.base import RedirectView +from django.contrib.auth import authenticate, login +from django.http.response import ( + HttpResponseBadRequest, + HttpResponseServerError, + HttpResponseRedirect +) + +from . import client +from .models import Nonce +from users.models import LoginLog +from users.tasks import write_login_log_async +from common.utils import get_request_ip + +logger = logging.getLogger(__name__) + + +def get_base_site_url(): + return settings.BASE_SITE_URL + + +class LoginView(RedirectView): + + def get_redirect_url(self, *args, **kwargs): + nonce = Nonce( + redirect_uri=get_base_site_url() + reverse( + "authentication:openid-login-complete"), + + next_path=self.request.GET.get('next') + ) + + cache.set(str(nonce.state), nonce, 24*3600) + + self.request.session['openid_state'] = str(nonce.state) + + authorization_url = client.openid_connect_client.\ + authorization_url( + redirect_uri=nonce.redirect_uri, scope='code', + state=str(nonce.state) + ) + + return authorization_url + + +class LoginCompleteView(RedirectView): + + def get(self, request, *args, **kwargs): + if 'error' in request.GET: + return HttpResponseServerError(self.request.GET['error']) + + if 'code' not in self.request.GET and 'state' not in self.request.GET: + return HttpResponseBadRequest() + + if self.request.GET['state'] != self.request.session['openid_state']: + return HttpResponseBadRequest() + + nonce = cache.get(self.request.GET['state']) + + if not nonce: + return HttpResponseBadRequest() + + user = authenticate( + request=self.request, + code=self.request.GET['code'], + redirect_uri=nonce.redirect_uri + ) + + cache.delete(str(nonce.state)) + + if not user: + return HttpResponseBadRequest() + + login(self.request, user) + + data = { + 'username': user.username, + 'mfa': int(user.otp_enabled), + 'reason': LoginLog.REASON_NOTHING, + 'status': True + } + self.write_login_log(data) + + return HttpResponseRedirect(nonce.next_path or '/') + + def write_login_log(self, data): + login_ip = get_request_ip(self.request) + user_agent = self.request.META.get('HTTP_USER_AGENT', '') + tmp_data = { + 'ip': login_ip, + 'type': 'W', + 'user_agent': user_agent + } + data.update(tmp_data) + write_login_log_async.delay(**data) diff --git a/apps/authentication/signals.py b/apps/authentication/signals.py new file mode 100644 index 000000000..f33a3b821 --- /dev/null +++ b/apps/authentication/signals.py @@ -0,0 +1,4 @@ +from django.dispatch import Signal + + +post_create_openid_user = Signal(providing_args=('user',)) diff --git a/apps/authentication/signals_handlers.py b/apps/authentication/signals_handlers.py new file mode 100644 index 000000000..7cf240386 --- /dev/null +++ b/apps/authentication/signals_handlers.py @@ -0,0 +1,33 @@ +from django.http.request import QueryDict +from django.contrib.auth.signals import user_logged_out +from django.dispatch import receiver +from django.conf import settings +from .openid import client +from .signals import post_create_openid_user + + +@receiver(user_logged_out) +def on_user_logged_out(sender, request, user, **kwargs): + if not settings.AUTH_OPENID: + return + + query = QueryDict('', mutable=True) + query.update({ + 'redirect_uri': settings.BASE_SITE_URL + }) + + openid_logout_url = "%s?%s" % ( + client.openid_connect_client.get_url( + name='end_session_endpoint'), + query.urlencode() + ) + + request.COOKIES['next'] = openid_logout_url + + +@receiver(post_create_openid_user) +def on_post_create_openid_user(sender, user=None, **kwargs): + if user and user.username != 'admin': + user.source = user.SOURCE_OPENID + user.save() + diff --git a/apps/authentication/tests.py b/apps/authentication/tests.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/authentication/tests.py @@ -0,0 +1 @@ + diff --git a/apps/authentication/urls/api_urls.py b/apps/authentication/urls/api_urls.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/authentication/urls/api_urls.py @@ -0,0 +1 @@ + diff --git a/apps/authentication/urls/view_urls.py b/apps/authentication/urls/view_urls.py new file mode 100644 index 000000000..4d4e6753a --- /dev/null +++ b/apps/authentication/urls/view_urls.py @@ -0,0 +1,16 @@ +# coding:utf-8 +# + +from django.urls import path +from authentication.openid import views + +app_name = 'authentication' + +urlpatterns = [ + # openid + path('openid/login/', views.LoginView.as_view(), name='openid-login'), + path('openid/login/complete/', views.LoginCompleteView.as_view(), + name='openid-login-complete'), + + # other +] diff --git a/apps/authentication/views.py b/apps/authentication/views.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/authentication/views.py @@ -0,0 +1 @@ + diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 6635b35e6..212878386 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -64,6 +64,7 @@ INSTALLED_APPS = [ 'common.apps.CommonConfig', 'terminal.apps.TerminalConfig', 'audits.apps.AuditsConfig', + 'authentication.apps.AuthenticationConfig', # authentication 'rest_framework', 'rest_framework_swagger', 'drf_yasg', @@ -94,6 +95,7 @@ MIDDLEWARE = [ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'authentication.openid.middleware.OpenIDAuthenticationMiddleware', # openid 'jumpserver.middleware.TimezoneMiddleware', 'jumpserver.middleware.DemoMiddleware', 'jumpserver.middleware.RequestMiddleware', @@ -389,6 +391,24 @@ AUTH_LDAP_BACKEND = 'django_auth_ldap.backend.LDAPBackend' if AUTH_LDAP: AUTHENTICATION_BACKENDS.insert(0, AUTH_LDAP_BACKEND) +# openid +# Auth OpenID settings +BASE_SITE_URL = CONFIG.BASE_SITE_URL +AUTH_OPENID = CONFIG.AUTH_OPENID +AUTH_OPENID_SERVER_URL = CONFIG.AUTH_OPENID_SERVER_URL +AUTH_OPENID_REALM_NAME = CONFIG.AUTH_OPENID_REALM_NAME +AUTH_OPENID_CLIENT_ID = CONFIG.AUTH_OPENID_CLIENT_ID +AUTH_OPENID_CLIENT_SECRET = CONFIG.AUTH_OPENID_CLIENT_SECRET +AUTH_OPENID_BACKENDS = [ + 'authentication.openid.backends.OpenIDAuthorizationPasswordBackend', + 'authentication.openid.backends.OpenIDAuthorizationCodeBackend', +] + +if AUTH_OPENID: + LOGIN_URL = reverse_lazy("authentication:openid-login") + AUTHENTICATION_BACKENDS.insert(0, AUTH_OPENID_BACKENDS[0]) + AUTHENTICATION_BACKENDS.insert(0, AUTH_OPENID_BACKENDS[1]) + # Celery using redis as broker CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/%(db)s' % { 'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '', diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index 22351e509..2319d81e5 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -75,6 +75,7 @@ app_view_patterns = [ path('ops/', include('ops.urls.view_urls', namespace='ops')), path('audits/', include('audits.urls.view_urls', namespace='audits')), path('orgs/', include('orgs.urls.views_urls', namespace='orgs')), + path('auth/', include('authentication.urls.view_urls'), name='auth'), ] if settings.XPACK_ENABLED: diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 9f41c1d67939c0a96cc3a11a618c509fd2a70610..bb3a0102676cdcc8f6fff601e97337378be1ec37 100644 GIT binary patch delta 17218 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 Nw@=*gR?;ic{{t7z1SkLi delta 17158 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\n" "Language-Team: Jumpserver team\n" @@ -115,7 +115,7 @@ msgid "Port" msgstr "端口" #: assets/forms/domain.py:15 assets/forms/label.py:13 -#: assets/models/asset.py:243 assets/templates/assets/admin_user_list.html:28 +#: assets/models/asset.py:253 assets/templates/assets/admin_user_list.html:28 #: assets/templates/assets/domain_detail.html:60 #: assets/templates/assets/domain_list.html:26 #: assets/templates/assets/label_list.html:16 @@ -1758,7 +1758,7 @@ msgstr "改密日志" #: 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:331 users/views/user.py:68 +#: users/views/group.py:92 users/views/login.py:334 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" @@ -2438,7 +2438,7 @@ msgstr "任务列表" msgid "Task run history" msgstr "执行历史" -#: orgs/mixins.py:78 orgs/models.py:24 +#: orgs/mixins.py:77 orgs/models.py:24 msgid "Organization" msgstr "组织管理" @@ -3092,11 +3092,11 @@ msgstr "登录频繁, 稍后重试" msgid "Please carry seed value and conduct MFA secondary certification" msgstr "请携带seed值, 进行MFA二次认证" -#: users/api/auth.py:195 +#: users/api/auth.py:196 msgid "Please verify the user name and password first" msgstr "请先进行用户名和密码验证" -#: users/api/auth.py:207 +#: users/api/auth.py:208 msgid "MFA certification failed" msgstr "MFA认证失败" @@ -3448,7 +3448,7 @@ msgstr "获取更多信息" #: users/templates/users/forgot_password.html:11 #: users/templates/users/forgot_password.html:27 -#: users/templates/users/login.html:79 +#: users/templates/users/login.html:81 msgid "Forgot password" msgstr "忘记密码" @@ -3497,6 +3497,14 @@ msgstr "改变世界,从一点点开始。" msgid "Captcha invalid" msgstr "验证码错误" +#: users/templates/users/login.html:87 +msgid "More login options" +msgstr "更多登录方式" + +#: users/templates/users/login.html:91 +msgid "Keycloak" +msgstr "" + #: users/templates/users/login_otp.html:46 #: users/templates/users/user_detail.html:91 #: users/templates/users/user_profile.html:85 @@ -3995,19 +4003,19 @@ msgstr "" "
    \n" " " -#: users/utils.py:148 +#: users/utils.py:162 msgid "User not exist" msgstr "用户不存在" -#: users/utils.py:150 +#: users/utils.py:164 msgid "Disabled or expired" msgstr "禁用或失效" -#: users/utils.py:163 +#: users/utils.py:177 msgid "Password or SSH public key invalid" msgstr "密码或密钥不合法" -#: users/utils.py:286 users/utils.py:296 +#: users/utils.py:300 users/utils.py:310 msgid "Bit" msgstr " 位" @@ -4027,52 +4035,52 @@ msgstr "用户组授权资产" msgid "Please enable cookies and try again." msgstr "设置你的浏览器支持cookie" -#: users/views/login.py:179 users/views/user.py:526 users/views/user.py:551 +#: users/views/login.py:180 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:208 +#: users/views/login.py:211 msgid "Logout success" msgstr "退出登录成功" -#: users/views/login.py:209 +#: users/views/login.py:212 msgid "Logout success, return login page" msgstr "退出登录成功,返回到登录页面" -#: users/views/login.py:225 +#: users/views/login.py:228 msgid "Email address invalid, please input again" msgstr "邮箱地址错误,重新输入" -#: users/views/login.py:238 +#: users/views/login.py:241 msgid "Send reset password message" msgstr "发送重置密码邮件" -#: users/views/login.py:239 +#: users/views/login.py:242 msgid "Send reset password mail success, login your mail box and follow it " msgstr "" "发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" -#: users/views/login.py:252 +#: users/views/login.py:255 msgid "Reset password success" msgstr "重置密码成功" -#: users/views/login.py:253 +#: users/views/login.py:256 msgid "Reset password success, return to login page" msgstr "重置密码成功,返回到登录页面" -#: users/views/login.py:274 users/views/login.py:287 +#: users/views/login.py:277 users/views/login.py:290 msgid "Token invalid or expired" msgstr "Token错误或失效" -#: users/views/login.py:283 +#: users/views/login.py:286 msgid "Password not same" msgstr "密码不一致" -#: users/views/login.py:293 users/views/user.py:127 users/views/user.py:422 +#: users/views/login.py:296 users/views/user.py:127 users/views/user.py:422 msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" -#: users/views/login.py:331 +#: users/views/login.py:334 msgid "First login" msgstr "首次登陆" diff --git a/apps/users/models/user.py b/apps/users/models/user.py index bf2cae7f1..8192ae095 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -40,9 +40,11 @@ class User(AbstractUser): ) SOURCE_LOCAL = 'local' SOURCE_LDAP = 'ldap' + SOURCE_OPENID = 'openid' SOURCE_CHOICES = ( (SOURCE_LOCAL, 'Local'), (SOURCE_LDAP, 'LDAP/AD'), + (SOURCE_OPENID, 'OpenID'), ) id = models.UUIDField(default=uuid.uuid4, primary_key=True) username = models.CharField( diff --git a/apps/users/templates/users/login.html b/apps/users/templates/users/login.html index 93ed4e023..6582dd447 100644 --- a/apps/users/templates/users/login.html +++ b/apps/users/templates/users/login.html @@ -75,9 +75,23 @@

    {% endif %} - - {% trans 'Forgot password' %}? - +
    + + + {% if AUTH_OPENID %} +
    +

    {% trans "More login options" %}

    +
    + +
    + {% endif %}

    diff --git a/apps/users/views/login.py b/apps/users/views/login.py index 4f2308d04..971b1cf2d 100644 --- a/apps/users/views/login.py +++ b/apps/users/views/login.py @@ -132,6 +132,7 @@ class UserLoginView(FormView): def get_context_data(self, **kwargs): context = { 'demo_mode': os.environ.get("DEMO_MODE"), + 'AUTH_OPENID': settings.AUTH_OPENID, } kwargs.update(context) return super().get_context_data(**kwargs) @@ -200,6 +201,9 @@ class UserLogoutView(TemplateView): def get(self, request, *args, **kwargs): auth_logout(request) + next_uri = request.COOKIES.get("next") + if next_uri: + return redirect(next_uri) response = super().get(request, *args, **kwargs) return response diff --git a/config_example.py b/config_example.py index a96f0d7c9..d0fc4bff9 100644 --- a/config_example.py +++ b/config_example.py @@ -54,6 +54,14 @@ class Config: REDIS_DB_CELERY = os.environ.get('REDIS_DB') or 3 REDIS_DB_CACHE = os.environ.get('REDIS_DB') or 4 + # Use OpenID authorization + # BASE_SITE_URL = 'http://localhost:8080' + # AUTH_OPENID = False # True or False + # AUTH_OPENID_SERVER_URL = 'https://openid-auth-server.com/' + # AUTH_OPENID_REALM_NAME = 'realm-name' + # AUTH_OPENID_CLIENT_ID = 'client-id' + # AUTH_OPENID_CLIENT_SECRET = 'client-secret' + def __init__(self): pass From 0f9326bd8f62013dbab1f1c0afd8d40587e3fec4 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Fri, 9 Nov 2018 15:00:37 +0800 Subject: [PATCH 26/27] =?UTF-8?q?[Update]=20=E6=9B=B4=E6=96=B0OpenID?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=20(#2013)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements/requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index d4c53e0bd..901a55aba 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -74,3 +74,5 @@ Werkzeug==0.14.1 drf-nested-routers==0.90.2 aliyun-python-sdk-core-v3==2.9.1 aliyun-python-sdk-ecs==4.10.1 +python-keycloak==0.13.3 +python-keycloak-client==0.1.3 From 6acda27d67dd3a6f630ec5682ab942f99c5a03b5 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sun, 11 Nov 2018 10:38:15 +0800 Subject: [PATCH 27/27] =?UTF-8?q?[Update]=20=E6=9B=B4=E6=96=B0=E7=89=88?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/__init__.py | 2 +- apps/templates/_footer.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/__init__.py b/apps/__init__.py index e8c9d2d6e..414812ccc 100644 --- a/apps/__init__.py +++ b/apps/__init__.py @@ -2,4 +2,4 @@ # -*- coding: utf-8 -*- # -__version__ = "1.4.3" +__version__ = "1.4.4" diff --git a/apps/templates/_footer.html b/apps/templates/_footer.html index 12c285f92..cc63b1bef 100644 --- a/apps/templates/_footer.html +++ b/apps/templates/_footer.html @@ -1,7 +1,7 @@ {% load i18n %}