diff --git a/apps/assets/api/admin_user.py b/apps/assets/api/admin_user.py
index 7048ce461..8d30ee9d9 100644
--- a/apps/assets/api/admin_user.py
+++ b/apps/assets/api/admin_user.py
@@ -17,6 +17,7 @@ from django.db import transaction
from rest_framework import generics
from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
+from rest_framework.pagination import LimitOffsetPagination
from common.mixins import IDInFilterMixin
from common.utils import get_logger
@@ -37,9 +38,17 @@ class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet):
"""
Admin user api set, for add,delete,update,list,retrieve resource
"""
+
+ filter_fields = ("name", "username")
+ search_fields = filter_fields
queryset = AdminUser.objects.all()
serializer_class = serializers.AdminUserSerializer
permission_classes = (IsOrgAdmin,)
+ pagination_class = LimitOffsetPagination
+
+ def get_queryset(self):
+ queryset = super().get_queryset().all()
+ return queryset
class AdminUserAuthApi(generics.UpdateAPIView):
diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py
index 54f0bacc2..92a1775d0 100644
--- a/apps/assets/api/asset.py
+++ b/apps/assets/api/asset.py
@@ -53,14 +53,14 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
if show_current_asset:
self.queryset = self.queryset.filter(
Q(nodes=node_id) | Q(nodes__isnull=True)
- ).distinct()
+ )
return
if show_current_asset:
- self.queryset = self.queryset.filter(nodes=node).distinct()
+ self.queryset = self.queryset.filter(nodes=node)
else:
self.queryset = self.queryset.filter(
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
- ).distinct()
+ )
def filter_admin_user_id(self):
admin_user_id = self.request.query_params.get('admin_user_id')
diff --git a/apps/assets/api/cmd_filter.py b/apps/assets/api/cmd_filter.py
index 14afc0ae3..cecdf2432 100644
--- a/apps/assets/api/cmd_filter.py
+++ b/apps/assets/api/cmd_filter.py
@@ -2,6 +2,7 @@
#
from rest_framework_bulk import BulkModelViewSet
+from rest_framework.pagination import LimitOffsetPagination
from django.shortcuts import get_object_or_404
from ..hands import IsOrgAdmin
@@ -13,14 +14,20 @@ __all__ = ['CommandFilterViewSet', 'CommandFilterRuleViewSet']
class CommandFilterViewSet(BulkModelViewSet):
+ filter_fields = ("name",)
+ search_fields = filter_fields
permission_classes = (IsOrgAdmin,)
queryset = CommandFilter.objects.all()
serializer_class = serializers.CommandFilterSerializer
+ pagination_class = LimitOffsetPagination
class CommandFilterRuleViewSet(BulkModelViewSet):
+ filter_fields = ("content",)
+ search_fields = filter_fields
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.CommandFilterRuleSerializer
+ pagination_class = LimitOffsetPagination
def get_queryset(self):
fpk = self.kwargs.get('filter_pk')
diff --git a/apps/assets/api/domain.py b/apps/assets/api/domain.py
index 37bebfb84..2e24829dc 100644
--- a/apps/assets/api/domain.py
+++ b/apps/assets/api/domain.py
@@ -2,6 +2,7 @@
from rest_framework_bulk import BulkModelViewSet
from rest_framework.views import APIView, Response
+from rest_framework.pagination import LimitOffsetPagination
from django.views.generic.detail import SingleObjectMixin
@@ -20,6 +21,11 @@ class DomainViewSet(BulkModelViewSet):
queryset = Domain.objects.all()
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.DomainSerializer
+ pagination_class = LimitOffsetPagination
+
+ def get_queryset(self):
+ queryset = super().get_queryset().all()
+ return queryset
def get_serializer_class(self):
if self.request.query_params.get('gateway'):
@@ -33,11 +39,12 @@ class DomainViewSet(BulkModelViewSet):
class GatewayViewSet(BulkModelViewSet):
- filter_fields = ("domain",)
+ filter_fields = ("domain__name", "name", "username", "ip")
search_fields = filter_fields
queryset = Gateway.objects.all()
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.GatewaySerializer
+ pagination_class = LimitOffsetPagination
class GatewayTestConnectionApi(SingleObjectMixin, APIView):
diff --git a/apps/assets/api/label.py b/apps/assets/api/label.py
index e5391c76a..eb8594e4a 100644
--- a/apps/assets/api/label.py
+++ b/apps/assets/api/label.py
@@ -14,6 +14,7 @@
# limitations under the License.
from rest_framework_bulk import BulkModelViewSet
+from rest_framework.pagination import LimitOffsetPagination
from django.db.models import Count
from common.utils import get_logger
@@ -27,8 +28,11 @@ __all__ = ['LabelViewSet']
class LabelViewSet(BulkModelViewSet):
+ filter_fields = ("name", "value")
+ search_fields = filter_fields
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.LabelSerializer
+ pagination_class = LimitOffsetPagination
def list(self, request, *args, **kwargs):
if request.query_params.get("distinct"):
diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py
index 16d475c35..3c1d0b3bd 100644
--- a/apps/assets/api/system_user.py
+++ b/apps/assets/api/system_user.py
@@ -42,9 +42,16 @@ class SystemUserViewSet(BulkModelViewSet):
"""
System user api set, for add,delete,update,list,retrieve resource
"""
+ filter_fields = ("name", "username")
+ search_fields = filter_fields
queryset = SystemUser.objects.all()
serializer_class = serializers.SystemUserSerializer
permission_classes = (IsOrgAdminOrAppUser,)
+ pagination_class = LimitOffsetPagination
+
+ def get_queryset(self):
+ queryset = super().get_queryset().all()
+ return queryset
class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py
index e035f7ca1..3d5c50997 100644
--- a/apps/assets/models/node.py
+++ b/apps/assets/models/node.py
@@ -121,10 +121,10 @@ class Node(OrgModelMixin):
def get_assets(self):
from .asset import Asset
if self.is_default_node():
- assets = Asset.objects.filter(nodes__isnull=True)
+ assets = Asset.objects.filter(Q(nodes__id=self.id) | Q(nodes__isnull=True))
else:
assets = Asset.objects.filter(nodes__id=self.id)
- return assets
+ return assets.distinct()
def get_valid_assets(self):
return self.get_assets().valid()
diff --git a/apps/assets/templates/assets/admin_user_list.html b/apps/assets/templates/assets/admin_user_list.html
index 16fcd1950..25ee56fba 100644
--- a/apps/assets/templates/assets/admin_user_list.html
+++ b/apps/assets/templates/assets/admin_user_list.html
@@ -93,7 +93,7 @@ $(document).ready(function(){
columns: [{data: function(){return ""}}, {data: "name" }, {data: "username" }, {data: "assets_amount" },
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" }]
};
- jumpserver.initDataTable(options);
+ jumpserver.initServerSideDataTable(options)
})
.on('click', '.btn_admin_user_delete', function () {
diff --git a/apps/assets/templates/assets/cmd_filter_list.html b/apps/assets/templates/assets/cmd_filter_list.html
index 78060177b..3a4feeae0 100644
--- a/apps/assets/templates/assets/cmd_filter_list.html
+++ b/apps/assets/templates/assets/cmd_filter_list.html
@@ -66,7 +66,7 @@ function initTable() {
],
op_html: $('#actions').html()
};
- jumpserver.initDataTable(options);
+ jumpserver.initServerSideDataTable(options);
}
$(document).ready(function(){
initTable();
diff --git a/apps/assets/templates/assets/cmd_filter_rule_list.html b/apps/assets/templates/assets/cmd_filter_rule_list.html
index 119b44fd4..78c3a36d5 100644
--- a/apps/assets/templates/assets/cmd_filter_rule_list.html
+++ b/apps/assets/templates/assets/cmd_filter_rule_list.html
@@ -95,7 +95,7 @@ function initTable() {
],
op_html: $('#actions').html()
};
- jumpserver.initDataTable(options);
+ jumpserver.initServerSideDataTable(options);
}
$(document).ready(function(){
initTable();
diff --git a/apps/assets/templates/assets/domain_gateway_list.html b/apps/assets/templates/assets/domain_gateway_list.html
index 43e8f43df..e7a3467e3 100644
--- a/apps/assets/templates/assets/domain_gateway_list.html
+++ b/apps/assets/templates/assets/domain_gateway_list.html
@@ -98,7 +98,7 @@ function initTable() {
],
op_html: $('#actions').html()
};
- jumpserver.initDataTable(options);
+ jumpserver.initServerSideDataTable(options);
}
$(document).ready(function(){
initTable();
diff --git a/apps/assets/templates/assets/domain_list.html b/apps/assets/templates/assets/domain_list.html
index 6913671f4..a0c6e869e 100644
--- a/apps/assets/templates/assets/domain_list.html
+++ b/apps/assets/templates/assets/domain_list.html
@@ -62,7 +62,7 @@ function initTable() {
],
op_html: $('#actions').html()
};
- jumpserver.initDataTable(options);
+ jumpserver.initServerSideDataTable(options);
}
$(document).ready(function(){
initTable();
diff --git a/apps/assets/templates/assets/label_list.html b/apps/assets/templates/assets/label_list.html
index 1c1b380d5..d2fa9958a 100644
--- a/apps/assets/templates/assets/label_list.html
+++ b/apps/assets/templates/assets/label_list.html
@@ -47,7 +47,7 @@ function initTable() {
],
op_html: $('#actions').html()
};
- jumpserver.initDataTable(options);
+ jumpserver.initServerSideDataTable(options);
}
$(document).ready(function(){
initTable();
diff --git a/apps/assets/templates/assets/system_user_list.html b/apps/assets/templates/assets/system_user_list.html
index 2d1358e46..f7c2a613b 100644
--- a/apps/assets/templates/assets/system_user_list.html
+++ b/apps/assets/templates/assets/system_user_list.html
@@ -100,7 +100,7 @@ function initTable() {
],
op_html: $('#actions').html()
};
- jumpserver.initDataTable(options);
+ jumpserver.initServerSideDataTable(options);
}
$(document).ready(function(){
diff --git a/apps/assets/templates/assets/user_asset_list.html b/apps/assets/templates/assets/user_asset_list.html
index dc799e734..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/assets/views/label.py b/apps/assets/views/label.py
index de30eaaa3..9ed289e3e 100644
--- a/apps/assets/views/label.py
+++ b/apps/assets/views/label.py
@@ -36,6 +36,7 @@ class LabelCreateView(AdminUserRequiredMixin, CreateView):
form_class = LabelForm
success_url = reverse_lazy('assets:label-list')
success_message = create_success_msg
+ disable_name = ['draw', 'search', 'limit', 'offset', '_']
def get_context_data(self, **kwargs):
context = {
@@ -45,6 +46,16 @@ class LabelCreateView(AdminUserRequiredMixin, CreateView):
kwargs.update(context)
return super().get_context_data(**kwargs)
+ def form_valid(self, form):
+ name = form.cleaned_data.get('name')
+ if name in self.disable_name:
+ msg = _(
+ 'Tips: Avoid using label names reserved internally: {}'
+ ).format(', '.join(self.disable_name))
+ form.add_error("name", msg)
+ return self.form_invalid(form)
+ return super().form_valid(form)
+
class LabelUpdateView(AdminUserRequiredMixin, UpdateView):
model = Label
diff --git a/apps/audits/views.py b/apps/audits/views.py
index 4b1c11c16..c64bbe5b4 100644
--- a/apps/audits/views.py
+++ b/apps/audits/views.py
@@ -160,8 +160,12 @@ class LoginLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
return users
def get_queryset(self):
- users = self.get_org_users()
- queryset = super().get_queryset().filter(username__in=users)
+ if current_org.is_default():
+ queryset = super().get_queryset()
+ else:
+ users = self.get_org_users()
+ queryset = super().get_queryset().filter(username__in=users)
+
self.user = self.request.GET.get('user', '')
self.keyword = self.request.GET.get("keyword", '')
diff --git a/apps/common/api.py b/apps/common/api.py
index e09cf9726..51416fa3b 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
@@ -8,8 +11,9 @@ from django.core.mail import get_connection, send_mail
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
-from .permissions import IsOrgAdmin
+from .permissions import IsOrgAdmin, IsSuperUser
from .serializers import MailTestSerializer, LDAPTestSerializer
+from .models import Setting
class MailTestingAPI(APIView):
@@ -85,6 +89,79 @@ class LDAPTestingAPI(APIView):
return Response({"error": str(serializer.errors)}, status=401)
+class ReplayStorageCreateAPI(APIView):
+ permission_classes = (IsSuperUser,)
+
+ 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)
+ return storage.is_valid(src, target)
+
+
+class ReplayStorageDeleteAPI(APIView):
+ permission_classes = (IsSuperUser,)
+
+ 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 = (IsSuperUser,)
+
+ 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
+ try:
+ storage = jms_storage.get_log_storage(storage_data)
+ except Exception:
+ return False
+
+ return storage.ping()
+
+
+class CommandStorageDeleteAPI(APIView):
+ permission_classes = (IsSuperUser,)
+
+ 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..10ac4ec17 100644
--- a/apps/common/forms.py
+++ b/apps/common/forms.py
@@ -135,32 +135,24 @@ 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")
)
+class TerminalCommandStorage(BaseForm):
+ pass
+
+
class SecuritySettingForm(BaseForm):
# MFA global setting
SECURITY_MFA_AUTH = forms.BooleanField(
diff --git a/apps/common/mixins.py b/apps/common/mixins.py
index ceeaed6c1..0a7d15fef 100644
--- a/apps/common/mixins.py
+++ b/apps/common/mixins.py
@@ -117,6 +117,3 @@ class DatetimeSearchMixin:
def get(self, request, *args, **kwargs):
self.get_date_range()
return super().get(request, *args, **kwargs)
-
-
-
diff --git a/apps/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/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/templates/common/command_storage_create.html b/apps/common/templates/common/command_storage_create.html
new file mode 100644
index 000000000..a3a83f2e7
--- /dev/null
+++ b/apps/common/templates/common/command_storage_create.html
@@ -0,0 +1,176 @@
+{#{% extends 'base.html' %}#}
+{% extends '_base_create_update.html' %}
+{% load static %}
+{% load bootstrap3 %}
+{% load i18n %}
+{% load common_tags %}
+
+{% block content %}
+
+{% 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..6382d27e1
--- /dev/null
+++ b/apps/common/templates/common/replay_storage_create.html
@@ -0,0 +1,248 @@
+{#{% extends 'base.html' %}#}
+{% extends '_base_create_update.html' %}
+{% load static %}
+{% load bootstrap3 %}
+{% load i18n %}
+{% load common_tags %}
+
+{% block content %}
+
+{% 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..0435e48e9 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 @@
{% trans 'Name' %} |
{% trans 'Type' %} |
+ {% trans 'Action' %} |
@@ -96,18 +109,14 @@
{{ name }} |
{{ setting.TYPE }} |
+ {% trans 'Delete' %} |
{% endfor %}
+ {% 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..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
@@ -387,6 +388,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/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo
index d88a5bfae..9f41c1d67 100644
Binary files a/apps/locale/zh/LC_MESSAGES/django.mo and b/apps/locale/zh/LC_MESSAGES/django.mo differ
diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po
index 4c0305a50..0f8a6448f 100644
--- a/apps/locale/zh/LC_MESSAGES/django.po
+++ b/apps/locale/zh/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-10-16 16:03+0800\n"
+"POT-Creation-Date: 2018-11-01 13:58+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler \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
@@ -188,7 +191,7 @@ msgstr "名称"
#: assets/templates/assets/system_user_list.html:30
#: audits/templates/audits/login_log_list.html:49
#: perms/templates/perms/asset_permission_user.html:55 users/forms.py:15
-#: users/forms.py:33 users/models/authentication.py:70 users/models/user.py:49
+#: users/forms.py:33 users/models/authentication.py:72 users/models/user.py:49
#: users/templates/users/_select_user_modal.html:14
#: users/templates/users/login.html:62
#: users/templates/users/user_detail.html:67
@@ -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
@@ -657,8 +666,8 @@ msgstr "手动登录"
#: assets/views/domain.py:29 assets/views/domain.py:45
#: assets/views/domain.py:61 assets/views/domain.py:74
#: assets/views/domain.py:98 assets/views/domain.py:126
-#: assets/views/domain.py:145 assets/views/label.py:26 assets/views/label.py:42
-#: assets/views/label.py:58 assets/views/system_user.py:28
+#: assets/views/domain.py:145 assets/views/label.py:26 assets/views/label.py:43
+#: assets/views/label.py:69 assets/views/system_user.py:28
#: assets/views/system_user.py:44 assets/views/system_user.py:60
#: assets/views/system_user.py:74 templates/_nav.html:19
msgid "Assets"
@@ -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:80
#: 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:81
#: 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,19 +1046,20 @@ 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
#: users/templates/users/user_detail.html:431
#: users/templates/users/user_detail.html:476
#: users/templates/users/user_group_create_update.html:32
-#: users/templates/users/user_group_list.html:87
+#: users/templates/users/user_group_list.html:88
#: users/templates/users/user_list.html:201
#: users/templates/users/user_profile.html:232
#: xpack/plugins/cloud/templates/cloud/account_create_update.html:34
@@ -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!"
@@ -1260,7 +1276,7 @@ msgstr "重命名失败,不能更改root节点的名称"
#: users/templates/users/user_detail.html:376
#: users/templates/users/user_detail.html:402
#: users/templates/users/user_detail.html:470
-#: users/templates/users/user_group_list.html:81
+#: users/templates/users/user_group_list.html:82
#: users/templates/users/user_list.html:195
msgid "Are you sure?"
msgstr "你确认吗?"
@@ -1271,11 +1287,12 @@ 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
#: users/templates/users/user_group_create_update.html:31
-#: users/templates/users/user_group_list.html:85
+#: users/templates/users/user_group_list.html:86
#: users/templates/users/user_list.html:199
#: xpack/plugins/orgs/templates/orgs/org_create_update.html:32
msgid "Cancel"
@@ -1346,7 +1363,7 @@ msgstr "创建命令过滤器"
#: assets/templates/assets/cmd_filter_rule_list.html:33
#: assets/views/cmd_filter.py:98
msgid "Command filter rule list"
-msgstr "命令过滤器列表"
+msgstr "命令过滤器规则列表"
#: assets/templates/assets/cmd_filter_rule_list.html:50
msgid "Create rule"
@@ -1407,7 +1424,7 @@ msgstr "JMS => 网域网关 => 目标资产"
msgid "Create domain"
msgstr "创建网域"
-#: assets/templates/assets/label_list.html:6 assets/views/label.py:43
+#: assets/templates/assets/label_list.html:6 assets/views/label.py:44
msgid "Create label"
msgstr "创建标签"
@@ -1575,7 +1592,11 @@ msgstr "创建网关"
msgid "Label list"
msgstr "标签列表"
-#: assets/views/label.py:59
+#: assets/views/label.py:53
+msgid "Tips: Avoid using label names reserved internally: {}"
+msgstr "提示: 请避免使用内部预留标签名: {}"
+
+#: assets/views/label.py:70
msgid "Update label"
msgstr "更新标签"
@@ -1618,7 +1639,7 @@ msgid "Filename"
msgstr "文件名"
#: audits/models.py:22 audits/templates/audits/ftp_log_list.html:76
-#: ops/templates/ops/task_list.html:39 users/models/authentication.py:66
+#: ops/templates/ops/task_list.html:39 users/models/authentication.py:68
#: users/templates/users/user_detail.html:452 xpack/plugins/cloud/api.py:61
msgid "Success"
msgstr "成功"
@@ -1686,19 +1707,19 @@ msgid "City"
msgstr "城市"
#: audits/templates/audits/login_log_list.html:54 users/forms.py:169
-#: users/models/authentication.py:75 users/models/user.py:73
+#: users/models/authentication.py:77 users/models/user.py:73
#: users/templates/users/first_login.html:45
msgid "MFA"
msgstr "MFA"
#: audits/templates/audits/login_log_list.html:55
-#: users/models/authentication.py:76 xpack/plugins/cloud/models.py:192
+#: users/models/authentication.py:78 xpack/plugins/cloud/models.py:192
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:69
msgid "Reason"
msgstr "原因"
#: audits/templates/audits/login_log_list.html:56
-#: users/models/authentication.py:77 xpack/plugins/cloud/models.py:191
+#: users/models/authentication.py:79 xpack/plugins/cloud/models.py:191
#: xpack/plugins/cloud/models.py:208
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:67
@@ -1735,34 +1756,47 @@ msgstr "操作日志"
msgid "Password change log"
msgstr "改密日志"
-#: audits/views.py:183 templates/_nav.html:10 users/views/group.py:28
+#: audits/views.py:187 templates/_nav.html:10 users/views/group.py:28
#: users/views/group.py:44 users/views/group.py:60 users/views/group.py:76
-#: users/views/group.py:92 users/views/login.py:327 users/views/user.py:68
+#: users/views/group.py:92 users/views/login.py:331 users/views/user.py:68
#: users/views/user.py:83 users/views/user.py:111 users/views/user.py:193
#: users/views/user.py:354 users/views/user.py:404 users/views/user.py:439
msgid "Users"
msgstr "用户管理"
-#: audits/views.py:184 templates/_nav.html:76
+#: audits/views.py:188 templates/_nav.html:76
msgid "Login log"
msgstr "登录日志"
-#: 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:139
+msgid "Error: Account invalid"
+msgstr ""
+
+#: common/api.py:110 common/api.py:142
+msgid "Create succeed"
+msgstr "创建成功"
+
+#: common/api.py:128 common/api.py:162
+#: common/templates/common/terminal_setting.html:151
+msgid "Delete succeed"
+msgstr "删除成功"
+
#: common/const.py:6
#, python-format
msgid "%(name)s was created successfully"
@@ -1879,120 +1913,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:160
msgid "MFA Secondary certification"
msgstr "MFA 二次认证"
-#: common/forms.py:170
+#: common/forms.py:162
msgid ""
"After opening, the user login must use MFA secondary authentication (valid "
"for all users, including administrators)"
msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)"
-#: common/forms.py:177
+#: common/forms.py:169
msgid "Limit the number of login failures"
msgstr "限制登录失败次数"
-#: common/forms.py:182
+#: common/forms.py:174
msgid "No logon interval"
msgstr "禁止登录时间间隔"
-#: common/forms.py:184
+#: common/forms.py:176
msgid ""
"Tip :(unit/minute) if the user has failed to log in for a limited number of "
"times, no login is allowed during this time interval."
msgstr ""
"提示: (单位: 分钟) 当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录."
-#: common/forms.py:190
+#: common/forms.py:182
msgid "Connection max idle time"
msgstr "SSH最大空闲时间"
-#: common/forms.py:192
+#: common/forms.py:184
msgid ""
"If idle time more than it, disconnect connection(only ssh now) Unit: minute"
msgstr "提示: (单位: 分钟) 如果超过该配置没有操作,连接会被断开(仅ssh) "
-#: common/forms.py:198
+#: common/forms.py:190
msgid "Password minimum length"
msgstr "密码最小长度 "
-#: common/forms.py:204
+#: common/forms.py:196
msgid "Must contain capital letters"
msgstr "必须包含大写字母"
-#: common/forms.py:206
+#: common/forms.py:198
msgid ""
"After opening, the user password changes and resets must contain uppercase "
"letters"
msgstr "开启后,用户密码修改、重置必须包含大写字母"
-#: common/forms.py:212
+#: common/forms.py:204
msgid "Must contain lowercase letters"
msgstr "必须包含小写字母"
-#: common/forms.py:213
+#: common/forms.py:205
msgid ""
"After opening, the user password changes and resets must contain lowercase "
"letters"
msgstr "开启后,用户密码修改、重置必须包含小写字母"
-#: common/forms.py:219
+#: common/forms.py:211
msgid "Must contain numeric characters"
msgstr "必须包含数字字符"
-#: common/forms.py:220
+#: common/forms.py:212
msgid ""
"After opening, the user password changes and resets must contain numeric "
"characters"
msgstr "开启后,用户密码修改、重置必须包含数字字符"
-#: common/forms.py:226
+#: common/forms.py:218
msgid "Must contain special characters"
msgstr "必须包含特殊字符"
-#: common/forms.py:227
+#: common/forms.py:219
msgid ""
"After opening, the user password changes and resets must contain special "
"characters"
@@ -2016,7 +2028,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 +2036,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 +2044,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 +2052,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 +2060,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:64
+msgid "Index"
+msgstr "索引"
+
+#: common/templates/common/command_storage_create.html:71
+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 +2134,59 @@ msgstr "用户登录设置"
msgid "Password check rule"
msgstr "密码校验规则"
+#: common/templates/common/terminal_setting.html:76 terminal/forms.py:27
+#: 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:32
+#: terminal/models.py:23
+msgid "Replay storage"
+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 +2230,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 "再次执行"
@@ -2373,23 +2481,13 @@ msgstr "用户或用户组"
#: perms/templates/perms/asset_permission_asset.html:27
#: perms/templates/perms/asset_permission_detail.html:27
#: perms/templates/perms/asset_permission_user.html:27
-msgid "Assets and asset groups"
-msgstr "资产或资产组"
+msgid "Assets and node"
+msgstr "资产或节点"
#: perms/templates/perms/asset_permission_asset.html:80
msgid "Add asset to this permission"
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 +2763,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 +2881,12 @@ msgstr "输入"
msgid "Session"
msgstr "会话"
-#: terminal/forms.py:31
+#: terminal/forms.py:28
msgid "Command can store in server db or ES, default to server, more see docs"
msgstr ""
"命令支持存储到服务器端数据库、ES中,默认存储的服务器端数据库,更多查看文档"
-#: terminal/forms.py:36
+#: terminal/forms.py:33
msgid ""
"Replay file can store in server disk, AWS S3, Aliyun OSS, default to server, "
"more see docs"
@@ -2994,19 +3088,19 @@ msgstr "你可以使用ssh客户端工具连接终端"
msgid "Log in frequently and try again later"
msgstr "登录频繁, 稍后重试"
-#: users/api/auth.py:79
+#: users/api/auth.py:82
msgid "Please carry seed value and conduct MFA secondary certification"
msgstr "请携带seed值, 进行MFA二次认证"
-#: users/api/auth.py:192
+#: users/api/auth.py:195
msgid "Please verify the user name and password first"
msgstr "请先进行用户名和密码验证"
-#: users/api/auth.py:204
+#: users/api/auth.py:207
msgid "MFA certification failed"
msgstr "MFA认证失败"
-#: users/api/user.py:135
+#: users/api/user.py:138
msgid "Could not reset self otp, use profile reset instead"
msgstr "不能再该页面重置MFA, 请去个人信息页面重置"
@@ -3175,40 +3269,44 @@ msgstr "ssh密钥"
msgid "Disabled"
msgstr "禁用"
-#: users/models/authentication.py:52 users/models/authentication.py:60
+#: users/models/authentication.py:52 users/models/authentication.py:61
msgid "-"
msgstr ""
-#: users/models/authentication.py:61
+#: users/models/authentication.py:62
msgid "Username/password check failed"
msgstr "用户名/密码 校验失败"
-#: users/models/authentication.py:62
+#: users/models/authentication.py:63
msgid "MFA authentication failed"
msgstr "MFA 认证失败"
-#: users/models/authentication.py:67 xpack/plugins/cloud/models.py:184
+#: users/models/authentication.py:64
+msgid "Username does not exist"
+msgstr "用户名不存在"
+
+#: users/models/authentication.py:69 xpack/plugins/cloud/models.py:184
#: xpack/plugins/cloud/models.py:198
msgid "Failed"
msgstr "失败"
-#: users/models/authentication.py:71
+#: users/models/authentication.py:73
msgid "Login type"
msgstr "登录方式"
-#: users/models/authentication.py:72
+#: users/models/authentication.py:74
msgid "Login ip"
msgstr "登录IP"
-#: users/models/authentication.py:73
+#: users/models/authentication.py:75
msgid "Login city"
msgstr "登录城市"
-#: users/models/authentication.py:74
+#: users/models/authentication.py:76
msgid "User agent"
msgstr "Agent"
-#: users/models/authentication.py:78
+#: users/models/authentication.py:80
msgid "Date login"
msgstr "登录日期"
@@ -3641,20 +3739,20 @@ msgstr "添加用户"
msgid "Create user group"
msgstr "创建用户组"
-#: users/templates/users/user_group_list.html:82
+#: users/templates/users/user_group_list.html:83
msgid "This will delete the selected groups !!!"
msgstr "删除选择组"
-#: users/templates/users/user_group_list.html:91
+#: users/templates/users/user_group_list.html:92
msgid "UserGroups Deleted."
msgstr "用户组删除"
-#: users/templates/users/user_group_list.html:92
-#: users/templates/users/user_group_list.html:97
+#: users/templates/users/user_group_list.html:93
+#: users/templates/users/user_group_list.html:98
msgid "UserGroups Delete"
msgstr "用户组删除"
-#: users/templates/users/user_group_list.html:96
+#: users/templates/users/user_group_list.html:97
msgid "UserGroup Deleting failed."
msgstr "用户组删除失败"
@@ -3925,56 +4023,56 @@ msgstr "更新用户组"
msgid "User group granted asset"
msgstr "用户组授权资产"
-#: users/views/login.py:69
+#: users/views/login.py:70
msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie"
-#: users/views/login.py:175 users/views/user.py:526 users/views/user.py:551
+#: users/views/login.py:179 users/views/user.py:526 users/views/user.py:551
msgid "MFA code invalid, or ntp sync server time"
msgstr "MFA验证码不正确,或者服务器端时间不对"
-#: users/views/login.py:204
+#: users/views/login.py:208
msgid "Logout success"
msgstr "退出登录成功"
-#: users/views/login.py:205
+#: users/views/login.py:209
msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面"
-#: users/views/login.py:221
+#: users/views/login.py:225
msgid "Email address invalid, please input again"
msgstr "邮箱地址错误,重新输入"
-#: users/views/login.py:234
+#: users/views/login.py:238
msgid "Send reset password message"
msgstr "发送重置密码邮件"
-#: users/views/login.py:235
+#: users/views/login.py:239
msgid "Send reset password mail success, login your mail box and follow it "
msgstr ""
"发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)"
-#: users/views/login.py:248
+#: users/views/login.py:252
msgid "Reset password success"
msgstr "重置密码成功"
-#: users/views/login.py:249
+#: users/views/login.py:253
msgid "Reset password success, return to login page"
msgstr "重置密码成功,返回到登录页面"
-#: users/views/login.py:270 users/views/login.py:283
+#: users/views/login.py:274 users/views/login.py:287
msgid "Token invalid or expired"
msgstr "Token错误或失效"
-#: users/views/login.py:279
+#: users/views/login.py:283
msgid "Password not same"
msgstr "密码不一致"
-#: users/views/login.py:289 users/views/user.py:127 users/views/user.py:422
+#: users/views/login.py:293 users/views/user.py:127 users/views/user.py:422
msgid "* Your password does not meet the requirements"
msgstr "* 您的密码不符合要求"
-#: users/views/login.py:327
+#: users/views/login.py:331
msgid "First login"
msgstr "首次登陆"
@@ -4160,12 +4258,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 +4431,21 @@ msgstr "创建组织"
msgid "Update org"
msgstr "更新组织"
+#, fuzzy
+#~| msgid "Delete succeed"
+#~ msgid "Delete success"
+#~ msgstr "删除成功"
+
+#~ msgid ""
+#~ "Set terminal storage setting, `default` is the using as default,You can "
+#~ "set other storage and some terminal using"
+#~ 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 "同步实例任务详情"
diff --git a/apps/ops/inventory.py b/apps/ops/inventory.py
index 117d961a9..5b5e98551 100644
--- a/apps/ops/inventory.py
+++ b/apps/ops/inventory.py
@@ -50,7 +50,7 @@ class JMSInventory(BaseInventory):
def convert_to_ansible(self, asset, run_as_admin=False):
info = {
'id': asset.id,
- 'hostname': asset.hostname,
+ 'hostname': asset.fullname,
'ip': asset.ip,
'port': asset.port,
'vars': dict(),
diff --git a/apps/ops/views.py b/apps/ops/views.py
index 380fdadf4..1bbfbfc23 100644
--- a/apps/ops/views.py
+++ b/apps/ops/views.py
@@ -6,7 +6,7 @@ from django.views.generic import ListView, DetailView, TemplateView
from common.mixins import DatetimeSearchMixin
from .models import Task, AdHoc, AdHocRunHistory, CeleryTask
-from common.permissions import SuperUserRequiredMixin
+from common.permissions import SuperUserRequiredMixin, AdminUserRequiredMixin
class TaskListView(SuperUserRequiredMixin, DatetimeSearchMixin, ListView):
@@ -121,6 +121,6 @@ class AdHocHistoryDetailView(SuperUserRequiredMixin, DetailView):
return super().get_context_data(**kwargs)
-class CeleryTaskLogView(SuperUserRequiredMixin, DetailView):
+class CeleryTaskLogView(AdminUserRequiredMixin, DetailView):
template_name = 'ops/celery_task_log.html'
model = CeleryTask
diff --git a/apps/orgs/api.py b/apps/orgs/api.py
index 7da77cfce..a99715d47 100644
--- a/apps/orgs/api.py
+++ b/apps/orgs/api.py
@@ -1,14 +1,68 @@
# -*- coding: utf-8 -*-
#
-from rest_framework import viewsets
+from rest_framework import status
+from rest_framework.views import Response
+from rest_framework_bulk import BulkModelViewSet
from common.permissions import IsSuperUserOrAppUser
from .models import Organization
-from .serializers import OrgSerializer
+from .serializers import OrgSerializer, OrgReadSerializer, \
+ OrgMembershipUserSerializer, OrgMembershipAdminSerializer
+from users.models import User, UserGroup
+from assets.models import Asset, Domain, AdminUser, SystemUser, Label
+from perms.models import AssetPermission
+from orgs.utils import current_org
+from common.utils import get_logger
+from .mixins import OrgMembershipModelViewSetMixin
+
+logger = get_logger(__file__)
-class OrgViewSet(viewsets.ModelViewSet):
+class OrgViewSet(BulkModelViewSet):
queryset = Organization.objects.all()
serializer_class = OrgSerializer
permission_classes = (IsSuperUserOrAppUser,)
+ org = None
+
+ def get_serializer_class(self):
+ if self.action in ('list', 'retrieve'):
+ return OrgReadSerializer
+ else:
+ return super().get_serializer_class()
+
+ def get_data_from_model(self, model):
+ if model == User:
+ data = model.objects.filter(orgs__id=self.org.id)
+ else:
+ data = model.objects.filter(org_id=self.org.id)
+ return data
+
+ def destroy(self, request, *args, **kwargs):
+ self.org = self.get_object()
+ models = [
+ User, UserGroup,
+ Asset, Domain, AdminUser, SystemUser, Label,
+ AssetPermission,
+ ]
+ for model in models:
+ data = self.get_data_from_model(model)
+ if data:
+ return Response(status=status.HTTP_400_BAD_REQUEST)
+ else:
+ if str(current_org) == str(self.org):
+ return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
+ self.org.delete()
+ return Response({'msg': True}, status=status.HTTP_200_OK)
+
+
+class OrgMembershipAdminsViewSet(OrgMembershipModelViewSetMixin, BulkModelViewSet):
+ serializer_class = OrgMembershipAdminSerializer
+ membership_class = Organization.admins.through
+ permission_classes = (IsSuperUserOrAppUser, )
+
+
+class OrgMembershipUsersViewSet(OrgMembershipModelViewSetMixin, BulkModelViewSet):
+ serializer_class = OrgMembershipUserSerializer
+ membership_class = Organization.users.through
+ permission_classes = (IsSuperUserOrAppUser, )
diff --git a/apps/orgs/mixins.py b/apps/orgs/mixins.py
index 917a220bf..497377520 100644
--- a/apps/orgs/mixins.py
+++ b/apps/orgs/mixins.py
@@ -9,7 +9,6 @@ from django.forms import ModelForm
from django.http.response import HttpResponseForbidden
from django.core.exceptions import ValidationError
-
from common.utils import get_logger
from .utils import current_org, set_current_org, set_to_root_org
from .models import Organization
@@ -19,7 +18,7 @@ tl = Local()
__all__ = [
'OrgManager', 'OrgViewGenericMixin', 'OrgModelMixin', 'OrgModelForm',
- 'RootOrgViewMixin',
+ 'RootOrgViewMixin', 'OrgMembershipSerializerMixin', 'OrgMembershipModelViewSetMixin'
]
@@ -176,3 +175,29 @@ class OrgModelForm(ModelForm):
continue
model = field.queryset.model
field.queryset = model.objects.all()
+
+
+class OrgMembershipSerializerMixin:
+ def run_validation(self, initial_data=None):
+ initial_data['organization'] = str(self.context['org'].id)
+ return super().run_validation(initial_data)
+
+
+class OrgMembershipModelViewSetMixin:
+ org = None
+ membership_class = None
+ lookup_field = 'user'
+ lookup_url_kwarg = 'user_id'
+ http_method_names = ['get', 'post', 'delete', 'head', 'options']
+
+ def dispatch(self, request, *args, **kwargs):
+ self.org = Organization.objects.get(pk=kwargs.get('org_id'))
+ return super().dispatch(request, *args, **kwargs)
+
+ def get_serializer_context(self):
+ context = super().get_serializer_context()
+ context['org'] = self.org
+ return context
+
+ def get_queryset(self):
+ return self.membership_class.objects.filter(organization=self.org)
diff --git a/apps/orgs/serializers.py b/apps/orgs/serializers.py
index 8a2de4582..679125195 100644
--- a/apps/orgs/serializers.py
+++ b/apps/orgs/serializers.py
@@ -1,10 +1,81 @@
from rest_framework.serializers import ModelSerializer
+from rest_framework import serializers
+from rest_framework_bulk import BulkListSerializer
+
+from users.models import User, UserGroup
+from assets.models import Asset, Domain, AdminUser, SystemUser, Label
+from perms.models import AssetPermission
+from .utils import set_current_org, get_current_org
from .models import Organization
+from .mixins import OrgMembershipSerializerMixin
class OrgSerializer(ModelSerializer):
class Meta:
model = Organization
+ list_serializer_class = BulkListSerializer
fields = '__all__'
read_only_fields = ['id', 'created_by', 'date_created']
+
+
+class OrgReadSerializer(ModelSerializer):
+ admins = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True)
+ users = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True)
+ user_groups = serializers.SerializerMethodField()
+ assets = serializers.SerializerMethodField()
+ domains = serializers.SerializerMethodField()
+ admin_users = serializers.SerializerMethodField()
+ system_users = serializers.SerializerMethodField()
+ labels = serializers.SerializerMethodField()
+ perms = serializers.SerializerMethodField()
+
+ class Meta:
+ model = Organization
+ fields = '__all__'
+
+ @staticmethod
+ def get_data_from_model(obj, model):
+ current_org = get_current_org()
+ set_current_org(Organization.root())
+ if model == Asset:
+ data = [o.hostname for o in model.objects.filter(org_id=obj.id)]
+ else:
+ data = [o.name for o in model.objects.filter(org_id=obj.id)]
+ set_current_org(current_org)
+ return data
+
+ def get_user_groups(self, obj):
+ return self.get_data_from_model(obj, UserGroup)
+
+ def get_assets(self, obj):
+ return self.get_data_from_model(obj, Asset)
+
+ def get_domains(self, obj):
+ return self.get_data_from_model(obj, Domain)
+
+ def get_admin_users(self, obj):
+ return self.get_data_from_model(obj, AdminUser)
+
+ def get_system_users(self, obj):
+ return self.get_data_from_model(obj, SystemUser)
+
+ def get_labels(self, obj):
+ return self.get_data_from_model(obj, Label)
+
+ def get_perms(self, obj):
+ return self.get_data_from_model(obj, AssetPermission)
+
+
+class OrgMembershipAdminSerializer(OrgMembershipSerializerMixin, ModelSerializer):
+ class Meta:
+ model = Organization.admins.through
+ list_serializer_class = BulkListSerializer
+ fields = '__all__'
+
+
+class OrgMembershipUserSerializer(OrgMembershipSerializerMixin, ModelSerializer):
+ class Meta:
+ model = Organization.users.through
+ list_serializer_class = BulkListSerializer
+ fields = '__all__'
diff --git a/apps/orgs/urls/api_urls.py b/apps/orgs/urls/api_urls.py
index a90d911e2..deca429ed 100644
--- a/apps/orgs/urls/api_urls.py
+++ b/apps/orgs/urls/api_urls.py
@@ -1,12 +1,20 @@
# -*- coding: utf-8 -*-
#
+from django.urls import path
from rest_framework.routers import DefaultRouter
from .. import api
app_name = 'orgs'
router = DefaultRouter()
+
+router.register(r'org/(?P
[0-9a-zA-Z\-]{36})/membership/admins',
+ api.OrgMembershipAdminsViewSet, 'membership-admins')
+
+router.register(r'org/(?P[0-9a-zA-Z\-]{36})/membership/users',
+ api.OrgMembershipUsersViewSet, 'membership-users'),
+
router.register(r'orgs', api.OrgViewSet, 'org')
diff --git a/apps/perms/api.py b/apps/perms/api.py
index 04e9df301..1488c21e6 100644
--- a/apps/perms/api.py
+++ b/apps/perms/api.py
@@ -5,6 +5,7 @@ from django.shortcuts import get_object_or_404
from rest_framework.views import APIView, Response
from rest_framework.generics import ListAPIView, get_object_or_404, RetrieveUpdateAPIView
from rest_framework import viewsets
+from rest_framework.pagination import LimitOffsetPagination
from common.utils import set_or_append_attr_bulk
from common.permissions import IsValidUser, IsOrgAdmin, IsOrgAdminOrAppUser
@@ -15,6 +16,7 @@ from .hands import AssetGrantedSerializer, User, UserGroup, Asset, Node, \
NodeGrantedSerializer, SystemUser, NodeSerializer
from orgs.utils import set_to_root_org
from . import serializers
+from .mixins import AssetsFilterMixin
__all__ = [
@@ -32,6 +34,7 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
"""
queryset = AssetPermission.objects.all()
serializer_class = serializers.AssetPermissionCreateUpdateSerializer
+ pagination_class = LimitOffsetPagination
permission_classes = (IsOrgAdmin,)
def get_serializer_class(self):
@@ -40,10 +43,15 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
return self.serializer_class
def get_queryset(self):
- queryset = super().get_queryset()
+ queryset = super().get_queryset().all()
+ search = self.request.query_params.get('search')
asset_id = self.request.query_params.get('asset')
node_id = self.request.query_params.get('node')
inherit_nodes = set()
+
+ if search:
+ queryset = queryset.filter(name__icontains=search)
+
if not asset_id and not node_id:
return queryset
@@ -62,15 +70,17 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
_permissions = queryset.filter(nodes=n)
set_or_append_attr_bulk(_permissions, "inherit", n.value)
permissions.update(_permissions)
- return permissions
+
+ return list(permissions)
-class UserGrantedAssetsApi(ListAPIView):
+class UserGrantedAssetsApi(AssetsFilterMixin, ListAPIView):
"""
用户授权的所有资产
"""
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = AssetGrantedSerializer
+ pagination_class = LimitOffsetPagination
def change_org_if_need(self):
if self.request.user.is_superuser or \
@@ -93,6 +103,7 @@ class UserGrantedAssetsApi(ListAPIView):
system_users_granted = [s for s in v if s.protocol == k.protocol]
k.system_users_granted = system_users_granted
queryset.append(k)
+
return queryset
def get_permissions(self):
@@ -131,7 +142,7 @@ class UserGrantedNodesApi(ListAPIView):
return super().get_permissions()
-class UserGrantedNodesWithAssetsApi(ListAPIView):
+class UserGrantedNodesWithAssetsApi(AssetsFilterMixin, ListAPIView):
"""
用户授权的节点并带着节点下资产的api
"""
@@ -166,19 +177,25 @@ class UserGrantedNodesWithAssetsApi(ListAPIView):
queryset.append(node)
return queryset
+ def sort_assets(self, queryset):
+ for node in queryset:
+ node.assets_granted = super().sort_assets(node.assets_granted)
+ return queryset
+
def get_permissions(self):
if self.kwargs.get('pk') is None:
self.permission_classes = (IsValidUser,)
return super().get_permissions()
-class UserGrantedNodeAssetsApi(ListAPIView):
+class UserGrantedNodeAssetsApi(AssetsFilterMixin, ListAPIView):
"""
查询用户授权的节点下的资产的api, 与上面api不同的是,只返回某个节点下的资产
"""
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = AssetGrantedSerializer
-
+ pagination_class = LimitOffsetPagination
+
def change_org_if_need(self):
if self.request.user.is_superuser or \
self.request.user.is_app or \
@@ -200,6 +217,8 @@ class UserGrantedNodeAssetsApi(ListAPIView):
assets = nodes.get(node, [])
for asset, system_users in assets.items():
asset.system_users_granted = system_users
+
+ assets = list(assets.keys())
return assets
def get_permissions(self):
diff --git a/apps/perms/mixins.py b/apps/perms/mixins.py
new file mode 100644
index 000000000..e41c0529b
--- /dev/null
+++ b/apps/perms/mixins.py
@@ -0,0 +1,36 @@
+# ~*~ coding: utf-8 ~*~
+#
+
+
+class AssetsFilterMixin(object):
+ """
+ 对资产进行过滤(查询,排序)
+ """
+
+ def filter_queryset(self, queryset):
+ queryset = self.search_assets(queryset)
+ queryset = self.sort_assets(queryset)
+ return queryset
+
+ def search_assets(self, queryset):
+ from perms.utils import is_obj_attr_has
+ value = self.request.query_params.get('search')
+ if not value:
+ return queryset
+ queryset = [asset for asset in queryset if is_obj_attr_has(asset, value)]
+ return queryset
+
+ def sort_assets(self, queryset):
+ from perms.utils import sort_assets
+ order_by = self.request.query_params.get('order')
+ if not order_by:
+ order_by = 'hostname'
+
+ if order_by.startswith('-'):
+ order_by = order_by.lstrip('-')
+ reverse = True
+ else:
+ reverse = False
+
+ queryset = sort_assets(queryset, order_by=order_by, reverse=reverse)
+ return queryset
diff --git a/apps/perms/templates/perms/asset_permission_asset.html b/apps/perms/templates/perms/asset_permission_asset.html
index 364d7e60f..e774692f3 100644
--- a/apps/perms/templates/perms/asset_permission_asset.html
+++ b/apps/perms/templates/perms/asset_permission_asset.html
@@ -24,7 +24,7 @@
- {% trans 'Assets and asset groups' %}
+ {% trans 'Assets and node' %}
diff --git a/apps/perms/templates/perms/asset_permission_create_update.html b/apps/perms/templates/perms/asset_permission_create_update.html
index 0ec3ae639..2d79890f6 100644
--- a/apps/perms/templates/perms/asset_permission_create_update.html
+++ b/apps/perms/templates/perms/asset_permission_create_update.html
@@ -54,9 +54,9 @@
+
@@ -80,19 +81,27 @@
{% endblock %}
{% block custom_foot_js %}
+
+
+
+
{% endblock %}
\ No newline at end of file
diff --git a/apps/perms/templates/perms/asset_permission_detail.html b/apps/perms/templates/perms/asset_permission_detail.html
index 3996cb274..9c38cfc29 100644
--- a/apps/perms/templates/perms/asset_permission_detail.html
+++ b/apps/perms/templates/perms/asset_permission_detail.html
@@ -24,7 +24,7 @@
- {% trans 'Assets and asset groups' %}
+ {% trans 'Assets and node' %}
{% trans 'Update' %}
diff --git a/apps/perms/templates/perms/asset_permission_list.html b/apps/perms/templates/perms/asset_permission_list.html
index 31ccce112..1c1a21470 100644
--- a/apps/perms/templates/perms/asset_permission_list.html
+++ b/apps/perms/templates/perms/asset_permission_list.html
@@ -217,7 +217,7 @@ function initTable() {
select: {},
op_html: $('#actions').html()
};
- table = jumpserver.initDataTable(options);
+ table = jumpserver.initServerSideDataTable(options);
return table
}
diff --git a/apps/perms/templates/perms/asset_permission_user.html b/apps/perms/templates/perms/asset_permission_user.html
index 5d490bc28..eef6bf601 100644
--- a/apps/perms/templates/perms/asset_permission_user.html
+++ b/apps/perms/templates/perms/asset_permission_user.html
@@ -24,7 +24,7 @@
- {% trans 'Assets and asset groups' %}
+ {% trans 'Assets and node' %}
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/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/jumpserver.js b/apps/static/js/jumpserver.js
index 5f2335c1d..3feedbd54 100644
--- a/apps/static/js/jumpserver.js
+++ b/apps/static/js/jumpserver.js
@@ -146,12 +146,15 @@ function activeNav() {
if (app === ''){
$('#index').addClass('active');
}
- else if (app === 'xpack') {
+ else if (app === 'xpack' && resource === 'cloud') {
var item = url_array[3];
$("#" + app).addClass('active');
$('#' + app + ' #' + resource).addClass('active');
$('#' + 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/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/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..893f15b12 100644
--- a/apps/terminal/forms.py
+++ b/apps/terminal/forms.py
@@ -2,36 +2,33 @@
#
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():
+ from common import utils
+ command_storage = utils.get_command_storage_or_create_default_storage()
+ for k, v in command_storage.items():
yield (k, k)
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():
+ from common import utils
+ replay_storage = utils.get_replay_storage_or_create_default_storage()
+ for k, v in replay_storage.items():
yield (k, k)
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"),
)
diff --git a/apps/users/api/auth.py b/apps/users/api/auth.py
index 4abd2839b..9de2be6b2 100644
--- a/apps/users/api/auth.py
+++ b/apps/users/api/auth.py
@@ -43,10 +43,13 @@ class UserAuthApi(RootOrgViewMixin, APIView):
user, msg = self.check_user_valid(request)
if not user:
+ username = request.data.get('username', '')
+ exist = User.objects.filter(username=username).first()
+ reason = LoginLog.REASON_PASSWORD if exist else LoginLog.REASON_NOT_EXIST
data = {
- 'username': request.data.get('username', ''),
+ 'username': username,
'mfa': LoginLog.MFA_UNKNOWN,
- 'reason': LoginLog.REASON_PASSWORD,
+ 'reason': reason,
'status': False
}
self.write_login_log(request, data)
diff --git a/apps/users/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/api/user.py b/apps/users/api/user.py
index ba0113a36..6bc0d4622 100644
--- a/apps/users/api/user.py
+++ b/apps/users/api/user.py
@@ -9,6 +9,7 @@ from rest_framework import generics
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from rest_framework_bulk import BulkModelViewSet
+from rest_framework.pagination import LimitOffsetPagination
from ..serializers import UserSerializer, UserPKUpdateSerializer, \
UserUpdateGroupSerializer, ChangeUserPasswordSerializer
@@ -28,10 +29,12 @@ __all__ = [
class UserViewSet(IDInFilterMixin, BulkModelViewSet):
+ filter_fields = ('username', 'email', 'name', 'id')
+ search_fields = filter_fields
queryset = User.objects.exclude(role="App")
serializer_class = UserSerializer
permission_classes = (IsOrgAdmin,)
- filter_fields = ('username', 'email', 'name', 'id')
+ pagination_class = LimitOffsetPagination
def get_queryset(self):
queryset = super().get_queryset()
diff --git a/apps/users/models/authentication.py b/apps/users/models/authentication.py
index cb0b7d85f..5377b0f75 100644
--- a/apps/users/models/authentication.py
+++ b/apps/users/models/authentication.py
@@ -55,11 +55,13 @@ class LoginLog(models.Model):
REASON_NOTHING = 0
REASON_PASSWORD = 1
REASON_MFA = 2
+ REASON_NOT_EXIST = 3
REASON_CHOICE = (
(REASON_NOTHING, _('-')),
(REASON_PASSWORD, _('Username/password check failed')),
(REASON_MFA, _('MFA authentication failed')),
+ (REASON_NOT_EXIST, _("Username does not exist")),
)
STATUS_CHOICE = (
@@ -67,7 +69,7 @@ class LoginLog(models.Model):
(False, _('Failed'))
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
- username = models.CharField(max_length=20, verbose_name=_('Username'))
+ username = models.CharField(max_length=128, verbose_name=_('Username'))
type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2, verbose_name=_('Login type'))
ip = models.GenericIPAddressField(verbose_name=_('Login ip'))
city = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('Login city'))
diff --git a/apps/users/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 @@
@@ -52,18 +52,24 @@
{% endblock %}
{% block custom_foot_js %}
+
+
+
+
{% endblock %}
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');
diff --git a/apps/users/templates/users/user_list.html b/apps/users/templates/users/user_list.html
index 4caf6e7a4..5b4565b8e 100644
--- a/apps/users/templates/users/user_list.html
+++ b/apps/users/templates/users/user_list.html
@@ -95,7 +95,7 @@ function initTable() {
],
op_html: $('#actions').html()
};
- table = jumpserver.initDataTable(options);
+ var table = jumpserver.initServerSideDataTable(options);
return table
}
diff --git a/apps/users/views/login.py b/apps/users/views/login.py
index c4e7538e6..4f2308d04 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, \
@@ -78,12 +79,15 @@ class UserLoginView(FormView):
def form_invalid(self, form):
# write login failed log
username = form.cleaned_data.get('username')
+ exist = User.objects.filter(username=username).first()
+ reason = LoginLog.REASON_PASSWORD if exist else LoginLog.REASON_NOT_EXIST
data = {
'username': username,
'mfa': LoginLog.MFA_UNKNOWN,
- 'reason': LoginLog.REASON_PASSWORD,
+ 'reason': reason,
'status': False
}
+
self.write_login_log(data)
# limit user login failed count
@@ -318,7 +322,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)
diff --git a/requirements/deb_requirements.txt b/requirements/deb_requirements.txt
index f4131a3ea..d4cf29941 100644
--- a/requirements/deb_requirements.txt
+++ b/requirements/deb_requirements.txt
@@ -1 +1 @@
-libtiff5-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev python-tk python-dev openssl libssl-dev libldap2-dev libsasl2-dev sqlite gcc automake libkrb5-dev
+libtiff5-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev python-tk python-dev openssl libssl-dev libldap2-dev libsasl2-dev sqlite gcc automake libkrb5-dev sshpass
diff --git a/requirements/rpm_requirements.txt b/requirements/rpm_requirements.txt
index 2721d614d..be3ce68e3 100644
--- a/requirements/rpm_requirements.txt
+++ b/requirements/rpm_requirements.txt
@@ -1 +1 @@
-libtiff-devel libjpeg-devel libzip-devel freetype-devel lcms2-devel libwebp-devel tcl-devel tk-devel sshpass openldap-devel mysql-devel libffi-devel openssh-clients
+libtiff-devel libjpeg-devel libzip-devel freetype-devel lcms2-devel libwebp-devel tcl-devel tk-devel sshpass openldap-devel mariadb-devel libffi-devel openssh-clients