diff --git a/apps/applications/api/remote_app.py b/apps/applications/api/remote_app.py index 83b1d490a..79beef8fc 100644 --- a/apps/applications/api/remote_app.py +++ b/apps/applications/api/remote_app.py @@ -1,10 +1,8 @@ # coding: utf-8 # - -from rest_framework import generics - from orgs.mixins.api import OrgBulkModelViewSet +from orgs.mixins import generics from ..hands import IsOrgAdmin, IsAppUser from ..models import RemoteApp from ..serializers import RemoteAppSerializer, RemoteAppConnectionInfoSerializer @@ -16,14 +14,14 @@ __all__ = [ class RemoteAppViewSet(OrgBulkModelViewSet): + model = RemoteApp filter_fields = ('name',) search_fields = filter_fields permission_classes = (IsOrgAdmin,) - queryset = RemoteApp.objects.all() serializer_class = RemoteAppSerializer class RemoteAppConnectionInfoApi(generics.RetrieveAPIView): - queryset = RemoteApp.objects.all() + model = RemoteApp permission_classes = (IsAppUser, ) serializer_class = RemoteAppConnectionInfoSerializer diff --git a/apps/assets/api/__init__.py b/apps/assets/api/__init__.py index a4404e290..e7126878d 100644 --- a/apps/assets/api/__init__.py +++ b/apps/assets/api/__init__.py @@ -7,3 +7,4 @@ from .domain import * from .cmd_filter import * from .asset_user import * from .gathered_user import * +from .favorite_asset import * diff --git a/apps/assets/api/admin_user.py b/apps/assets/api/admin_user.py index fd10e6129..b91f10c65 100644 --- a/apps/assets/api/admin_user.py +++ b/apps/assets/api/admin_user.py @@ -15,11 +15,10 @@ from django.db import transaction from django.shortcuts import get_object_or_404 -from rest_framework import generics from rest_framework.response import Response from orgs.mixins.api import OrgBulkModelViewSet +from orgs.mixins import generics -from common.mixins import CommonApiMixin from common.utils import get_logger from ..hands import IsOrgAdmin from ..models import AdminUser, Asset @@ -39,22 +38,21 @@ class AdminUserViewSet(OrgBulkModelViewSet): """ Admin user api set, for add,delete,update,list,retrieve resource """ - + model = AdminUser filter_fields = ("name", "username") search_fields = filter_fields - queryset = AdminUser.objects.all() serializer_class = serializers.AdminUserSerializer permission_classes = (IsOrgAdmin,) class AdminUserAuthApi(generics.UpdateAPIView): - queryset = AdminUser.objects.all() + model = AdminUser serializer_class = serializers.AdminUserAuthSerializer permission_classes = (IsOrgAdmin,) class ReplaceNodesAdminUserApi(generics.UpdateAPIView): - queryset = AdminUser.objects.all() + model = AdminUser serializer_class = serializers.ReplaceNodeAdminUserSerializer permission_classes = (IsOrgAdmin,) @@ -79,7 +77,7 @@ class AdminUserTestConnectiveApi(generics.RetrieveAPIView): """ Test asset admin user assets_connectivity """ - queryset = AdminUser.objects.all() + model = AdminUser permission_classes = (IsOrgAdmin,) serializer_class = serializers.TaskIDSerializer diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 39341328b..64fdc16dc 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -3,14 +3,14 @@ import random -from rest_framework import generics from rest_framework.response import Response from django.shortcuts import get_object_or_404 from common.utils import get_logger, get_object_or_none from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from orgs.mixins.api import OrgBulkModelViewSet -from ..models import Asset, AdminUser, Node +from orgs.mixins import generics +from ..models import Asset, Node from .. import serializers from ..tasks import update_asset_hardware_info_manual, \ test_asset_connectivity_manual @@ -29,10 +29,10 @@ class AssetViewSet(OrgBulkModelViewSet): """ API endpoint that allows Asset to be viewed or edited. """ + model = Asset filter_fields = ("hostname", "ip", "systemuser__id", "admin_user__id") search_fields = ("hostname", "ip") ordering_fields = ("hostname", "ip", "port", "cpu_cores") - queryset = Asset.objects.all() serializer_class = serializers.AssetSerializer permission_classes = (IsOrgAdminOrAppUser,) extra_filter_backends = [AssetByNodeFilterBackend, LabelFilterBackend] @@ -57,7 +57,7 @@ class AssetRefreshHardwareApi(generics.RetrieveAPIView): """ Refresh asset hardware info """ - queryset = Asset.objects.all() + model = Asset serializer_class = serializers.AssetSerializer permission_classes = (IsOrgAdmin,) @@ -72,7 +72,7 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView): """ Test asset admin user assets_connectivity """ - queryset = Asset.objects.all() + model = Asset permission_classes = (IsOrgAdmin,) serializer_class = serializers.TaskIDSerializer @@ -84,9 +84,9 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView): class AssetGatewayApi(generics.RetrieveAPIView): - queryset = Asset.objects.all() permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.GatewayWithAuthSerializer + model = Asset def retrieve(self, request, *args, **kwargs): asset_id = kwargs.get('pk') @@ -98,4 +98,4 @@ class AssetGatewayApi(generics.RetrieveAPIView): serializer = serializers.GatewayWithAuthSerializer(instance=gateway) return Response(serializer.data) else: - return Response({"msg": "Not have gateway"}, status=404) \ No newline at end of file + return Response({"msg": "Not have gateway"}, status=404) diff --git a/apps/assets/api/cmd_filter.py b/apps/assets/api/cmd_filter.py index 846172548..b4ae67c72 100644 --- a/apps/assets/api/cmd_filter.py +++ b/apps/assets/api/cmd_filter.py @@ -13,14 +13,15 @@ __all__ = ['CommandFilterViewSet', 'CommandFilterRuleViewSet'] class CommandFilterViewSet(OrgBulkModelViewSet): + model = CommandFilter filter_fields = ("name",) search_fields = filter_fields permission_classes = (IsOrgAdmin,) - queryset = CommandFilter.objects.all() serializer_class = serializers.CommandFilterSerializer class CommandFilterRuleViewSet(OrgBulkModelViewSet): + model = CommandFilterRule filter_fields = ("content",) search_fields = filter_fields permission_classes = (IsOrgAdmin,) diff --git a/apps/assets/api/domain.py b/apps/assets/api/domain.py index 03c4c2307..13be62315 100644 --- a/apps/assets/api/domain.py +++ b/apps/assets/api/domain.py @@ -15,7 +15,7 @@ __all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"] class DomainViewSet(OrgBulkModelViewSet): - queryset = Domain.objects.all() + model = Domain permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.DomainSerializer @@ -26,16 +26,15 @@ class DomainViewSet(OrgBulkModelViewSet): class GatewayViewSet(OrgBulkModelViewSet): + model = Gateway filter_fields = ("domain__name", "name", "username", "ip", "domain") search_fields = filter_fields - queryset = Gateway.objects.all() permission_classes = (IsOrgAdmin,) serializer_class = serializers.GatewaySerializer class GatewayTestConnectionApi(SingleObjectMixin, APIView): permission_classes = (IsOrgAdmin,) - model = Gateway object = None def post(self, request, *args, **kwargs): diff --git a/apps/assets/api/favorite_asset.py b/apps/assets/api/favorite_asset.py new file mode 100644 index 000000000..174c77330 --- /dev/null +++ b/apps/assets/api/favorite_asset.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# +from rest_framework_bulk import BulkModelViewSet + +from common.permissions import IsValidUser +from orgs.utils import tmp_to_root_org +from ..models import FavoriteAsset +from ..serializers import FavoriteAssetSerializer + +__all__ = ['FavoriteAssetViewSet'] + + +class FavoriteAssetViewSet(BulkModelViewSet): + serializer_class = FavoriteAssetSerializer + permission_classes = (IsValidUser,) + filter_fields = ['asset'] + + def dispatch(self, request, *args, **kwargs): + with tmp_to_root_org(): + return super().dispatch(request, *args, **kwargs) + + def get_queryset(self): + queryset = FavoriteAsset.objects.filter(user=self.request.user) + return queryset + + def allow_bulk_destroy(self, qs, filtered): + return filtered.count() == 1 diff --git a/apps/assets/api/gathered_user.py b/apps/assets/api/gathered_user.py index 038d47208..b9f137648 100644 --- a/apps/assets/api/gathered_user.py +++ b/apps/assets/api/gathered_user.py @@ -13,12 +13,10 @@ __all__ = ['GatheredUserViewSet'] class GatheredUserViewSet(OrgModelViewSet): - queryset = GatheredUser.objects.all() + model = GatheredUser serializer_class = GatheredUserSerializer permission_classes = [IsOrgAdmin] extra_filter_backends = [AssetRelatedByNodeFilterBackend] filter_fields = ['asset', 'username', 'present'] search_fields = ['username', 'asset__ip', 'asset__hostname'] - - diff --git a/apps/assets/api/label.py b/apps/assets/api/label.py index c3b798959..fe298169a 100644 --- a/apps/assets/api/label.py +++ b/apps/assets/api/label.py @@ -27,6 +27,7 @@ __all__ = ['LabelViewSet'] class LabelViewSet(OrgBulkModelViewSet): + model = Label filter_fields = ("name", "value") search_fields = filter_fields permission_classes = (IsOrgAdmin,) diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index 4e608163f..1451650d2 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from rest_framework import generics +from rest_framework import status from rest_framework.serializers import ValidationError from rest_framework.views import APIView from rest_framework.response import Response @@ -23,9 +23,12 @@ from django.shortcuts import get_object_or_404 from common.utils import get_logger, get_object_or_none from common.tree import TreeNodeSerializer from orgs.mixins.api import OrgModelViewSet +from orgs.mixins import generics from ..hands import IsOrgAdmin from ..models import Node -from ..tasks import update_assets_hardware_info_util, test_asset_connectivity_util +from ..tasks import ( + update_assets_hardware_info_util, test_asset_connectivity_util +) from .. import serializers @@ -40,9 +43,9 @@ __all__ = [ class NodeViewSet(OrgModelViewSet): + model = Node filter_fields = ('value', 'key', 'id') search_fields = ('value', ) - queryset = Node.objects.all() permission_classes = (IsOrgAdmin,) serializer_class = serializers.NodeSerializer @@ -59,6 +62,13 @@ class NodeViewSet(OrgModelViewSet): raise ValidationError({"error": msg}) return super().perform_update(serializer) + def destroy(self, request, *args, **kwargs): + node = self.get_object() + if node.has_children_or_contains_assets(): + msg = _("Deletion failed and the node contains children or assets") + return Response(data={'msg': msg}, status=status.HTTP_403_FORBIDDEN) + return super().destroy(request, *args, **kwargs) + class NodeListAsTreeApi(generics.ListAPIView): """ @@ -72,6 +82,7 @@ class NodeListAsTreeApi(generics.ListAPIView): } ] """ + model = Node permission_classes = (IsOrgAdmin,) serializer_class = TreeNodeSerializer @@ -80,10 +91,6 @@ class NodeListAsTreeApi(generics.ListAPIView): queryset = [node.as_tree_node() for node in queryset] return queryset - def get_queryset(self): - queryset = Node.objects.all() - return queryset - def filter_queryset(self, queryset): queryset = super().filter_queryset(queryset) queryset = self.to_tree_queryset(queryset) @@ -91,7 +98,6 @@ class NodeListAsTreeApi(generics.ListAPIView): class NodeChildrenApi(generics.ListCreateAPIView): - queryset = Node.objects.all() permission_classes = (IsOrgAdmin,) serializer_class = serializers.NodeSerializer instance = None @@ -155,6 +161,7 @@ class NodeChildrenAsTreeApi(NodeChildrenApi): ] """ + model = Node serializer_class = TreeNodeSerializer http_method_names = ['get'] @@ -197,7 +204,7 @@ class NodeAssetsApi(generics.ListAPIView): class NodeAddChildrenApi(generics.UpdateAPIView): - queryset = Node.objects.all() + model = Node permission_classes = (IsOrgAdmin,) serializer_class = serializers.NodeAddChildrenSerializer instance = None @@ -214,8 +221,8 @@ class NodeAddChildrenApi(generics.UpdateAPIView): class NodeAddAssetsApi(generics.UpdateAPIView): + model = Node serializer_class = serializers.NodeAssetsSerializer - queryset = Node.objects.all() permission_classes = (IsOrgAdmin,) instance = None @@ -226,8 +233,8 @@ class NodeAddAssetsApi(generics.UpdateAPIView): class NodeRemoveAssetsApi(generics.UpdateAPIView): + model = Node serializer_class = serializers.NodeAssetsSerializer - queryset = Node.objects.all() permission_classes = (IsOrgAdmin,) instance = None @@ -242,8 +249,8 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView): class NodeReplaceAssetsApi(generics.UpdateAPIView): + model = Node serializer_class = serializers.NodeAssetsSerializer - queryset = Node.objects.all() permission_classes = (IsOrgAdmin,) instance = None @@ -255,8 +262,8 @@ class NodeReplaceAssetsApi(generics.UpdateAPIView): class RefreshNodeHardwareInfoApi(APIView): - permission_classes = (IsOrgAdmin,) model = Node + permission_classes = (IsOrgAdmin,) def get(self, request, *args, **kwargs): node_id = kwargs.get('pk') diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index 9b87ed9aa..5bf38853d 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -14,13 +14,13 @@ # limitations under the License. from django.shortcuts import get_object_or_404 -from rest_framework import generics from rest_framework.response import Response from common.serializers import CeleryTaskSerializer from common.utils import get_logger from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from orgs.mixins.api import OrgBulkModelViewSet +from orgs.mixins import generics from ..models import SystemUser, Asset from .. import serializers from ..tasks import ( @@ -43,22 +43,18 @@ class SystemUserViewSet(OrgBulkModelViewSet): """ System user api set, for add,delete,update,list,retrieve resource """ + model = SystemUser filter_fields = ("name", "username") search_fields = filter_fields - queryset = SystemUser.objects.all() serializer_class = serializers.SystemUserSerializer permission_classes = (IsOrgAdminOrAppUser,) - def get_queryset(self): - queryset = super().get_queryset().all() - return queryset - class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView): """ Get system user auth info """ - queryset = SystemUser.objects.all() + model = SystemUser permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.SystemUserAuthSerializer @@ -72,7 +68,7 @@ class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView): """ Get system user with asset auth info """ - queryset = SystemUser.objects.all() + model = SystemUser permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.SystemUserAuthSerializer @@ -88,7 +84,7 @@ class SystemUserPushApi(generics.RetrieveAPIView): """ Push system user to cluster assets api """ - queryset = SystemUser.objects.all() + model = SystemUser permission_classes = (IsOrgAdmin,) serializer_class = CeleryTaskSerializer @@ -105,7 +101,7 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView): """ Push system user to cluster assets api """ - queryset = SystemUser.objects.all() + model = SystemUser permission_classes = (IsOrgAdmin,) serializer_class = CeleryTaskSerializer @@ -132,7 +128,7 @@ class SystemUserAssetsListView(generics.ListAPIView): class SystemUserPushToAssetApi(generics.RetrieveAPIView): - queryset = SystemUser.objects.all() + model = SystemUser permission_classes = (IsOrgAdmin,) serializer_class = serializers.TaskIDSerializer @@ -145,7 +141,7 @@ class SystemUserPushToAssetApi(generics.RetrieveAPIView): class SystemUserTestAssetConnectivityApi(generics.RetrieveAPIView): - queryset = SystemUser.objects.all() + model = SystemUser permission_classes = (IsOrgAdmin,) serializer_class = serializers.TaskIDSerializer diff --git a/apps/assets/const.py b/apps/assets/const.py new file mode 100644 index 000000000..c23b700ba --- /dev/null +++ b/apps/assets/const.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# + +from django.utils.translation import ugettext_lazy as _ + + +GENERAL_LIMIT_SPECIAL_CHARACTERS_HELP_TEXT = _( + 'Only Numbers、letters、 chinese and characters ( {} ) are allowed' +).format(" ".join(['.', '_', '@', '-'])) + +GENERAL_LIMIT_SPECIAL_CHARACTERS_PATTERN = r"^[\._@\w-]+$" + +GENERAL_LIMIT_SPECIAL_CHARACTERS_ERROR_MSG = \ + _("* The contains characters that are not allowed") diff --git a/apps/assets/filters.py b/apps/assets/filters.py index 46df36ee7..b68d9d127 100644 --- a/apps/assets/filters.py +++ b/apps/assets/filters.py @@ -53,7 +53,7 @@ class AssetByNodeFilterBackend(filters.BaseFilterBackend): return queryset if node is None: - return queryset.none() + return queryset query_all = self.is_query_all(request) if query_all: pattern = node.get_all_children_pattern(with_self=True) diff --git a/apps/assets/forms/asset.py b/apps/assets/forms/asset.py index d83172373..b6e5d3a42 100644 --- a/apps/assets/forms/asset.py +++ b/apps/assets/forms/asset.py @@ -7,6 +7,7 @@ from common.utils import get_logger from orgs.mixins.forms import OrgModelForm from ..models import Asset, Node +from ..const import GENERAL_LIMIT_SPECIAL_CHARACTERS_HELP_TEXT logger = get_logger(__file__) @@ -14,10 +15,6 @@ __all__ = [ 'AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm', 'ProtocolForm', ] -HELP_TEXTS_ASSET_HOSTNAME = _( - 'Only Numbers、letters、 chinese and characters ( {} ) are allowed' -).format(" ".join(['.', '_', '@'])) - class ProtocolForm(forms.Form): name = forms.ChoiceField( @@ -72,7 +69,7 @@ class AssetCreateForm(OrgModelForm): 'nodes': _("Node"), } help_texts = { - 'hostname': HELP_TEXTS_ASSET_HOSTNAME, + 'hostname': GENERAL_LIMIT_SPECIAL_CHARACTERS_HELP_TEXT, 'admin_user': _( 'root or other NOPASSWD sudo privilege user existed in asset,' 'If asset is windows or other set any one, more see admin user left menu' @@ -119,7 +116,7 @@ class AssetUpdateForm(OrgModelForm): 'nodes': _("Node"), } help_texts = { - 'hostname': HELP_TEXTS_ASSET_HOSTNAME, + 'hostname': GENERAL_LIMIT_SPECIAL_CHARACTERS_HELP_TEXT, 'admin_user': _( 'root or other NOPASSWD sudo privilege user existed in asset,' 'If asset is windows or other set any one, more see admin user left menu' @@ -132,7 +129,7 @@ class AssetUpdateForm(OrgModelForm): class AssetBulkUpdateForm(OrgModelForm): assets = forms.ModelMultipleChoiceField( required=True, - label=_('Select assets'), queryset=Asset.objects.all(), + label=_('Select assets'), queryset=Asset.objects, widget=forms.SelectMultiple( attrs={ 'class': 'select2', @@ -158,11 +155,18 @@ class AssetBulkUpdateForm(OrgModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + self.set_fields_queryset() + # 重写其他字段为不再required for name, field in self.fields.items(): if name != 'assets': field.required = False + def set_fields_queryset(self): + assets_field = self.fields['assets'] + if hasattr(self, 'data'): + assets_field.queryset = Asset.objects.all() + def save(self, commit=True): changed_fields = [] for field in self._meta.fields: diff --git a/apps/assets/forms/domain.py b/apps/assets/forms/domain.py index 496147a99..71d9f6cfa 100644 --- a/apps/assets/forms/domain.py +++ b/apps/assets/forms/domain.py @@ -12,7 +12,7 @@ __all__ = ['DomainForm', 'GatewayForm'] class DomainForm(forms.ModelForm): assets = forms.ModelMultipleChoiceField( - queryset=Asset.objects.all(), label=_('Asset'), required=False, + queryset=Asset.objects, label=_('Asset'), required=False, widget=forms.SelectMultiple( attrs={'class': 'select2', 'data-placeholder': _('Select assets')} ) @@ -23,19 +23,23 @@ class DomainForm(forms.ModelForm): fields = ['name', 'comment', 'assets'] def __init__(self, *args, **kwargs): - if kwargs.get('instance', None): - initial = kwargs.get('initial', {}) - initial['assets'] = kwargs['instance'].assets.all() super().__init__(*args, **kwargs) + self.set_fields_queryset() - # 前端渲染优化, 防止过多资产 + def set_fields_queryset(self): assets_field = self.fields.get('assets') + + # 没有data代表是渲染表单, 有data代表是提交创建/更新表单 if not self.data: - instance = kwargs.get('instance') - if instance: - assets_field.queryset = instance.assets.all() + # 有instance 代表渲染更新表单, 否则是创建表单 + # 前端渲染优化, 防止过多资产, 设置assets queryset为none + if self.instance: + assets_field.initial = self.instance.assets.all() + assets_field.queryset = self.instance.assets.all() else: assets_field.queryset = Asset.objects.none() + else: + assets_field.queryset = Asset.objects.all() def save(self, commit=True): instance = super().save(commit=commit) diff --git a/apps/assets/forms/label.py b/apps/assets/forms/label.py index 8a5a54e4a..1f2f13987 100644 --- a/apps/assets/forms/label.py +++ b/apps/assets/forms/label.py @@ -10,7 +10,7 @@ __all__ = ['LabelForm'] class LabelForm(forms.ModelForm): assets = forms.ModelMultipleChoiceField( - queryset=Asset.objects.all(), label=_('Asset'), required=False, + queryset=Asset.objects.none(), label=_('Asset'), required=False, widget=forms.SelectMultiple( attrs={'class': 'select2', 'data-placeholder': _('Select assets')} ) @@ -21,19 +21,23 @@ class LabelForm(forms.ModelForm): fields = ['name', 'value', 'assets'] def __init__(self, *args, **kwargs): - if kwargs.get('instance', None): - initial = kwargs.get('initial', {}) - initial['assets'] = kwargs['instance'].assets.all() super().__init__(*args, **kwargs) + self.set_fields_queryset() - # 前端渲染优化, 防止过多资产 + def set_fields_queryset(self): assets_field = self.fields.get('assets') + + # 没有data代表是渲染表单, 有data代表是提交创建/更新表单 if not self.data: - instance = kwargs.get('instance') - if instance: - assets_field.queryset = instance.assets.all() + # 有instance 代表渲染更新表单, 否则是创建表单 + # 前端渲染优化, 防止过多资产, 设置assets queryset为none + if self.instance: + assets_field.initial = self.instance.assets.all() + assets_field.queryset = self.instance.assets.all() else: assets_field.queryset = Asset.objects.none() + else: + assets_field.queryset = Asset.objects.all() def save(self, commit=True): label = super().save(commit=commit) diff --git a/apps/assets/forms/user.py b/apps/assets/forms/user.py index 3d67b434b..b50639684 100644 --- a/apps/assets/forms/user.py +++ b/apps/assets/forms/user.py @@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _ from common.utils import validate_ssh_private_key, ssh_pubkey_gen, get_logger from orgs.mixins.forms import OrgModelForm from ..models import AdminUser, SystemUser +from ..const import GENERAL_LIMIT_SPECIAL_CHARACTERS_HELP_TEXT logger = get_logger(__file__) __all__ = [ @@ -98,6 +99,7 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm): }), } help_texts = { + 'name': GENERAL_LIMIT_SPECIAL_CHARACTERS_HELP_TEXT, 'auto_push': _('Auto push system user to asset'), 'priority': _('1-100, High level will be using login asset as default, ' 'if user was granted more than 2 system user'), diff --git a/apps/assets/migrations/0042_favoriteasset.py b/apps/assets/migrations/0042_favoriteasset.py new file mode 100644 index 000000000..3baebee74 --- /dev/null +++ b/apps/assets/migrations/0042_favoriteasset.py @@ -0,0 +1,31 @@ +# Generated by Django 2.2.5 on 2019-10-16 08:38 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('assets', '0041_gathereduser'), + ] + + operations = [ + migrations.CreateModel( + name='FavoriteAsset', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Asset')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'unique_together': {('user', 'asset')}, + }, + ), + ] diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index c69f19bf6..db9c54aed 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -10,3 +10,4 @@ from .authbook import * from .utils import * from .authbook import * from .gathered_user import * +from .favorite_asset import * diff --git a/apps/assets/models/cmd_filter.py b/apps/assets/models/cmd_filter.py index 38776ff38..91febd40b 100644 --- a/apps/assets/models/cmd_filter.py +++ b/apps/assets/models/cmd_filter.py @@ -7,6 +7,7 @@ from django.db import models from django.core.validators import MinValueValidator, MaxValueValidator from django.utils.translation import ugettext_lazy as _ +from common.utils import lazyproperty from orgs.mixins.models import OrgModelMixin @@ -57,25 +58,30 @@ class CommandFilterRule(OrgModelMixin): date_updated = models.DateTimeField(auto_now=True) created_by = models.CharField(max_length=128, blank=True, default='', verbose_name=_('Created by')) - __pattern = None - class Meta: ordering = ('-priority', 'action') verbose_name = _("Command filter rule") - @property + @lazyproperty def _pattern(self): - if self.__pattern: - return self.__pattern if self.type == 'command': regex = [] - for cmd in self.content.split('\r\n'): - cmd = cmd.replace(' ', '\s+') - regex.append(r'\b{0}\b'.format(cmd)) - self.__pattern = re.compile(r'{}'.format('|'.join(regex))) + content = self.content.replace('\r\n', '\n') + for cmd in content.split('\n'): + cmd = re.escape(cmd) + cmd = cmd.replace('\\ ', '\s+') + if cmd[-1].isalpha(): + regex.append(r'\b{0}\b'.format(cmd)) + else: + regex.append(r'\b{0}'.format(cmd)) + s = r'{}'.format('|'.join(regex)) else: - self.__pattern = re.compile(r'{0}'.format(self.content)) - return self.__pattern + s = r'{0}'.format(self.content) + try: + _pattern = re.compile(s) + except: + _pattern = '' + return _pattern def match(self, data): found = self._pattern.search(data) diff --git a/apps/assets/models/favorite_asset.py b/apps/assets/models/favorite_asset.py new file mode 100644 index 000000000..3abc69c8c --- /dev/null +++ b/apps/assets/models/favorite_asset.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# +from django.db import models + +from common.mixins.models import CommonModelMixin + + +__all__ = ['FavoriteAsset'] + + +class FavoriteAsset(CommonModelMixin): + user = models.ForeignKey('users.User', on_delete=models.CASCADE) + asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE) + + class Meta: + unique_together = ('user', 'asset') + + @classmethod + def get_user_favorite_assets_id(cls, user): + return cls.objects.filter(user=user).values_list('asset', flat=True) diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py index d1628be76..dd1f706f2 100644 --- a/apps/assets/models/node.py +++ b/apps/assets/models/node.py @@ -324,6 +324,8 @@ class SomeNodesMixin: ungrouped_value = _('ungrouped') empty_key = '-11' empty_value = _("empty") + favorite_key = '-12' + favorite_value = _("favorite") def is_default_node(self): return self.key == self.default_key @@ -363,7 +365,7 @@ class SomeNodesMixin: @classmethod def ungrouped_node(cls): with tmp_to_org(Organization.system()): - defaults = {'value': cls.ungrouped_key} + defaults = {'value': cls.ungrouped_value} obj, created = cls.objects.get_or_create( defaults=defaults, key=cls.ungrouped_key ) @@ -387,11 +389,21 @@ class SomeNodesMixin: ) return obj + @classmethod + def favorite_node(cls): + with tmp_to_org(Organization.system()): + defaults = {'value': cls.favorite_value} + obj, created = cls.objects.get_or_create( + defaults=defaults, key=cls.favorite_key + ) + return obj + @classmethod def initial_some_nodes(cls): cls.default_node() cls.empty_node() cls.ungrouped_node() + cls.favorite_node() class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixin): @@ -412,11 +424,11 @@ class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin def __str__(self): return self.value - def __eq__(self, other): - if not other: - return False - return self.id == other.id - + # def __eq__(self, other): + # if not other: + # return False + # return self.id == other.id + # def __gt__(self, other): self_key = [int(k) for k in self.key.split(':')] other_key = [int(k) for k in other.key.split(':')] @@ -470,8 +482,13 @@ class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin tree_node = TreeNode(**data) return tree_node - def delete(self, using=None, keep_parents=False): + def has_children_or_contains_assets(self): if self.children or self.get_assets(): + return True + return False + + def delete(self, using=None, keep_parents=False): + if self.has_children_or_contains_assets(): return return super().delete(using=using, keep_parents=keep_parents) diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index d3447b8ba..443c32981 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -120,6 +120,10 @@ class SystemUser(AssetUser): def __str__(self): return '{0.name}({0.username})'.format(self) + @property + def nodes_amount(self): + return self.nodes.all().count() + @property def login_mode_display(self): return self.get_login_mode_display() diff --git a/apps/assets/serializers/__init__.py b/apps/assets/serializers/__init__.py index 9c86ef407..2c3e9fbd4 100644 --- a/apps/assets/serializers/__init__.py +++ b/apps/assets/serializers/__init__.py @@ -10,3 +10,4 @@ from .domain import * from .cmd_filter import * from .asset_user import * from .gathered_user import * +from .favorite_asset import * diff --git a/apps/assets/serializers/admin_user.py b/apps/assets/serializers/admin_user.py index 918f1d9bf..63aac8cc0 100644 --- a/apps/assets/serializers/admin_user.py +++ b/apps/assets/serializers/admin_user.py @@ -45,7 +45,7 @@ class ReplaceNodeAdminUserSerializer(serializers.ModelSerializer): 管理用户更新关联到的集群 """ nodes = serializers.PrimaryKeyRelatedField( - many=True, queryset=Node.objects.all() + many=True, queryset=Node.objects ) class Meta: diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index c53b3057d..f198aee76 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -8,6 +8,10 @@ from django.utils.translation import ugettext_lazy as _ from orgs.mixins.serializers import BulkOrgResourceModelSerializer from common.serializers import AdaptedBulkListSerializer from ..models import Asset, Node, Label +from ..const import ( + GENERAL_LIMIT_SPECIAL_CHARACTERS_PATTERN, + GENERAL_LIMIT_SPECIAL_CHARACTERS_ERROR_MSG +) from .base import ConnectivitySerializer __all__ = [ @@ -94,10 +98,10 @@ class AssetSerializer(BulkOrgResourceModelSerializer): @staticmethod def validate_hostname(hostname): - pattern = r"^[\._@\w-]+$" + pattern = GENERAL_LIMIT_SPECIAL_CHARACTERS_PATTERN res = re.match(pattern, hostname) if res is None: - msg = _("* The hostname contains characters that are not allowed") + msg = GENERAL_LIMIT_SPECIAL_CHARACTERS_ERROR_MSG raise serializers.ValidationError(msg) return hostname @@ -136,6 +140,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer): class AssetSimpleSerializer(serializers.ModelSerializer): + connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity")) class Meta: model = Asset diff --git a/apps/assets/serializers/asset_user.py b/apps/assets/serializers/asset_user.py index a93d2b2c3..18b5ea982 100644 --- a/apps/assets/serializers/asset_user.py +++ b/apps/assets/serializers/asset_user.py @@ -79,7 +79,7 @@ class AssetUserAuthInfoSerializer(serializers.ModelSerializer): class AssetUserPushSerializer(serializers.Serializer): - asset = serializers.PrimaryKeyRelatedField(queryset=Asset.objects.all(), label=_("Asset")) + asset = serializers.PrimaryKeyRelatedField(queryset=Asset.objects, label=_("Asset")) username = serializers.CharField(max_length=1024) def create(self, validated_data): diff --git a/apps/assets/serializers/favorite_asset.py b/apps/assets/serializers/favorite_asset.py new file mode 100644 index 000000000..8429d959e --- /dev/null +++ b/apps/assets/serializers/favorite_asset.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# + +from rest_framework import serializers + +from orgs.utils import tmp_to_root_org +from common.serializers import AdaptedBulkListSerializer +from common.mixins import BulkSerializerMixin +from ..models import FavoriteAsset + + +__all__ = ['FavoriteAssetSerializer'] + + +class FavoriteAssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): + user = serializers.HiddenField( + default=serializers.CurrentUserDefault() + ) + + class Meta: + list_serializer_class = AdaptedBulkListSerializer + model = FavoriteAsset + fields = ['user', 'asset'] diff --git a/apps/assets/serializers/node.py b/apps/assets/serializers/node.py index 10a7a52d1..79e07df13 100644 --- a/apps/assets/serializers/node.py +++ b/apps/assets/serializers/node.py @@ -38,8 +38,10 @@ class NodeSerializer(BulkOrgResourceModelSerializer): return data -class NodeAssetsSerializer(serializers.ModelSerializer): - assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all()) +class NodeAssetsSerializer(BulkOrgResourceModelSerializer): + assets = serializers.PrimaryKeyRelatedField( + many=True, queryset=Asset.objects + ) class Meta: model = Node diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index eea93360b..1340741e6 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -1,3 +1,4 @@ +import re from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ @@ -6,6 +7,10 @@ from common.serializers import AdaptedBulkListSerializer from common.utils import ssh_pubkey_gen from orgs.mixins.serializers import BulkOrgResourceModelSerializer from ..models import SystemUser +from ..const import ( + GENERAL_LIMIT_SPECIAL_CHARACTERS_PATTERN, + GENERAL_LIMIT_SPECIAL_CHARACTERS_ERROR_MSG +) from .base import AuthSerializer, AuthSerializerMixin @@ -21,18 +26,28 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): fields = [ 'id', 'name', 'username', 'password', 'public_key', 'private_key', 'login_mode', 'login_mode_display', 'priority', 'protocol', - 'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment', 'nodes', - 'assets_amount', 'auto_generate_key' + 'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment', + 'assets_amount', 'nodes_amount', 'auto_generate_key' ] extra_kwargs = { 'password': {"write_only": True}, 'public_key': {"write_only": True}, 'private_key': {"write_only": True}, + 'nodes_amount': {'label': _('Node')}, 'assets_amount': {'label': _('Asset')}, 'login_mode_display': {'label': _('Login mode display')}, 'created_by': {'read_only': True}, } + @staticmethod + def validate_name(name): + pattern = GENERAL_LIMIT_SPECIAL_CHARACTERS_PATTERN + res = re.match(pattern, name) + if res is None: + msg = GENERAL_LIMIT_SPECIAL_CHARACTERS_ERROR_MSG + raise serializers.ValidationError(msg) + return name + def validate_auto_push(self, value): login_mode = self.initial_data.get("login_mode") protocol = self.initial_data.get("protocol") diff --git a/apps/assets/templates/assets/asset_bulk_update.html b/apps/assets/templates/assets/asset_bulk_update.html index 2dd0d8660..08df48e91 100644 --- a/apps/assets/templates/assets/asset_bulk_update.html +++ b/apps/assets/templates/assets/asset_bulk_update.html @@ -25,12 +25,20 @@ +{% include 'assets/_asset_list_modal.html' %} {% endblock %} {% block custom_foot_js %} {% endblock %} diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 46ea16483..35651429b 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -22,6 +22,7 @@ router.register(r'cmd-filters', api.CommandFilterViewSet, 'cmd-filter') router.register(r'asset-users', api.AssetUserViewSet, 'asset-user') router.register(r'asset-users-info', api.AssetUserExportViewSet, 'asset-user-info') router.register(r'gathered-users', api.GatheredUserViewSet, 'gathered-user') +router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset') cmd_filter_router = routers.NestedDefaultRouter(router, r'cmd-filters', lookup='filter') cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule') diff --git a/apps/audits/api.py b/apps/audits/api.py index 626749b29..4d7165b4b 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -1,14 +1,14 @@ # -*- coding: utf-8 -*- # -from rest_framework import viewsets - from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor +from orgs.mixins.api import OrgModelViewSet from .models import FTPLog from .serializers import FTPLogSerializer -class FTPLogViewSet(viewsets.ModelViewSet): - queryset = FTPLog.objects.all() +class FTPLogViewSet(OrgModelViewSet): + model = FTPLog serializer_class = FTPLogSerializer permission_classes = (IsOrgAdminOrAppUser | IsOrgAuditor,) + diff --git a/apps/authentication/backends/ldap.py b/apps/authentication/backends/ldap.py index 9dd151561..07ec0f375 100644 --- a/apps/authentication/backends/ldap.py +++ b/apps/authentication/backends/ldap.py @@ -32,6 +32,13 @@ class LDAPAuthorizationBackend(LDAPBackend): if not username: logger.info('Authenticate failed: username is None') return None + if settings.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS: + user_model = self.get_user_model() + exist = user_model.objects.filter(username=username).exists() + if not exist: + msg = 'Authentication failed: user ({}) is not in the user list' + logger.info(msg.format(username)) + return None ldap_user = LDAPUser(self, username=username.strip(), request=request) user = self.authenticate_ldap_user(ldap_user, password) logger.info('Authenticate user: {}'.format(user)) diff --git a/apps/common/api.py b/apps/common/api.py index 4c5d28254..d69540cfd 100644 --- a/apps/common/api.py +++ b/apps/common/api.py @@ -83,8 +83,6 @@ class LogTailApi(generics.RetrieveAPIView): return Response({"data": data, 'end': end, 'mark': new_mark}) - - class ResourcesIDCacheApi(APIView): def post(self, request, *args, **kwargs): spm = str(uuid.uuid4()) diff --git a/apps/common/filters.py b/apps/common/filters.py index 7792ff088..81138120b 100644 --- a/apps/common/filters.py +++ b/apps/common/filters.py @@ -63,7 +63,6 @@ class IDSpmFilter(filters.BaseFilterBackend): cache_key = const.KEY_CACHE_RESOURCES_ID.format(spm) resources_id = cache.get(cache_key) if not resources_id or not isinstance(resources_id, list): - queryset = queryset.none() return queryset queryset = queryset.filter(id__in=resources_id) return queryset diff --git a/apps/common/mixins/models.py b/apps/common/mixins/models.py index d4af23896..df3d899e0 100644 --- a/apps/common/mixins/models.py +++ b/apps/common/mixins/models.py @@ -1,12 +1,15 @@ # -*- coding: utf-8 -*- # - +import uuid from django.db import models from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -__all__ = ["NoDeleteManager", "NoDeleteModelMixin", "NoDeleteQuerySet"] +__all__ = [ + "NoDeleteManager", "NoDeleteModelMixin", "NoDeleteQuerySet", + "CommonModelMixin" +] class NoDeleteQuerySet(models.query.QuerySet): @@ -40,3 +43,13 @@ class NoDeleteModelMixin(models.Model): self.is_discard = True self.discard_time = timezone.now() return self.save() + + +class CommonModelMixin(models.Model): + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by')) + date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')) + date_updated = models.DateTimeField(auto_now=True, verbose_name=_('Date updated')) + + class Meta: + abstract = True diff --git a/apps/common/signals_handlers.py b/apps/common/signals_handlers.py index e7dd728c3..1fbfd536f 100644 --- a/apps/common/signals_handlers.py +++ b/apps/common/signals_handlers.py @@ -36,7 +36,7 @@ def on_request_finished_logging_db_query(sender, **kwargs): queries = connection.queries counters = defaultdict(Counter) for query in queries: - if not query['sql'].startswith('SELECT'): + if not query['sql'] or not query['sql'].startswith('SELECT'): continue tables = pattern.findall(query['sql']) table_name = ''.join(tables) diff --git a/apps/common/tree.py b/apps/common/tree.py index e73b43aa6..1ffe4347e 100644 --- a/apps/common/tree.py +++ b/apps/common/tree.py @@ -51,6 +51,8 @@ class TreeNode: result = True elif self.pId != other.pId: result = self.pId > other.pId + elif str(self.id).startswith('-') and not str(other.id).startswith('-'): + result = False else: result = self.name > other.name return result diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 9825eaece..0df26d149 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -378,6 +378,7 @@ defaults = { 'AUTH_LDAP_SYNC_IS_PERIODIC': False, 'AUTH_LDAP_SYNC_INTERVAL': None, 'AUTH_LDAP_SYNC_CRONTAB': None, + 'AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS': False, 'HTTP_BIND_HOST': '0.0.0.0', 'HTTP_LISTEN_PORT': 8080, 'WS_LISTEN_PORT': 8070, diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index b0db8dbab..6088358f1 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -429,6 +429,7 @@ AUTH_LDAP_SEARCH_PAGED_SIZE = CONFIG.AUTH_LDAP_SEARCH_PAGED_SIZE AUTH_LDAP_SYNC_IS_PERIODIC = CONFIG.AUTH_LDAP_SYNC_IS_PERIODIC AUTH_LDAP_SYNC_INTERVAL = CONFIG.AUTH_LDAP_SYNC_INTERVAL AUTH_LDAP_SYNC_CRONTAB = CONFIG.AUTH_LDAP_SYNC_CRONTAB +AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS = CONFIG.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS AUTH_LDAP_SERVER_URI = 'ldap://localhost:389' AUTH_LDAP_BIND_DN = 'cn=admin,dc=jumpserver,dc=org' diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 4d853714e..62707d108 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 4240cb36f..f282f0c22 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: 2019-10-08 17:09+0800\n" +"POT-Creation-Date: 2019-10-17 16:09+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -78,7 +78,7 @@ msgstr "运行参数" #: assets/forms/domain.py:15 assets/forms/label.py:13 #: assets/models/asset.py:295 assets/models/authbook.py:24 #: assets/models/gathered_user.py:14 assets/serializers/admin_user.py:32 -#: assets/serializers/asset_user.py:82 assets/serializers/system_user.py:31 +#: assets/serializers/asset_user.py:82 assets/serializers/system_user.py:36 #: assets/templates/assets/admin_user_list.html:46 #: assets/templates/assets/domain_detail.html:60 #: assets/templates/assets/domain_list.html:26 @@ -112,9 +112,9 @@ msgstr "资产" #: applications/templates/applications/remote_app_detail.html:53 #: applications/templates/applications/remote_app_list.html:20 #: applications/templates/applications/user_remote_app_list.html:16 -#: assets/forms/asset.py:24 assets/forms/domain.py:73 assets/forms/user.py:74 -#: assets/forms/user.py:94 assets/models/base.py:28 assets/models/cluster.py:18 -#: assets/models/cmd_filter.py:20 assets/models/domain.py:20 +#: assets/forms/asset.py:21 assets/forms/domain.py:73 assets/forms/user.py:75 +#: assets/forms/user.py:95 assets/models/base.py:28 assets/models/cluster.py:18 +#: assets/models/cmd_filter.py:21 assets/models/domain.py:20 #: assets/models/group.py:20 assets/models/label.py:18 #: assets/templates/assets/admin_user_detail.html:56 #: assets/templates/assets/admin_user_list.html:44 @@ -186,18 +186,18 @@ msgstr "参数" #: applications/models/remote_app.py:39 #: applications/templates/applications/remote_app_detail.html:73 #: assets/models/asset.py:174 assets/models/base.py:36 -#: assets/models/cluster.py:28 assets/models/cmd_filter.py:25 -#: assets/models/cmd_filter.py:58 assets/models/group.py:21 +#: assets/models/cluster.py:28 assets/models/cmd_filter.py:26 +#: assets/models/cmd_filter.py:59 assets/models/group.py:21 #: assets/templates/assets/admin_user_detail.html:68 -#: assets/templates/assets/asset_detail.html:124 +#: assets/templates/assets/asset_detail.html:122 #: assets/templates/assets/cmd_filter_detail.html:77 #: assets/templates/assets/domain_detail.html:72 #: assets/templates/assets/system_user_detail.html:100 -#: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:16 -#: perms/models/base.py:54 +#: common/mixins/models.py:50 ops/templates/ops/adhoc_detail.html:86 +#: orgs/models.py:16 perms/models/base.py:54 #: perms/templates/perms/asset_permission_detail.html:98 #: perms/templates/perms/remote_app_permission_detail.html:90 -#: users/models/user.py:414 users/serializers/v1.py:141 +#: users/models/user.py:414 users/serializers/v1.py:143 #: users/templates/users/user_detail.html:111 #: xpack/plugins/change_auth_plan/models.py:108 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113 @@ -216,7 +216,8 @@ msgstr "创建者" #: assets/models/label.py:25 assets/templates/assets/admin_user_detail.html:64 #: assets/templates/assets/cmd_filter_detail.html:69 #: assets/templates/assets/domain_detail.html:68 -#: assets/templates/assets/system_user_detail.html:96 ops/models/adhoc.py:45 +#: assets/templates/assets/system_user_detail.html:96 +#: common/mixins/models.py:51 ops/models/adhoc.py:45 #: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:64 #: orgs/models.py:17 perms/models/base.py:55 #: perms/templates/perms/asset_permission_detail.html:94 @@ -238,12 +239,12 @@ msgstr "创建日期" #: applications/templates/applications/remote_app_list.html:23 #: applications/templates/applications/user_remote_app_list.html:19 #: assets/models/asset.py:176 assets/models/base.py:33 -#: assets/models/cluster.py:29 assets/models/cmd_filter.py:22 -#: assets/models/cmd_filter.py:55 assets/models/domain.py:21 +#: assets/models/cluster.py:29 assets/models/cmd_filter.py:23 +#: assets/models/cmd_filter.py:56 assets/models/domain.py:21 #: assets/models/domain.py:53 assets/models/group.py:23 #: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:72 #: assets/templates/assets/admin_user_list.html:50 -#: assets/templates/assets/asset_detail.html:132 +#: assets/templates/assets/asset_detail.html:130 #: 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 @@ -310,6 +311,7 @@ msgstr "远程应用" #: users/templates/users/_user.html:50 #: users/templates/users/user_bulk_update.html:23 #: users/templates/users/user_detail.html:178 +#: users/templates/users/user_group_create_update.html:31 #: users/templates/users/user_password_update.html:75 #: users/templates/users/user_profile.html:209 #: users/templates/users/user_profile_update.html:67 @@ -394,7 +396,7 @@ msgstr "详情" #: assets/templates/assets/admin_user_detail.html:24 #: assets/templates/assets/admin_user_list.html:26 #: assets/templates/assets/admin_user_list.html:111 -#: assets/templates/assets/asset_detail.html:27 +#: assets/templates/assets/asset_detail.html:26 #: assets/templates/assets/asset_list.html:78 #: assets/templates/assets/asset_list.html:167 #: assets/templates/assets/cmd_filter_detail.html:29 @@ -440,7 +442,7 @@ msgstr "更新" #: applications/templates/applications/remote_app_list.html:55 #: assets/templates/assets/admin_user_detail.html:28 #: assets/templates/assets/admin_user_list.html:112 -#: assets/templates/assets/asset_detail.html:31 +#: assets/templates/assets/asset_detail.html:30 #: assets/templates/assets/asset_list.html:168 #: assets/templates/assets/cmd_filter_detail.html:33 #: assets/templates/assets/cmd_filter_list.html:59 @@ -498,7 +500,7 @@ msgstr "创建远程应用" #: applications/templates/applications/remote_app_list.html:24 #: applications/templates/applications/user_remote_app_list.html:20 -#: assets/models/cmd_filter.py:54 +#: assets/models/cmd_filter.py:55 #: assets/templates/assets/_asset_user_list.html:25 #: assets/templates/assets/admin_user_list.html:51 #: assets/templates/assets/asset_list.html:100 @@ -522,7 +524,7 @@ msgstr "创建远程应用" #: settings/templates/settings/terminal_setting.html:107 #: terminal/templates/terminal/session_list.html:36 #: terminal/templates/terminal/terminal_list.html:36 -#: users/templates/users/_granted_assets.html:29 +#: users/templates/users/_granted_assets.html:34 #: users/templates/users/user_group_list.html:38 #: users/templates/users/user_list.html:41 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:60 @@ -537,7 +539,6 @@ msgid "Action" msgstr "动作" #: applications/templates/applications/user_remote_app_list.html:52 -#: assets/templates/assets/user_asset_list.html:32 #: perms/models/asset_permission.py:32 msgid "Connect" msgstr "连接" @@ -564,32 +565,40 @@ msgstr "远程应用详情" msgid "My RemoteApp" msgstr "我的远程应用" -#: assets/api/node.py:58 +#: assets/api/node.py:61 msgid "You can't update the root node name" msgstr "不能修改根节点名称" -#: assets/api/node.py:266 +#: assets/api/node.py:68 +msgid "Deletion failed and the node contains children or assets" +msgstr "删除失败,节点包含子节点或资产" + +#: assets/api/node.py:273 msgid "Update node asset hardware information: {}" msgstr "更新节点资产硬件信息: {}" -#: assets/api/node.py:280 +#: assets/api/node.py:287 msgid "Test if the assets under the node are connectable: {}" msgstr "测试节点下资产是否可连接: {}" -#: assets/forms/asset.py:18 +#: assets/const.py:8 msgid "Only Numbers、letters、 chinese and characters ( {} ) are allowed" msgstr "只允许包含数字、字母、中文和特殊字符( {} )" -#: assets/forms/asset.py:28 assets/models/asset.py:140 +#: assets/const.py:14 +msgid "* The contains characters that are not allowed" +msgstr "* 包含不被允许的字符" + +#: assets/forms/asset.py:25 assets/models/asset.py:140 #: assets/models/domain.py:50 #: assets/templates/assets/domain_gateway_list.html:69 #: settings/templates/settings/replay_storage_create.html:59 msgid "Port" msgstr "端口" -#: assets/forms/asset.py:59 assets/models/asset.py:145 -#: assets/models/user.py:110 assets/templates/assets/asset_detail.html:190 -#: assets/templates/assets/asset_detail.html:198 +#: assets/forms/asset.py:56 assets/models/asset.py:145 +#: assets/models/user.py:110 assets/templates/assets/asset_detail.html:188 +#: assets/templates/assets/asset_detail.html:196 #: assets/templates/assets/system_user_assets.html:83 #: perms/models/asset_permission.py:81 #: xpack/plugins/change_auth_plan/models.py:74 @@ -598,16 +607,16 @@ msgstr "端口" msgid "Nodes" msgstr "节点" -#: assets/forms/asset.py:62 assets/forms/asset.py:109 +#: assets/forms/asset.py:59 assets/forms/asset.py:106 #: assets/models/asset.py:149 assets/models/cluster.py:19 -#: assets/models/user.py:68 assets/templates/assets/asset_detail.html:76 +#: assets/models/user.py:68 assets/templates/assets/asset_detail.html:74 #: templates/_nav.html:44 xpack/plugins/cloud/models.py:161 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:68 #: xpack/plugins/orgs/templates/orgs/org_list.html:19 msgid "Admin user" msgstr "管理用户" -#: assets/forms/asset.py:65 assets/forms/asset.py:112 assets/forms/asset.py:152 +#: assets/forms/asset.py:62 assets/forms/asset.py:109 assets/forms/asset.py:149 #: assets/templates/assets/asset_create.html:48 #: assets/templates/assets/asset_create.html:50 #: assets/templates/assets/asset_list.html:85 @@ -615,16 +624,16 @@ msgstr "管理用户" msgid "Label" msgstr "标签" -#: assets/forms/asset.py:68 assets/forms/asset.py:115 +#: assets/forms/asset.py:65 assets/forms/asset.py:112 #: assets/models/asset.py:144 assets/models/domain.py:26 -#: assets/models/domain.py:52 assets/templates/assets/asset_detail.html:80 -#: assets/templates/assets/user_asset_list.html:53 +#: assets/models/domain.py:52 assets/templates/assets/asset_detail.html:78 +#: assets/templates/assets/user_asset_list.html:80 #: xpack/plugins/orgs/templates/orgs/org_list.html:18 msgid "Domain" msgstr "网域" -#: assets/forms/asset.py:72 assets/forms/asset.py:106 assets/forms/asset.py:119 -#: assets/forms/asset.py:155 assets/models/node.py:409 +#: assets/forms/asset.py:69 assets/forms/asset.py:103 assets/forms/asset.py:116 +#: assets/forms/asset.py:152 assets/models/node.py:421 #: assets/templates/assets/asset_create.html:42 #: perms/forms/asset_permission.py:83 perms/forms/asset_permission.py:90 #: perms/templates/perms/asset_permission_list.html:53 @@ -639,7 +648,7 @@ msgstr "网域" msgid "Node" msgstr "节点" -#: assets/forms/asset.py:77 assets/forms/asset.py:124 +#: assets/forms/asset.py:74 assets/forms/asset.py:121 msgid "" "root or other NOPASSWD sudo privilege user existed in asset,If asset is " "windows or other set any one, more see admin user left menu" @@ -647,16 +656,16 @@ msgstr "" "root或其他拥有NOPASSWD: ALL权限的用户, 如果是windows或其它硬件可以随意设置一" "个, 更多信息查看左侧 `管理用户` 菜单" -#: assets/forms/asset.py:80 assets/forms/asset.py:127 +#: assets/forms/asset.py:77 assets/forms/asset.py:124 msgid "Windows 2016 RDP protocol is different, If is window 2016, set it" msgstr "Windows 2016的RDP协议与之前不同,如果是请设置" -#: assets/forms/asset.py:81 assets/forms/asset.py:128 +#: assets/forms/asset.py:78 assets/forms/asset.py:125 msgid "" "If your have some network not connect with each other, you can set domain" msgstr "如果有多个的互相隔离的网络,设置资产属于的网域,使用网域网关跳转登录" -#: assets/forms/asset.py:135 assets/forms/asset.py:139 +#: assets/forms/asset.py:132 assets/forms/asset.py:136 #: assets/forms/domain.py:17 assets/forms/label.py:15 #: perms/templates/perms/asset_permission_asset.html:78 #: xpack/plugins/change_auth_plan/forms.py:55 @@ -676,7 +685,7 @@ msgstr "不能包含特殊字符" msgid "SSH gateway support proxy SSH,RDP,VNC" msgstr "SSH网关,支持代理SSH,RDP和VNC" -#: assets/forms/domain.py:74 assets/forms/user.py:75 assets/forms/user.py:95 +#: assets/forms/domain.py:74 assets/forms/user.py:76 assets/forms/user.py:96 #: assets/models/base.py:29 assets/models/gathered_user.py:16 #: assets/templates/assets/_asset_user_auth_update_modal.html:15 #: assets/templates/assets/_asset_user_auth_view_modal.html:21 @@ -692,7 +701,7 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC" #: ops/models/adhoc.py:189 perms/templates/perms/asset_permission_list.html:70 #: perms/templates/perms/asset_permission_user.html:55 #: perms/templates/perms/remote_app_permission_user.html:54 -#: settings/templates/settings/_ldap_list_users_modal.html:30 users/forms.py:14 +#: settings/templates/settings/_ldap_list_users_modal.html:30 users/forms.py:13 #: users/models/user.py:371 users/templates/users/_select_user_modal.html:14 #: users/templates/users/user_detail.html:67 #: users/templates/users/user_list.html:36 @@ -708,18 +717,18 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC" msgid "Username" msgstr "用户名" -#: assets/forms/user.py:25 +#: assets/forms/user.py:26 msgid "Password or private key passphrase" msgstr "密码或密钥密码" -#: assets/forms/user.py:26 assets/models/base.py:30 +#: assets/forms/user.py:27 assets/models/base.py:30 #: assets/serializers/asset_user.py:63 #: assets/templates/assets/_asset_user_auth_update_modal.html:21 #: assets/templates/assets/_asset_user_auth_view_modal.html:27 #: authentication/forms.py:15 #: authentication/templates/authentication/login.html:68 #: authentication/templates/authentication/new_login.html:95 -#: settings/forms.py:114 users/forms.py:16 users/forms.py:28 +#: settings/forms.py:114 users/forms.py:15 users/forms.py:27 #: users/templates/users/reset_password.html:53 #: users/templates/users/user_password_authentication.html:18 #: users/templates/users/user_password_update.html:44 @@ -731,31 +740,31 @@ msgstr "密码或密钥密码" msgid "Password" msgstr "密码" -#: assets/forms/user.py:29 assets/serializers/asset_user.py:71 +#: assets/forms/user.py:30 assets/serializers/asset_user.py:71 #: assets/templates/assets/_asset_user_auth_update_modal.html:27 #: users/models/user.py:400 msgid "Private key" msgstr "ssh私钥" -#: assets/forms/user.py:41 +#: assets/forms/user.py:42 msgid "Invalid private key, Only support RSA/DSA format key" msgstr "不合法的密钥,仅支持RSA/DSA格式的密钥" -#: assets/forms/user.py:52 +#: assets/forms/user.py:53 msgid "Password and private key file must be input one" msgstr "密码和私钥, 必须输入一个" -#: assets/forms/user.py:97 assets/models/cmd_filter.py:31 +#: assets/forms/user.py:98 assets/models/cmd_filter.py:32 #: assets/models/user.py:118 assets/templates/assets/_system_user.html:66 #: assets/templates/assets/system_user_detail.html:165 msgid "Command filter" msgstr "命令过滤器" -#: assets/forms/user.py:101 +#: assets/forms/user.py:103 msgid "Auto push system user to asset" msgstr "自动推送系统用户到资产" -#: assets/forms/user.py:102 +#: assets/forms/user.py:104 msgid "" "1-100, High level will be using login asset as default, if user was granted " "more than 2 system user" @@ -763,13 +772,13 @@ msgstr "" "1-100, 1最低优先级,100最高优先级。授权多个用户时,高优先级的系统用户将会作为" "默认登录用户" -#: assets/forms/user.py:104 +#: assets/forms/user.py:106 msgid "" "If you choose manual login mode, you do not need to fill in the username and " "password." msgstr "如果选择手动登录模式,用户名和密码可以不填写" -#: assets/forms/user.py:106 +#: assets/forms/user.py:108 msgid "Use comma split multi command, ex: /bin/whoami,/bin/ifconfig" msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig" @@ -777,13 +786,13 @@ msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig" #: assets/serializers/asset_user.py:28 #: assets/templates/assets/_asset_list_modal.html:47 #: assets/templates/assets/_asset_user_list.html:20 -#: assets/templates/assets/asset_detail.html:64 +#: assets/templates/assets/asset_detail.html:62 #: assets/templates/assets/asset_list.html:97 #: assets/templates/assets/domain_gateway_list.html:68 -#: assets/templates/assets/user_asset_list.html:49 +#: assets/templates/assets/user_asset_list.html:76 #: audits/templates/audits/login_log_list.html:60 #: perms/templates/perms/asset_permission_asset.html:58 settings/forms.py:144 -#: users/templates/users/_granted_assets.html:26 +#: users/templates/users/_granted_assets.html:31 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:54 #: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:73 msgid "IP" @@ -795,61 +804,62 @@ msgstr "IP" #: assets/templates/assets/_asset_user_auth_update_modal.html:9 #: assets/templates/assets/_asset_user_auth_view_modal.html:15 #: assets/templates/assets/_asset_user_list.html:19 -#: assets/templates/assets/asset_detail.html:60 +#: assets/templates/assets/asset_detail.html:58 #: assets/templates/assets/asset_list.html:96 -#: assets/templates/assets/user_asset_list.html:48 +#: assets/templates/assets/user_asset_list.html:75 #: perms/templates/perms/asset_permission_asset.html:57 #: perms/templates/perms/asset_permission_list.html:73 settings/forms.py:143 -#: users/templates/users/_granted_assets.html:25 +#: users/templates/users/_granted_assets.html:30 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:53 #: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:72 msgid "Hostname" msgstr "主机名" #: assets/models/asset.py:139 assets/models/domain.py:51 -#: assets/models/user.py:113 assets/templates/assets/asset_detail.html:72 +#: assets/models/user.py:113 assets/templates/assets/asset_detail.html:70 #: assets/templates/assets/domain_gateway_list.html:70 #: assets/templates/assets/system_user_detail.html:70 #: assets/templates/assets/system_user_list.html:53 #: terminal/templates/terminal/session_list.html:31 +#: terminal/templates/terminal/session_list.html:75 msgid "Protocol" msgstr "协议" -#: assets/models/asset.py:142 assets/serializers/asset.py:64 +#: assets/models/asset.py:142 assets/serializers/asset.py:68 #: assets/templates/assets/asset_create.html:24 -#: assets/templates/assets/user_asset_list.html:50 +#: assets/templates/assets/user_asset_list.html:77 #: perms/serializers/user_permission.py:48 msgid "Protocols" msgstr "协议组" -#: assets/models/asset.py:143 assets/templates/assets/asset_detail.html:104 -#: assets/templates/assets/user_asset_list.html:51 +#: assets/models/asset.py:143 assets/templates/assets/asset_detail.html:102 +#: assets/templates/assets/user_asset_list.html:78 msgid "Platform" msgstr "系统平台" #: assets/models/asset.py:146 assets/models/authbook.py:27 -#: assets/models/cmd_filter.py:21 assets/models/domain.py:54 -#: assets/models/label.py:22 assets/templates/assets/asset_detail.html:112 +#: assets/models/cmd_filter.py:22 assets/models/domain.py:54 +#: assets/models/label.py:22 assets/templates/assets/asset_detail.html:110 msgid "Is active" msgstr "激活" -#: assets/models/asset.py:152 assets/templates/assets/asset_detail.html:68 +#: assets/models/asset.py:152 assets/templates/assets/asset_detail.html:66 msgid "Public IP" msgstr "公网IP" -#: assets/models/asset.py:153 assets/templates/assets/asset_detail.html:120 +#: assets/models/asset.py:153 assets/templates/assets/asset_detail.html:118 msgid "Asset number" msgstr "资产编号" -#: assets/models/asset.py:156 assets/templates/assets/asset_detail.html:84 +#: assets/models/asset.py:156 assets/templates/assets/asset_detail.html:82 msgid "Vendor" msgstr "制造商" -#: assets/models/asset.py:157 assets/templates/assets/asset_detail.html:88 +#: assets/models/asset.py:157 assets/templates/assets/asset_detail.html:86 msgid "Model" msgstr "型号" -#: assets/models/asset.py:158 assets/templates/assets/asset_detail.html:116 +#: assets/models/asset.py:158 assets/templates/assets/asset_detail.html:114 msgid "Serial number" msgstr "序列号" @@ -870,7 +880,7 @@ msgstr "CPU核数" msgid "CPU vcpus" msgstr "CPU总数" -#: assets/models/asset.py:164 assets/templates/assets/asset_detail.html:96 +#: assets/models/asset.py:164 assets/templates/assets/asset_detail.html:94 msgid "Memory" msgstr "内存" @@ -882,7 +892,7 @@ msgstr "硬盘大小" msgid "Disk info" msgstr "硬盘信息" -#: assets/models/asset.py:168 assets/templates/assets/asset_detail.html:108 +#: assets/models/asset.py:168 assets/templates/assets/asset_detail.html:106 msgid "OS" msgstr "操作系统" @@ -899,7 +909,7 @@ msgid "Hostname raw" msgstr "主机名原始" #: assets/models/asset.py:173 assets/templates/assets/asset_create.html:46 -#: assets/templates/assets/asset_detail.html:224 templates/_nav.html:46 +#: assets/templates/assets/asset_detail.html:222 templates/_nav.html:46 msgid "Labels" msgstr "标签管理" @@ -930,7 +940,8 @@ msgid "SSH public key" msgstr "ssh公钥" #: assets/models/base.py:35 assets/models/gathered_user.py:21 -#: assets/templates/assets/cmd_filter_detail.html:73 ops/models/adhoc.py:46 +#: assets/templates/assets/cmd_filter_detail.html:73 common/mixins/models.py:52 +#: ops/models/adhoc.py:46 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:109 #: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:76 msgid "Date updated" @@ -994,11 +1005,11 @@ msgstr "北京电信" msgid "BGP full netcom" msgstr "BGP全网通" -#: assets/models/cmd_filter.py:38 +#: assets/models/cmd_filter.py:39 msgid "Regex" msgstr "正则表达式" -#: assets/models/cmd_filter.py:39 ops/models/command.py:21 +#: assets/models/cmd_filter.py:40 ops/models/command.py:21 #: ops/templates/ops/command_execution_list.html:64 terminal/models.py:163 #: terminal/templates/terminal/command_list.html:28 #: terminal/templates/terminal/command_list.html:68 @@ -1007,19 +1018,19 @@ msgstr "正则表达式" msgid "Command" msgstr "命令" -#: assets/models/cmd_filter.py:44 +#: assets/models/cmd_filter.py:45 msgid "Deny" msgstr "拒绝" -#: assets/models/cmd_filter.py:45 +#: assets/models/cmd_filter.py:46 msgid "Allow" msgstr "允许" -#: assets/models/cmd_filter.py:49 +#: assets/models/cmd_filter.py:50 msgid "Filter" msgstr "过滤器" -#: assets/models/cmd_filter.py:50 +#: assets/models/cmd_filter.py:51 #: assets/templates/assets/cmd_filter_rule_list.html:58 #: audits/templates/audits/login_log_list.html:58 #: perms/templates/perms/remote_app_permission_remote_app.html:54 @@ -1030,26 +1041,26 @@ msgstr "过滤器" msgid "Type" msgstr "类型" -#: assets/models/cmd_filter.py:51 assets/models/user.py:112 +#: assets/models/cmd_filter.py:52 assets/models/user.py:112 #: assets/templates/assets/cmd_filter_rule_list.html:60 msgid "Priority" msgstr "优先级" -#: assets/models/cmd_filter.py:51 +#: assets/models/cmd_filter.py:52 msgid "1-100, the higher will be match first" msgstr "优先级可选范围为1-100,1最低优先级,100最高优先级" -#: assets/models/cmd_filter.py:53 +#: assets/models/cmd_filter.py:54 #: assets/templates/assets/cmd_filter_rule_list.html:59 #: xpack/plugins/license/models.py:29 msgid "Content" msgstr "内容" -#: assets/models/cmd_filter.py:53 +#: assets/models/cmd_filter.py:54 msgid "One line one command" msgstr "每行一个命令" -#: assets/models/cmd_filter.py:64 +#: assets/models/cmd_filter.py:63 msgid "Command filter rule" msgstr "命令过滤规则" @@ -1097,9 +1108,9 @@ msgstr "默认资产组" #: terminal/models.py:156 terminal/templates/terminal/command_list.html:29 #: terminal/templates/terminal/command_list.html:65 #: terminal/templates/terminal/session_list.html:27 -#: terminal/templates/terminal/session_list.html:71 users/forms.py:312 +#: terminal/templates/terminal/session_list.html:71 users/forms.py:319 #: users/models/user.py:127 users/models/user.py:143 users/models/user.py:500 -#: users/serializers/v1.py:130 users/templates/users/user_group_detail.html:78 +#: users/serializers/v1.py:132 users/templates/users/user_group_detail.html:78 #: users/templates/users/user_group_list.html:36 users/views/user.py:250 #: xpack/plugins/orgs/forms.py:28 #: xpack/plugins/orgs/templates/orgs/org_detail.html:113 @@ -1107,7 +1118,7 @@ msgstr "默认资产组" msgid "User" msgstr "用户" -#: assets/models/label.py:19 assets/models/node.py:400 +#: assets/models/label.py:19 assets/models/node.py:412 #: assets/templates/assets/label_list.html:15 settings/models.py:30 msgid "Value" msgstr "值" @@ -1128,7 +1139,11 @@ msgstr "未分组" msgid "empty" msgstr "空" -#: assets/models/node.py:399 +#: assets/models/node.py:328 +msgid "favorite" +msgstr "收藏夹" + +#: assets/models/node.py:411 msgid "Key" msgstr "键" @@ -1182,7 +1197,7 @@ msgstr "Shell" msgid "Login mode" msgstr "登录模式" -#: assets/models/user.py:162 assets/templates/assets/user_asset_list.html:52 +#: assets/models/user.py:162 assets/templates/assets/user_asset_list.html:79 #: audits/models.py:20 audits/templates/audits/ftp_log_list.html:52 #: audits/templates/audits/ftp_log_list.html:75 #: perms/forms/asset_permission.py:86 perms/forms/remote_app_permission.py:43 @@ -1198,7 +1213,7 @@ msgstr "登录模式" #: terminal/templates/terminal/command_list.html:67 #: terminal/templates/terminal/session_list.html:29 #: terminal/templates/terminal/session_list.html:73 -#: users/templates/users/_granted_assets.html:27 +#: users/templates/users/_granted_assets.html:32 #: xpack/plugins/orgs/templates/orgs/org_list.html:20 msgid "System user" msgstr "系统用户" @@ -1208,50 +1223,47 @@ msgstr "系统用户" msgid "%(value)s is not an even number" msgstr "%(value)s is not an even number" -#: assets/models/utils.py:43 assets/tasks/const.py:81 +#: assets/models/utils.py:43 assets/tasks/const.py:84 msgid "Unreachable" msgstr "不可达" -#: assets/models/utils.py:44 assets/tasks/const.py:82 +#: assets/models/utils.py:44 assets/tasks/const.py:85 #: assets/templates/assets/asset_list.html:99 msgid "Reachable" msgstr "可连接" -#: assets/models/utils.py:45 assets/tasks/const.py:83 +#: assets/models/utils.py:45 assets/tasks/const.py:86 #: authentication/utils.py:13 xpack/plugins/license/models.py:78 msgid "Unknown" msgstr "未知" -#: assets/serializers/asset.py:22 +#: assets/serializers/asset.py:26 msgid "Protocol format should {}/{}" msgstr "协议格式 {}/{}" -#: assets/serializers/asset.py:39 +#: assets/serializers/asset.py:43 msgid "Protocol duplicate: {}" msgstr "协议重复: {}" -#: assets/serializers/asset.py:65 assets/serializers/asset_user.py:29 +#: assets/serializers/asset.py:69 assets/serializers/asset.py:143 +#: assets/serializers/asset_user.py:29 #: assets/templates/assets/_asset_user_list.html:23 msgid "Connectivity" msgstr "连接" -#: assets/serializers/asset.py:91 +#: assets/serializers/asset.py:95 msgid "Hardware info" msgstr "硬件信息" -#: assets/serializers/asset.py:92 orgs/mixins/serializers.py:27 +#: assets/serializers/asset.py:96 orgs/mixins/serializers.py:27 msgid "Org name" msgstr "组织名称" -#: assets/serializers/asset.py:100 -msgid "* The hostname contains characters that are not allowed" -msgstr "* 主机名包含不被允许的字符" - #: assets/serializers/asset_user.py:31 msgid "Backend" msgstr "后端" -#: assets/serializers/asset_user.py:67 users/forms.py:263 +#: assets/serializers/asset_user.py:67 users/forms.py:262 #: users/models/user.py:403 users/templates/users/first_login.html:42 #: users/templates/users/user_password_update.html:49 #: users/templates/users/user_profile.html:69 @@ -1277,15 +1289,15 @@ msgstr "值" msgid "The same level node name cannot be the same" msgstr "同级别节点名字不能重复" -#: assets/serializers/system_user.py:32 +#: assets/serializers/system_user.py:37 msgid "Login mode display" msgstr "登录模式显示" -#: assets/serializers/system_user.py:67 +#: assets/serializers/system_user.py:81 msgid "* Automatic login mode must fill in the username." msgstr "自动登录模式,必须填写用户名" -#: assets/serializers/system_user.py:78 +#: assets/serializers/system_user.py:92 msgid "Password or private key required" msgstr "密码或密钥密码需要一个" @@ -1424,6 +1436,7 @@ msgstr "资产列表" #: assets/templates/assets/_asset_list_modal.html:33 #: assets/templates/assets/_node_tree.html:40 #: ops/templates/ops/command_execution_create.html:49 +#: ops/templates/ops/command_execution_create.html:143 #: users/templates/users/_granted_assets.html:7 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:66 msgid "Loading" @@ -1443,7 +1456,7 @@ msgid "Please input password" msgstr "请输入密码" #: assets/templates/assets/_asset_user_auth_update_modal.html:68 -#: assets/templates/assets/asset_detail.html:304 +#: assets/templates/assets/asset_detail.html:302 #: users/templates/users/user_detail.html:313 #: users/templates/users/user_detail.html:340 #: xpack/plugins/interface/views.py:35 @@ -1492,7 +1505,7 @@ msgstr "查看" #: assets/templates/assets/_asset_user_list.html:75 #: assets/templates/assets/admin_user_assets.html:61 #: assets/templates/assets/asset_asset_user_list.html:57 -#: assets/templates/assets/asset_detail.html:178 +#: assets/templates/assets/asset_detail.html:176 #: assets/templates/assets/system_user_assets.html:63 #: assets/templates/assets/system_user_detail.html:151 msgid "Test" @@ -1614,7 +1627,7 @@ msgstr "快速更新" #: assets/templates/assets/admin_user_assets.html:58 #: assets/templates/assets/asset_asset_user_list.html:54 -#: assets/templates/assets/asset_detail.html:175 +#: assets/templates/assets/asset_detail.html:173 msgid "Test connective" msgstr "测试可连接性" @@ -1631,7 +1644,7 @@ msgid "Select nodes" msgstr "选择节点" #: assets/templates/assets/admin_user_detail.html:100 -#: assets/templates/assets/asset_detail.html:204 +#: assets/templates/assets/asset_detail.html:202 #: assets/templates/assets/asset_list.html:422 #: assets/templates/assets/cmd_filter_detail.html:106 #: assets/templates/assets/system_user_assets.html:97 @@ -1639,7 +1652,7 @@ msgstr "选择节点" #: assets/templates/assets/system_user_list.html:139 #: authentication/templates/authentication/_mfa_confirm_modal.html:20 #: settings/templates/settings/terminal_setting.html:168 -#: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:108 +#: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:112 #: users/templates/users/user_detail.html:394 #: users/templates/users/user_detail.html:420 #: users/templates/users/user_detail.html:443 @@ -1719,8 +1732,8 @@ msgid "Asset users of" msgstr "资产用户" #: assets/templates/assets/asset_asset_user_list.html:47 -#: assets/templates/assets/asset_detail.html:144 -#: terminal/templates/terminal/session_detail.html:81 +#: assets/templates/assets/asset_detail.html:142 +#: terminal/templates/terminal/session_detail.html:85 #: users/templates/users/user_detail.html:140 #: users/templates/users/user_profile.html:150 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:128 @@ -1739,21 +1752,21 @@ msgstr "选择需要修改属性" msgid "Select all" msgstr "全选" -#: assets/templates/assets/asset_detail.html:92 +#: assets/templates/assets/asset_detail.html:90 msgid "CPU" msgstr "CPU" -#: assets/templates/assets/asset_detail.html:100 +#: assets/templates/assets/asset_detail.html:98 msgid "Disk" msgstr "硬盘" -#: assets/templates/assets/asset_detail.html:128 +#: assets/templates/assets/asset_detail.html:126 #: users/templates/users/user_detail.html:115 #: users/templates/users/user_profile.html:106 msgid "Date joined" msgstr "创建日期" -#: assets/templates/assets/asset_detail.html:150 authentication/models.py:15 +#: assets/templates/assets/asset_detail.html:148 authentication/models.py:15 #: authentication/templates/authentication/_access_key_modal.html:32 #: perms/models/base.py:51 #: perms/templates/perms/asset_permission_create_update.html:55 @@ -1767,11 +1780,11 @@ msgstr "创建日期" msgid "Active" msgstr "激活中" -#: assets/templates/assets/asset_detail.html:167 +#: assets/templates/assets/asset_detail.html:165 msgid "Refresh hardware" msgstr "更新硬件信息" -#: assets/templates/assets/asset_detail.html:170 +#: assets/templates/assets/asset_detail.html:168 msgid "Refresh" msgstr "刷新" @@ -1861,7 +1874,6 @@ msgstr "删除选择资产" #: users/templates/users/user_detail.html:392 #: users/templates/users/user_detail.html:418 #: users/templates/users/user_detail.html:486 -#: users/templates/users/user_group_create_update.html:31 #: users/templates/users/user_group_list.html:118 #: users/templates/users/user_list.html:254 #: xpack/plugins/interface/templates/interface/interface.html:101 @@ -2251,7 +2263,7 @@ msgstr "Agent" #: audits/models.py:85 audits/templates/audits/login_log_list.html:62 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 -#: users/forms.py:175 users/models/user.py:395 +#: users/forms.py:174 users/models/user.py:395 #: users/templates/users/first_login.html:45 msgid "MFA" msgstr "MFA" @@ -2474,7 +2486,7 @@ msgid "" "after {} minutes)" msgstr "账号已被锁定(请联系管理员解锁 或 {}分钟后重试)" -#: authentication/forms.py:66 users/forms.py:22 +#: authentication/forms.py:66 users/forms.py:21 msgid "MFA code" msgstr "MFA 验证码" @@ -2499,7 +2511,7 @@ msgid "Secret" msgstr "密文" #: authentication/templates/authentication/_access_key_modal.html:48 -#: users/templates/users/_granted_assets.html:75 +#: users/templates/users/_granted_assets.html:80 msgid "Show" msgstr "显示" @@ -2707,11 +2719,11 @@ msgstr "" msgid "Encrypt field using Secret Key" msgstr "" -#: common/mixins/models.py:31 +#: common/mixins/models.py:34 msgid "is discard" msgstr "" -#: common/mixins/models.py:32 +#: common/mixins/models.py:35 msgid "discard time" msgstr "" @@ -2984,38 +2996,38 @@ msgid "Task log" msgstr "任务列表" #: ops/templates/ops/command_execution_create.html:90 -#: terminal/templates/terminal/session_detail.html:91 -#: terminal/templates/terminal/session_detail.html:100 +#: terminal/templates/terminal/session_detail.html:95 +#: terminal/templates/terminal/session_detail.html:104 msgid "Go" msgstr "" -#: ops/templates/ops/command_execution_create.html:215 +#: ops/templates/ops/command_execution_create.html:216 msgid "Selected assets" msgstr "已选择资产" -#: ops/templates/ops/command_execution_create.html:218 +#: ops/templates/ops/command_execution_create.html:219 msgid "In total" msgstr "总共" -#: ops/templates/ops/command_execution_create.html:254 +#: ops/templates/ops/command_execution_create.html:256 msgid "" "Select the left asset, select the running system user, execute command in " "batch" msgstr "选择左侧资产, 选择运行的系统用户,批量执行命令" -#: ops/templates/ops/command_execution_create.html:275 +#: ops/templates/ops/command_execution_create.html:299 msgid "Unselected assets" msgstr "没有选中资产" -#: ops/templates/ops/command_execution_create.html:279 +#: ops/templates/ops/command_execution_create.html:303 msgid "No input command" msgstr "没有输入命令" -#: ops/templates/ops/command_execution_create.html:283 +#: ops/templates/ops/command_execution_create.html:307 msgid "No system user was selected" msgstr "没有选择系统用户" -#: ops/templates/ops/command_execution_create.html:328 +#: ops/templates/ops/command_execution_create.html:317 msgid "Pending" msgstr "等待" @@ -3099,7 +3111,7 @@ msgstr "命令执行列表" msgid "Command execution" msgstr "命令执行" -#: orgs/mixins/models.py:61 orgs/mixins/serializers.py:26 orgs/models.py:31 +#: orgs/mixins/models.py:58 orgs/mixins/serializers.py:26 orgs/models.py:31 msgid "Organization" msgstr "组织" @@ -3116,7 +3128,7 @@ msgstr "空" #: perms/templates/perms/asset_permission_list.html:71 #: perms/templates/perms/asset_permission_list.html:118 #: perms/templates/perms/remote_app_permission_list.html:16 -#: templates/_nav.html:21 users/forms.py:286 users/models/group.py:26 +#: templates/_nav.html:21 users/forms.py:293 users/models/group.py:26 #: users/models/user.py:379 users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_detail.html:218 #: users/templates/users/user_list.html:38 @@ -3394,21 +3406,21 @@ msgstr "连接LDAP成功" msgid "Match {} s users" msgstr "匹配 {} 个用户" -#: settings/api.py:163 +#: settings/api.py:151 msgid "succeed: {} failed: {} total: {}" msgstr "成功:{} 失败:{} 总数:{}" -#: settings/api.py:185 settings/api.py:221 +#: settings/api.py:173 settings/api.py:209 msgid "" "Error: Account invalid (Please make sure the information such as Access key " "or Secret key is correct)" msgstr "错误:账户无效 (请确保 Access key 或 Secret key 等信息正确)" -#: settings/api.py:191 settings/api.py:227 +#: settings/api.py:179 settings/api.py:215 msgid "Create succeed" msgstr "创建成功" -#: settings/api.py:209 settings/api.py:247 +#: settings/api.py:197 settings/api.py:235 #: settings/templates/settings/terminal_setting.html:154 msgid "Delete succeed" msgstr "删除成功" @@ -3923,11 +3935,11 @@ msgstr "删除失败" msgid "Are you sure about deleting it?" msgstr "您确定删除吗?" -#: settings/utils.py:91 +#: settings/utils.py:98 msgid "Search no entry matched in ou {}" msgstr "在ou:{}中没有匹配条目" -#: settings/utils.py:146 +#: settings/utils.py:172 msgid "The user source is not LDAP" msgstr "用户来源不是LDAP" @@ -3963,7 +3975,7 @@ msgid "Commercial support" msgstr "商业支持" #: templates/_header_bar.html:70 templates/_nav.html:30 -#: templates/_nav_user.html:32 users/forms.py:154 +#: templates/_nav_user.html:32 users/forms.py:153 #: users/templates/users/_user.html:43 #: users/templates/users/first_login.html:39 #: users/templates/users/user_password_update.html:40 @@ -4384,7 +4396,7 @@ msgstr "线程数" msgid "Boot Time" msgstr "运行时间" -#: terminal/models.py:162 terminal/templates/terminal/session_list.html:136 +#: terminal/models.py:162 terminal/templates/terminal/session_list.html:137 msgid "Replay" msgstr "回放" @@ -4404,7 +4416,7 @@ msgstr "参数" msgid "Export command" msgstr "导出命令" -#: terminal/templates/terminal/command_list.html:191 +#: terminal/templates/terminal/command_list.html:205 msgid "Goto" msgstr "转到" @@ -4418,19 +4430,19 @@ msgstr "会话详情" msgid "Command list" msgstr "命令记录列表" -#: terminal/templates/terminal/session_detail.html:63 +#: terminal/templates/terminal/session_detail.html:67 msgid "There is no command about this session" msgstr "该会话没有命令记录" -#: terminal/templates/terminal/session_detail.html:88 +#: terminal/templates/terminal/session_detail.html:92 msgid "Replay session" msgstr "回放会话" -#: terminal/templates/terminal/session_detail.html:97 +#: terminal/templates/terminal/session_detail.html:101 msgid "Monitor session" msgstr "监控" -#: terminal/templates/terminal/session_detail.html:105 +#: terminal/templates/terminal/session_detail.html:109 msgid "Terminate session" msgstr "终止会话" @@ -4450,15 +4462,15 @@ msgstr "终断所选" msgid "Confirm finished" msgstr "确认已完成" -#: terminal/templates/terminal/session_list.html:91 +#: terminal/templates/terminal/session_list.html:92 msgid "Terminate task send, waiting ..." msgstr "终断任务已发送,请等待" -#: terminal/templates/terminal/session_list.html:142 +#: terminal/templates/terminal/session_list.html:143 msgid "Terminate" msgstr "终断" -#: terminal/templates/terminal/session_list.html:173 +#: terminal/templates/terminal/session_list.html:174 msgid "Finish session success" msgstr "标记会话完成成功" @@ -4528,7 +4540,7 @@ msgstr "你可以使用ssh客户端工具连接终端" msgid "Could not reset self otp, use profile reset instead" msgstr "不能再该页面重置MFA, 请去个人信息页面重置" -#: users/forms.py:33 users/models/user.py:383 +#: users/forms.py:32 users/models/user.py:383 #: users/templates/users/_select_user_modal.html:15 #: users/templates/users/user_detail.html:87 #: users/templates/users/user_list.html:37 @@ -4536,44 +4548,44 @@ msgstr "不能再该页面重置MFA, 请去个人信息页面重置" msgid "Role" msgstr "角色" -#: users/forms.py:36 users/forms.py:233 +#: users/forms.py:35 users/forms.py:232 #: users/templates/users/user_update.html:30 msgid "ssh public key" msgstr "ssh公钥" -#: users/forms.py:37 users/forms.py:234 +#: users/forms.py:36 users/forms.py:233 msgid "ssh-rsa AAAA..." msgstr "" -#: users/forms.py:38 +#: users/forms.py:37 msgid "Paste user id_rsa.pub here." msgstr "复制用户公钥到这里" -#: users/forms.py:52 users/templates/users/user_detail.html:226 +#: users/forms.py:51 users/templates/users/user_detail.html:226 msgid "Join user groups" msgstr "添加到用户组" -#: users/forms.py:87 users/forms.py:248 +#: users/forms.py:86 users/forms.py:247 msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" -#: users/forms.py:91 users/forms.py:252 users/serializers/v1.py:116 +#: users/forms.py:90 users/forms.py:251 users/serializers/v1.py:116 msgid "Not a valid ssh public key" msgstr "ssh密钥不合法" -#: users/forms.py:104 users/views/login.py:114 users/views/user.py:287 +#: users/forms.py:103 users/views/login.py:114 users/views/user.py:287 msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" -#: users/forms.py:125 +#: users/forms.py:124 msgid "Reset link will be generated and sent to the user" msgstr "生成重置密码链接,通过邮件发送给用户" -#: users/forms.py:126 +#: users/forms.py:125 msgid "Set password" msgstr "设置密码" -#: users/forms.py:133 xpack/plugins/change_auth_plan/models.py:88 +#: users/forms.py:132 xpack/plugins/change_auth_plan/models.py:88 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:51 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:69 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:57 @@ -4581,7 +4593,7 @@ msgstr "设置密码" msgid "Password strategy" msgstr "密码策略" -#: users/forms.py:160 +#: users/forms.py:159 msgid "" "When enabled, you will enter the MFA binding process the next time you log " "in. you can also directly bind in \"personal information -> quick " @@ -4590,11 +4602,11 @@ msgstr "" "启用之后您将会在下次登录时进入MFA绑定流程;您也可以在(个人信息->快速修改->更" "改MFA设置)中直接绑定!" -#: users/forms.py:170 +#: users/forms.py:169 msgid "* Enable MFA authentication to make the account more secure." msgstr "* 启用MFA认证,使账号更加安全。" -#: users/forms.py:180 +#: users/forms.py:179 msgid "" "In order to protect you and your company, please keep your account, password " "and key sensitive information properly. (for example: setting complex " @@ -4603,41 +4615,41 @@ msgstr "" "为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:" "设置复杂密码,启用MFA认证)" -#: users/forms.py:187 users/templates/users/first_login.html:48 +#: users/forms.py:186 users/templates/users/first_login.html:48 #: users/templates/users/first_login.html:110 #: users/templates/users/first_login.html:139 msgid "Finish" msgstr "完成" -#: users/forms.py:193 +#: users/forms.py:192 msgid "Old password" msgstr "原来密码" -#: users/forms.py:198 +#: users/forms.py:197 msgid "New password" msgstr "新密码" -#: users/forms.py:203 +#: users/forms.py:202 msgid "Confirm password" msgstr "确认密码" -#: users/forms.py:213 +#: users/forms.py:212 msgid "Old password error" msgstr "原来密码错误" -#: users/forms.py:221 +#: users/forms.py:220 msgid "Password does not match" msgstr "密码不一致" -#: users/forms.py:231 +#: users/forms.py:230 msgid "Automatically configure and download the SSH key" msgstr "自动配置并下载SSH密钥" -#: users/forms.py:235 +#: users/forms.py:234 msgid "Paste your id_rsa.pub here." msgstr "复制你的公钥到这里" -#: users/forms.py:269 users/forms.py:274 users/forms.py:316 +#: users/forms.py:268 users/forms.py:273 users/forms.py:323 #: xpack/plugins/orgs/forms.py:18 msgid "Select users" msgstr "选择用户" @@ -4730,7 +4742,7 @@ msgstr "角色只能为 {}" msgid "Password does not match security rules" msgstr "密码不满足安全规则" -#: users/serializers/v1.py:147 +#: users/serializers/v1.py:157 msgid "Auditors cannot be join in the user group" msgstr "审计员不能被加入到用户组" @@ -5927,7 +5939,7 @@ msgstr "更新同步实例任务" #: xpack/plugins/gathered_user/views.py:21 #: xpack/plugins/gathered_user/views.py:34 #: xpack/plugins/gathered_user/views.py:49 -#: xpack/plugins/gathered_user/views.py:66 +#: xpack/plugins/gathered_user/views.py:69 msgid "Gathered user" msgstr "收集用户" @@ -5962,7 +5974,7 @@ msgstr "创建任务" msgid "Gathered user list" msgstr "收集用户列表" -#: xpack/plugins/gathered_user/views.py:67 +#: xpack/plugins/gathered_user/views.py:70 msgid "Update task" msgstr "更新任务" diff --git a/apps/ops/templates/ops/celery_task_log.html b/apps/ops/templates/ops/celery_task_log.html index 7e2527e75..8e99c356d 100644 --- a/apps/ops/templates/ops/celery_task_log.html +++ b/apps/ops/templates/ops/celery_task_log.html @@ -22,7 +22,8 @@ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/apps/ops/urls/ws_urls.py b/apps/ops/urls/ws_urls.py index d3148982e..5b6bd74d2 100644 --- a/apps/ops/urls/ws_urls.py +++ b/apps/ops/urls/ws_urls.py @@ -5,5 +5,5 @@ from .. import ws app_name = 'ops' urlpatterns = [ - path('ws/ops/tasks//log/', ws.CeleryLogWebsocket, name='task-log-ws'), + path('ws/ops/tasks/log/', ws.CeleryLogWebsocket, name='task-log-ws'), ] diff --git a/apps/ops/views/command.py b/apps/ops/views/command.py index 0b0ec6542..ba00bc617 100644 --- a/apps/ops/views/command.py +++ b/apps/ops/views/command.py @@ -79,7 +79,8 @@ class CommandExecutionStartView(PermissionsMixin, TemplateView): 'app': _('Ops'), 'action': _('Command execution'), 'form': self.get_form(), - 'system_users': system_users + 'system_users': system_users, + 'ws_port': settings.CONFIG.WS_LISTEN_PORT } kwargs.update(context) return super().get_context_data(**kwargs) diff --git a/apps/ops/ws.py b/apps/ops/ws.py index fbc46c1db..1d2bea3c5 100644 --- a/apps/ops/ws.py +++ b/apps/ops/ws.py @@ -1,41 +1,58 @@ import time +import os import threading +import json + +from celery.result import AsyncResult from .celery.utils import get_celery_task_log_path from channels.generic.websocket import JsonWebsocketConsumer class CeleryLogWebsocket(JsonWebsocketConsumer): - task = '' - task_log_f = None disconnected = False def connect(self): - task_id = self.scope['url_route']['kwargs']['task_id'] - log_path = get_celery_task_log_path(task_id) - try: - self.task_log_f = open(log_path) - except OSError: - self.send({'message': "Task {} log not found".format(task_id)}) - self.disconnect(None) - return - self.accept() - self.send_log_to_client() + + def receive(self, text_data=None, bytes_data=None, **kwargs): + data = json.loads(text_data) + task_id = data.get("task") + if task_id: + self.handle_task(task_id) + + def handle_task(self, task_id): + log_path = get_celery_task_log_path(task_id) + + def func(): + task_log_f = None + + while not self.disconnected: + if not os.path.exists(log_path): + self.send_json({'message': '.', 'task': task_id}) + time.sleep(0.5) + continue + self.send_json({'message': '\r\n'}) + try: + task_log_f = open(log_path) + break + except OSError: + return + + while not self.disconnected: + data = task_log_f.readline() + if data: + data = data.replace('\n', '\r\n') + self.send_json({'message': data, 'task': task_id}) + if data.startswith('Task') and data.find('succeeded'): + break + time.sleep(0.2) + task_log_f.close() + thread = threading.Thread(target=func) + thread.start() def disconnect(self, close_code): self.disconnected = True - if self.task_log_f and not self.task_log_f.closed: - self.task_log_f.close() self.close() - def send_log_to_client(self): - def func(): - while not self.disconnected: - data = self.task_log_f.read(4096) - if data: - data = data.replace('\n', '\r\n') - self.send_json({'message': data}) - time.sleep(0.2) - thread = threading.Thread(target=func) - thread.start() + diff --git a/apps/orgs/mixins/api.py b/apps/orgs/mixins/api.py index 9de7f2c7d..8fb7f30dd 100644 --- a/apps/orgs/mixins/api.py +++ b/apps/orgs/mixins/api.py @@ -5,12 +5,12 @@ from rest_framework.viewsets import ModelViewSet from rest_framework_bulk import BulkModelViewSet from common.mixins import CommonApiMixin -from ..utils import set_to_root_org +from ..utils import set_to_root_org, filter_org_queryset from ..models import Organization __all__ = [ 'RootOrgViewMixin', 'OrgMembershipModelViewSetMixin', 'OrgModelViewSet', - 'OrgBulkModelViewSet', + 'OrgBulkModelViewSet', 'OrgQuerySetMixin', ] @@ -22,7 +22,15 @@ class RootOrgViewMixin: class OrgQuerySetMixin: def get_queryset(self): - queryset = super().get_queryset().all() + if hasattr(self, 'model'): + queryset = self.model.objects.all() + else: + assert self.queryset is None, ( + "'%s' should not include a `queryset` attribute" + % self.__class__.__name__ + ) + queryset = super().get_queryset() + if hasattr(self, 'swagger_fake_view'): return queryset[:1] if hasattr(self, 'action') and self.action == 'list' and \ diff --git a/apps/orgs/mixins/generics.py b/apps/orgs/mixins/generics.py new file mode 100644 index 000000000..490ee1b87 --- /dev/null +++ b/apps/orgs/mixins/generics.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# +from rest_framework import generics + +from .api import OrgQuerySetMixin + + +class ListAPIView(OrgQuerySetMixin, generics.ListAPIView): + pass + + +class RetrieveAPIView(OrgQuerySetMixin, generics.RetrieveAPIView): + pass + + +class CreateAPIView(OrgQuerySetMixin, generics.CreateAPIView): + pass + + +class DestroyAPIView(OrgQuerySetMixin, generics.DestroyAPIView): + pass + + +class ListCreateAPIView(OrgQuerySetMixin, generics.ListCreateAPIView): + pass + + +class UpdateAPIView(OrgQuerySetMixin, generics.UpdateAPIView): + pass + + +class RetrieveUpdateAPIView(OrgQuerySetMixin, generics.RetrieveUpdateAPIView): + pass + + +class RetrieveDestroyAPIView(OrgQuerySetMixin, generics.RetrieveDestroyAPIView): + pass + + +class RetrieveUpdateDestroyAPIView(OrgQuerySetMixin, generics.RetrieveUpdateDestroyAPIView): + pass diff --git a/apps/orgs/mixins/models.py b/apps/orgs/mixins/models.py index 672b975dc..4ffa24c2a 100644 --- a/apps/orgs/mixins/models.py +++ b/apps/orgs/mixins/models.py @@ -9,6 +9,7 @@ from django.core.exceptions import ValidationError from common.utils import get_logger from ..utils import ( set_current_org, get_current_org, current_org, + get_org_filters ) from ..models import Organization @@ -19,42 +20,38 @@ __all__ = [ ] +class OrgQuerySet(models.QuerySet): + pass + + class OrgManager(models.Manager): - def get_queryset(self): - queryset = super(OrgManager, self).get_queryset() - kwargs = {} - - _current_org = get_current_org() - if _current_org is None: - kwargs['id'] = None - elif _current_org.is_real(): - kwargs['org_id'] = _current_org.id - elif _current_org.is_default(): - queryset = queryset.filter(org_id="") - # - # lines = traceback.format_stack() - # print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>") - # for line in lines[-10:-1]: - # print(line) - # print("<<<<<<<<<<<<<<<<<<<<<<<<<<<<") - - queryset = queryset.filter(**kwargs) + queryset = super().get_queryset() + kwargs = get_org_filters() + if kwargs: + return queryset.filter(**kwargs) return queryset - def all(self): - if not current_org: - msg = 'You can `objects.set_current_org(org).all()` then run it' - return self - else: - return super(OrgManager, self).all() - def set_current_org(self, org): if isinstance(org, str): org = Organization.get_instance(org) set_current_org(org) return self + def all(self): + # print("Call all: {}".format(current_org)) + # + # lines = traceback.format_stack() + # print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>") + # for line in lines[-10:-1]: + # print(line) + # print("<<<<<<<<<<<<<<<<<<<<<<<<<<<<") + if not current_org: + msg = 'You can `objects.set_current_org(org).all()` then run it' + return self + else: + return super().all() + class OrgModelMixin(models.Model): org_id = models.CharField(max_length=36, blank=True, default='', @@ -65,9 +62,12 @@ class OrgModelMixin(models.Model): def save(self, *args, **kwargs): org = get_current_org() - if org is not None and (org.is_real() or org.is_system()): + if org is None: + return super().save(*args, **kwargs) + + if org.is_real() or org.is_system(): self.org_id = org.id - elif org is not None and org.is_default(): + elif org.is_default(): self.org_id = '' return super().save(*args, **kwargs) diff --git a/apps/orgs/serializers.py b/apps/orgs/serializers.py index 222598c88..8f4df1e10 100644 --- a/apps/orgs/serializers.py +++ b/apps/orgs/serializers.py @@ -1,9 +1,13 @@ +import re from rest_framework.serializers import ModelSerializer from rest_framework import serializers - from users.models import User, UserGroup from assets.models import Asset, Domain, AdminUser, SystemUser, Label +from assets.const import ( + GENERAL_LIMIT_SPECIAL_CHARACTERS_PATTERN, + GENERAL_LIMIT_SPECIAL_CHARACTERS_ERROR_MSG +) from perms.models import AssetPermission from common.serializers import AdaptedBulkListSerializer from .utils import set_current_org, get_current_org @@ -18,6 +22,15 @@ class OrgSerializer(ModelSerializer): fields = '__all__' read_only_fields = ['created_by', 'date_created'] + @staticmethod + def validate_name(name): + pattern = GENERAL_LIMIT_SPECIAL_CHARACTERS_PATTERN + res = re.match(pattern, name) + if res is None: + msg = GENERAL_LIMIT_SPECIAL_CHARACTERS_ERROR_MSG + raise serializers.ValidationError(msg) + return name + class OrgReadSerializer(ModelSerializer): admins = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True) diff --git a/apps/orgs/utils.py b/apps/orgs/utils.py index 8fca26e35..2a7cfca6b 100644 --- a/apps/orgs/utils.py +++ b/apps/orgs/utils.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # +import traceback from werkzeug.local import LocalProxy from contextlib import contextmanager @@ -82,4 +83,31 @@ def tmp_to_org(org): set_current_org(ori_org) +def get_org_filters(): + kwargs = {} + + _current_org = get_current_org() + if _current_org is None: + return kwargs + + if _current_org.is_real(): + kwargs['org_id'] = _current_org.id + elif _current_org.is_default(): + kwargs["org_id"] = '' + return kwargs + + +def filter_org_queryset(queryset): + kwargs = get_org_filters() + + # + # lines = traceback.format_stack() + # print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>") + # for line in lines[-10:-1]: + # print(line) + # print("<<<<<<<<<<<<<<<<<<<<<<<<<<<<") + queryset = queryset.filter(**kwargs) + return queryset + + current_org = LocalProxy(get_current_org) diff --git a/apps/perms/api/asset_permission.py b/apps/perms/api/asset_permission.py index 41c64c7f1..0243f8f1b 100644 --- a/apps/perms/api/asset_permission.py +++ b/apps/perms/api/asset_permission.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- # -from django.utils import timezone from django.db.models import Q from rest_framework.views import Response from django.shortcuts import get_object_or_404 -from rest_framework.generics import RetrieveUpdateAPIView, ListAPIView -from rest_framework import viewsets from common.permissions import IsOrgAdmin +from orgs.mixins.api import OrgModelViewSet +from orgs.mixins import generics from common.utils import get_object_or_none from ..models import AssetPermission from ..hands import ( @@ -24,15 +23,21 @@ __all__ = [ ] -class AssetPermissionViewSet(viewsets.ModelViewSet): +class AssetPermissionViewSet(OrgModelViewSet): """ 资产授权列表的增删改查api """ - queryset = AssetPermission.objects.all() + model = AssetPermission serializer_class = serializers.AssetPermissionCreateUpdateSerializer filter_fields = ['name'] permission_classes = (IsOrgAdmin,) + def get_queryset(self): + queryset = super().get_queryset().prefetch_related( + "nodes", "assets", "users", "user_groups", "system_users" + ) + return queryset + def get_serializer_class(self): if self.action in ("list", 'retrieve') and \ self.request.query_params.get("display"): @@ -68,14 +73,17 @@ class AssetPermissionViewSet(viewsets.ModelViewSet): node_id = self.request.query_params.get('node_id') node_name = self.request.query_params.get('node') if node_id: - node = get_object_or_none(Node, pk=node_id) + _nodes = Node.objects.filter(pk=node_id) elif node_name: - node = get_object_or_none(Node, name=node_name) + _nodes = Node.objects.filter(value=node_name) else: return queryset - if not node: + if not _nodes: return queryset.none() - nodes = node.get_ancestors(with_self=True) + + nodes = set() + for node in _nodes: + nodes |= set(node.get_ancestors(with_self=True)) queryset = queryset.filter(nodes__in=nodes) return queryset @@ -160,19 +168,14 @@ class AssetPermissionViewSet(viewsets.ModelViewSet): queryset = queryset.distinct() return queryset - def get_queryset(self): - return self.queryset.all().prefetch_related( - "nodes", "assets", "users", "user_groups", "system_users" - ) - -class AssetPermissionRemoveUserApi(RetrieveUpdateAPIView): +class AssetPermissionRemoveUserApi(generics.RetrieveUpdateAPIView): """ 将用户从授权中移除,Detail页面会调用 """ + model = AssetPermission permission_classes = (IsOrgAdmin,) serializer_class = serializers.AssetPermissionUpdateUserSerializer - queryset = AssetPermission.objects.all() def update(self, request, *args, **kwargs): perm = self.get_object() @@ -187,10 +190,10 @@ class AssetPermissionRemoveUserApi(RetrieveUpdateAPIView): return Response({"error": serializer.errors}) -class AssetPermissionAddUserApi(RetrieveUpdateAPIView): +class AssetPermissionAddUserApi(generics.RetrieveUpdateAPIView): + model = AssetPermission permission_classes = (IsOrgAdmin,) serializer_class = serializers.AssetPermissionUpdateUserSerializer - queryset = AssetPermission.objects.all() def update(self, request, *args, **kwargs): perm = self.get_object() @@ -205,13 +208,13 @@ class AssetPermissionAddUserApi(RetrieveUpdateAPIView): return Response({"error": serializer.errors}) -class AssetPermissionRemoveAssetApi(RetrieveUpdateAPIView): +class AssetPermissionRemoveAssetApi(generics.RetrieveUpdateAPIView): """ 将用户从授权中移除,Detail页面会调用 """ + model = AssetPermission permission_classes = (IsOrgAdmin,) serializer_class = serializers.AssetPermissionUpdateAssetSerializer - queryset = AssetPermission.objects.all() def update(self, request, *args, **kwargs): perm = self.get_object() @@ -226,10 +229,10 @@ class AssetPermissionRemoveAssetApi(RetrieveUpdateAPIView): return Response({"error": serializer.errors}) -class AssetPermissionAddAssetApi(RetrieveUpdateAPIView): +class AssetPermissionAddAssetApi(generics.RetrieveUpdateAPIView): + model = AssetPermission permission_classes = (IsOrgAdmin,) serializer_class = serializers.AssetPermissionUpdateAssetSerializer - queryset = AssetPermission.objects.all() def update(self, request, *args, **kwargs): perm = self.get_object() @@ -244,7 +247,7 @@ class AssetPermissionAddAssetApi(RetrieveUpdateAPIView): return Response({"error": serializer.errors}) -class AssetPermissionAssetsApi(ListAPIView): +class AssetPermissionAssetsApi(generics.ListAPIView): permission_classes = (IsOrgAdmin,) serializer_class = serializers.AssetPermissionAssetsSerializer filter_fields = ("hostname", "ip") diff --git a/apps/perms/api/mixin.py b/apps/perms/api/mixin.py index 85db529a0..86e1a8c0a 100644 --- a/apps/perms/api/mixin.py +++ b/apps/perms/api/mixin.py @@ -4,7 +4,7 @@ from rest_framework.generics import get_object_or_404 from common.permissions import IsValidUser, IsOrgAdminOrAppUser from common.utils import get_logger -from orgs.utils import set_to_root_org +from orgs.utils import set_to_root_org, get_current_org, set_current_org, tmp_to_root_org from ..hands import User, UserGroup @@ -17,15 +17,24 @@ __all__ = [ class UserPermissionMixin: permission_classes = (IsOrgAdminOrAppUser,) + current_org = None obj = None def initial(self, *args, **kwargs): super().initial(*args, *kwargs) + self.current_org = get_current_org() + set_to_root_org() self.obj = self.get_obj() - def get(self, request, *args, **kwargs): - set_to_root_org() - return super().get(request, *args, **kwargs) + # def dispatch(self, request, *args, **kwargs): + # """不能这么做,校验权限时拿不到组织了""" + # with tmp_to_root_org(): + # return super().dispatch(request, *args, **kwargs) + + # def get(self, request, *args, **kwargs): + # """有的api重写了get方法""" + # with tmp_to_root_org(): + # return super().get(request, *args, **kwargs) def get_obj(self): user_id = self.kwargs.get('pk', '') @@ -40,6 +49,13 @@ class UserPermissionMixin: self.permission_classes = (IsValidUser,) return super().get_permissions() + def finalize_response(self, request, response, *args, **kwargs): + response = super().finalize_response(request, response, *args, **kwargs) + org = getattr(self, 'current_org', None) + if org: + set_current_org(org) + return response + class UserGroupPermissionMixin: obj = None diff --git a/apps/perms/api/remote_app_permission.py b/apps/perms/api/remote_app_permission.py index 12b1ebfb6..6ced7f0ae 100644 --- a/apps/perms/api/remote_app_permission.py +++ b/apps/perms/api/remote_app_permission.py @@ -1,10 +1,11 @@ # coding: utf-8 # -from rest_framework import viewsets, generics from rest_framework.views import Response from common.permissions import IsOrgAdmin +from orgs.mixins.api import OrgModelViewSet +from orgs.mixins import generics from ..models import RemoteAppPermission from ..serializers import ( RemoteAppPermissionSerializer, @@ -20,18 +21,18 @@ __all__ = [ ] -class RemoteAppPermissionViewSet(viewsets.ModelViewSet): +class RemoteAppPermissionViewSet(OrgModelViewSet): + model = RemoteAppPermission filter_fields = ('name', ) search_fields = filter_fields - queryset = RemoteAppPermission.objects.all() serializer_class = RemoteAppPermissionSerializer permission_classes = (IsOrgAdmin,) class RemoteAppPermissionAddUserApi(generics.RetrieveUpdateAPIView): + model = RemoteAppPermission permission_classes = (IsOrgAdmin,) serializer_class = RemoteAppPermissionUpdateUserSerializer - queryset = RemoteAppPermission.objects.all() def update(self, request, *args, **kwargs): perm = self.get_object() @@ -46,9 +47,9 @@ class RemoteAppPermissionAddUserApi(generics.RetrieveUpdateAPIView): class RemoteAppPermissionRemoveUserApi(generics.RetrieveUpdateAPIView): + model = RemoteAppPermission permission_classes = (IsOrgAdmin,) serializer_class = RemoteAppPermissionUpdateUserSerializer - queryset = RemoteAppPermission.objects.all() def update(self, request, *args, **kwargs): perm = self.get_object() @@ -63,9 +64,9 @@ class RemoteAppPermissionRemoveUserApi(generics.RetrieveUpdateAPIView): class RemoteAppPermissionAddRemoteAppApi(generics.RetrieveUpdateAPIView): + model = RemoteAppPermission permission_classes = (IsOrgAdmin,) serializer_class = RemoteAppPermissionUpdateRemoteAppSerializer - queryset = RemoteAppPermission.objects.all() def update(self, request, *args, **kwargs): perm = self.get_object() @@ -80,9 +81,9 @@ class RemoteAppPermissionAddRemoteAppApi(generics.RetrieveUpdateAPIView): class RemoteAppPermissionRemoveRemoteAppApi(generics.RetrieveUpdateAPIView): + model = RemoteAppPermission permission_classes = (IsOrgAdmin,) serializer_class = RemoteAppPermissionUpdateRemoteAppSerializer - queryset = RemoteAppPermission.objects.all() def update(self, request, *args, **kwargs): perm = self.get_object() diff --git a/apps/perms/api/user_permission/mixin.py b/apps/perms/api/user_permission/mixin.py index 069f08937..4a004e0d1 100644 --- a/apps/perms/api/user_permission/mixin.py +++ b/apps/perms/api/user_permission/mixin.py @@ -34,7 +34,7 @@ class UserNodeTreeMixin: for node in nodes: assets_amount = self.tree.valid_assets_amount(node.key) - if assets_amount == 0 and node.key != Node.empty_key: + if assets_amount == 0 and not node.key.startswith('-'): continue node.assets_amount = assets_amount data = ParserNode.parse_node_to_tree_node(node) diff --git a/apps/perms/api/user_remote_app_permission.py b/apps/perms/api/user_remote_app_permission.py index 030e760b5..868c92015 100644 --- a/apps/perms/api/user_remote_app_permission.py +++ b/apps/perms/api/user_remote_app_permission.py @@ -3,12 +3,10 @@ import uuid 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, -) from common.permissions import IsValidUser, IsOrgAdminOrAppUser from common.tree import TreeNodeSerializer +from orgs.mixins import generics from ..utils import ( RemoteAppPermissionUtil, construct_remote_apps_tree_root, parse_remote_app_to_tree_node, @@ -25,7 +23,7 @@ __all__ = [ ] -class UserGrantedRemoteAppsApi(ListAPIView): +class UserGrantedRemoteAppsApi(generics.ListAPIView): permission_classes = (IsOrgAdminOrAppUser,) serializer_class = RemoteAppSerializer filter_fields = ['name', 'id'] @@ -68,7 +66,7 @@ class UserGrantedRemoteAppsAsTreeApi(UserGrantedRemoteAppsApi): return super().get_serializer(data, many=True) -class UserGrantedRemoteAppSystemUsersApi(UserPermissionMixin, ListAPIView): +class UserGrantedRemoteAppSystemUsersApi(UserPermissionMixin, generics.ListAPIView): permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.RemoteAppSystemUserSerializer only_fields = serializers.RemoteAppSystemUserSerializer.Meta.only_fields @@ -110,7 +108,7 @@ class ValidateUserRemoteAppPermissionApi(APIView): # RemoteApp permission -class UserGroupGrantedRemoteAppsApi(ListAPIView): +class UserGroupGrantedRemoteAppsApi(generics.ListAPIView): permission_classes = (IsOrgAdminOrAppUser, ) serializer_class = RemoteAppSerializer diff --git a/apps/perms/hands.py b/apps/perms/hands.py index aef0f4875..ab9e3f494 100644 --- a/apps/perms/hands.py +++ b/apps/perms/hands.py @@ -2,10 +2,15 @@ # from users.models import User, UserGroup -from assets.models import Asset, SystemUser, Node, Label +from assets.models import Asset, SystemUser, Node, Label, FavoriteAsset from assets.serializers import NodeSerializer from applications.serializers import RemoteAppSerializer from applications.models import RemoteApp - +__all__ = [ + 'User', 'UserGroup', + 'Asset', 'SystemUser', 'Node', 'Label', 'FavoriteAsset', + 'NodeSerializer', 'RemoteAppSerializer', + 'RemoteApp' +] diff --git a/apps/perms/models/base.py b/apps/perms/models/base.py index b36d009b3..878d9d739 100644 --- a/apps/perms/models/base.py +++ b/apps/perms/models/base.py @@ -29,7 +29,7 @@ class BasePermissionQuerySet(models.QuerySet): return self.filter(is_active=False) def invalid(self): - now = timezone.now + now = timezone.now() q = ( Q(is_active=False) | Q(date_start__gt=now) | diff --git a/apps/perms/utils/asset_permission.py b/apps/perms/utils/asset_permission.py index bbc64bfa9..6754511d0 100644 --- a/apps/perms/utils/asset_permission.py +++ b/apps/perms/utils/asset_permission.py @@ -13,7 +13,7 @@ from common.utils import get_logger, timeit, lazyproperty from common.tree import TreeNode from assets.utils import TreeService from ..models import AssetPermission -from ..hands import Node, Asset, SystemUser +from ..hands import Node, Asset, SystemUser, User, FavoriteAsset logger = get_logger(__file__) @@ -293,6 +293,20 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin): parent=user_tree.root, ) + def add_favorite_node_if_need(self, user_tree): + if not isinstance(self.object, User): + return + node_key = Node.favorite_key + node_value = Node.favorite_value + user_tree.create_node( + identifier=node_key, tag=node_value, + parent=user_tree.root, + ) + assets_id = FavoriteAsset.get_user_favorite_assets_id(self.object) + all_valid_assets = user_tree.all_valid_assets(user_tree.root) + valid_assets_id = set(assets_id) & all_valid_assets + user_tree.set_assets(node_key, valid_assets_id) + def set_user_tree_to_local(self, user_tree): self._user_tree = user_tree self._user_tree_filter_id = self._filter_id @@ -323,6 +337,7 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin): self.add_single_assets_node_to_user_tree(user_tree) self.parse_user_tree_to_full_tree(user_tree) self.add_empty_node_if_need(user_tree) + self.add_favorite_node_if_need(user_tree) self.set_user_tree_to_cache_if_need(user_tree) self.set_user_tree_to_local(user_tree) return user_tree diff --git a/apps/settings/api.py b/apps/settings/api.py index 9df77d036..d327bde17 100644 --- a/apps/settings/api.py +++ b/apps/settings/api.py @@ -101,9 +101,11 @@ class LDAPUserListApi(generics.ListAPIView): def get_queryset(self): if hasattr(self, 'swagger_fake_view'): return [] - util = LDAPUtil() + q = self.request.query_params.get('search') try: - users = util.search_user_items() + util = LDAPUtil() + extra_filter = util.construct_extra_filter(util.SEARCH_FIELD_ALL, q) + users = util.search_user_items(extra_filter) except Exception as e: users = [] logger.error(e) @@ -112,20 +114,6 @@ class LDAPUserListApi(generics.ListAPIView): user['id'] = user['username'] return users - def filter_queryset(self, queryset): - search = self.request.query_params.get('search') - if not search: - return queryset - search = search.lower() - queryset = [ - q for q in queryset - if - search in q['username'].lower() - or search in q['name'].lower() - or search in q['email'].lower() - ] - return queryset - def sort_queryset(self, queryset): order_by = self.request.query_params.get('order') if not order_by: @@ -139,7 +127,7 @@ class LDAPUserListApi(generics.ListAPIView): return queryset def list(self, request, *args, **kwargs): - queryset = self.filter_queryset(self.get_queryset()) + queryset = self.get_queryset() queryset = self.sort_queryset(queryset) page = self.paginate_queryset(queryset) if page is not None: diff --git a/apps/settings/utils.py b/apps/settings/utils.py index 657aa4600..9ecd5d286 100644 --- a/apps/settings/utils.py +++ b/apps/settings/utils.py @@ -22,6 +22,9 @@ class LDAPOUGroupException(Exception): class LDAPUtil: _conn = None + SEARCH_FIELD_ALL = 'all' + SEARCH_FIELD_USERNAME = 'username' + def __init__(self, use_settings_config=True, server_uri=None, bind_dn=None, password=None, use_ssl=None, search_ougroup=None, search_filter=None, attr_map=None, auth_ldap=None): @@ -81,9 +84,13 @@ class LDAPUtil: user_item[attr] = value return user_item - def _search_user_items_ou(self, search_ou, cookie=None): + def _search_user_items_ou(self, search_ou, extra_filter=None, cookie=None): + search_filter = self.search_filter % {"user": "*"} + if extra_filter: + search_filter = '(&{}{})'.format(search_filter, extra_filter) + ok = self.connection.search( - search_ou, self.search_filter % ({"user": "*"}), + search_ou, search_filter, attributes=list(self.attr_map.values()), paged_size=self.paged_size, paged_cookie=cookie ) @@ -108,24 +115,43 @@ class LDAPUtil: cookie = self.connection.result['controls']['1.2.840.113556.1.4.319']['value']['cookie'] return cookie - def search_user_items(self): + def search_user_items(self, extra_filter=None): user_items = [] logger.info("Search user items") + for search_ou in str(self.search_ougroup).split("|"): logger.info("Search user search ou: {}".format(search_ou)) - _user_items = self._search_user_items_ou(search_ou) + _user_items = self._search_user_items_ou(search_ou, extra_filter=extra_filter) user_items.extend(_user_items) while self._cookie(): logger.info("Page Search user search ou: {}".format(search_ou)) - _user_items = self._search_user_items_ou(search_ou, self._cookie()) + _user_items = self._search_user_items_ou(search_ou, extra_filter, self._cookie()) user_items.extend(_user_items) logger.info("Search user items end") return user_items + def construct_extra_filter(self, field, q): + if not q: + return None + extra_filter = '' + if field == self.SEARCH_FIELD_ALL: + for attr in self.attr_map.values(): + extra_filter += '({}={})'.format(attr, q) + extra_filter = '(|{})'.format(extra_filter) + return extra_filter + + if field == self.SEARCH_FIELD_USERNAME and isinstance(q, list): + attr = self.attr_map.get('username') + for username in q: + extra_filter += '({}={})'.format(attr, username) + extra_filter = '(|{})'.format(extra_filter) + return extra_filter + def search_filter_user_items(self, username_list): - user_items = self.search_user_items() - if username_list: - user_items = [u for u in user_items if u['username'] in username_list] + extra_filter = self.construct_extra_filter( + self.SEARCH_FIELD_USERNAME, username_list + ) + user_items = self.search_user_items(extra_filter) return user_items @staticmethod diff --git a/apps/static/css/plugins/select2/select2.min.css b/apps/static/css/plugins/select2/select2.min.css index 76de04d92..7c18ad59d 100644 --- a/apps/static/css/plugins/select2/select2.min.css +++ b/apps/static/css/plugins/select2/select2.min.css @@ -1 +1 @@ -.select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-selection--single .select2-selection__clear{position:relative}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline-block;overflow:hidden;padding-left:8px;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-search--inline{float:left}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;padding:0}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-dropdown{background-color:white;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2-results{display:block}.select2-results__options{list-style:none;margin:0;padding:0}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2-results__option[aria-selected]{cursor:pointer}.select2-container--open .select2-dropdown{left:0}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-search--dropdown{display:block;padding:4px}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-search--dropdown.select2-search--hide{display:none}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.select2-hidden-accessible{border:0 !important;clip:rect(0 0 0 0) !important;height:1px !important;margin:-1px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow{left:1px;right:auto}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--default .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text}.select2-container--default .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%}.select2-container--default .select2-selection--multiple .select2-selection__rendered li{list-style:none}.select2-container--default .select2-selection--multiple .select2-selection__placeholder{color:#999;margin-top:5px;float:left}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-top:5px;margin-right:10px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline{float:right}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--default.select2-container--focus .select2-selection--multiple{border:solid black 1px;outline:0}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none}.select2-container--default.select2-container--open.select2-container--above .select2-selection--single,.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple{border-top-left-radius:0;border-top-right-radius:0}.select2-container--default.select2-container--open.select2-container--below .select2-selection--single,.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa}.select2-container--default .select2-search--inline .select2-search__field{background:transparent;border:none;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--default .select2-results__option[role=group]{padding:0}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#5897fb;color:white}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic .select2-selection--single{background-color:#f7f7f7;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top, #fff 50%, #eee 100%);background-image:-o-linear-gradient(top, #fff 50%, #eee 100%);background-image:linear-gradient(to bottom, #fff 50%, #eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-right:10px}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top, #eee 50%, #ccc 100%);background-image:-o-linear-gradient(top, #eee 50%, #ccc 100%);background-image:linear-gradient(to bottom, #eee 50%, #ccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0)}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:transparent;border:none}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top, #fff 0%, #eee 50%);background-image:-o-linear-gradient(top, #fff 0%, #eee 50%);background-image:linear-gradient(to bottom, #fff 0%, #eee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top, #eee 50%, #fff 100%);background-image:-o-linear-gradient(top, #eee 50%, #fff 100%);background-image:linear-gradient(to bottom, #eee 50%, #fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0)}.select2-container--classic .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--multiple .select2-selection__rendered{list-style:none;margin:0;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{color:#888;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{float:right}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;box-shadow:none}.select2-container--classic .select2-dropdown{background-color:#fff;border:1px solid transparent}.select2-container--classic .select2-dropdown--above{border-bottom:none}.select2-container--classic .select2-dropdown--below{border-top:none}.select2-container--classic .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--classic .select2-results__option[role=group]{padding:0}.select2-container--classic .select2-results__option[aria-disabled=true]{color:grey}.select2-container--classic .select2-results__option--highlighted[aria-selected]{background-color:#3875d7;color:#fff}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb} +.select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-selection--single .select2-selection__clear{position:relative}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline-block;overflow:hidden;padding-left:8px;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-search--inline{float:left}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;padding:0}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-dropdown{background-color:white;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2-results{display:block}.select2-results__options{list-style:none;margin:0;padding:0}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2-results__option[aria-selected]{cursor:pointer}.select2-container--open .select2-dropdown{left:0}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-search--dropdown{display:block;padding:4px}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-search--dropdown.select2-search--hide{display:none}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.select2-hidden-accessible{border:0 !important;clip:rect(0 0 0 0) !important;-webkit-clip-path:inset(50%) !important;clip-path:inset(50%) !important;height:1px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important;white-space:nowrap !important}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow{left:1px;right:auto}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--default .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text}.select2-container--default .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%}.select2-container--default .select2-selection--multiple .select2-selection__rendered li{list-style:none}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-top:5px;margin-right:10px;padding:1px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline{float:right}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--default.select2-container--focus .select2-selection--multiple{border:solid black 1px;outline:0}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none}.select2-container--default.select2-container--open.select2-container--above .select2-selection--single,.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple{border-top-left-radius:0;border-top-right-radius:0}.select2-container--default.select2-container--open.select2-container--below .select2-selection--single,.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa}.select2-container--default .select2-search--inline .select2-search__field{background:transparent;border:none;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--default .select2-results__option[role=group]{padding:0}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#5897fb;color:white}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic .select2-selection--single{background-color:#f7f7f7;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top, #fff 50%, #eee 100%);background-image:-o-linear-gradient(top, #fff 50%, #eee 100%);background-image:linear-gradient(to bottom, #fff 50%, #eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-right:10px}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top, #eee 50%, #ccc 100%);background-image:-o-linear-gradient(top, #eee 50%, #ccc 100%);background-image:linear-gradient(to bottom, #eee 50%, #ccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0)}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:transparent;border:none}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top, #fff 0%, #eee 50%);background-image:-o-linear-gradient(top, #fff 0%, #eee 50%);background-image:linear-gradient(to bottom, #fff 0%, #eee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top, #eee 50%, #fff 100%);background-image:-o-linear-gradient(top, #eee 50%, #fff 100%);background-image:linear-gradient(to bottom, #eee 50%, #fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0)}.select2-container--classic .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--multiple .select2-selection__rendered{list-style:none;margin:0;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{color:#888;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{float:right;margin-left:5px;margin-right:auto}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;box-shadow:none}.select2-container--classic .select2-dropdown{background-color:#fff;border:1px solid transparent}.select2-container--classic .select2-dropdown--above{border-bottom:none}.select2-container--classic .select2-dropdown--below{border-top:none}.select2-container--classic .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--classic .select2-results__option[role=group]{padding:0}.select2-container--classic .select2-results__option[aria-disabled=true]{color:grey}.select2-container--classic .select2-results__option--highlighted[aria-selected]{background-color:#3875d7;color:#fff}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb} diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index d028743be..4f55cfb4f 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -267,7 +267,7 @@ function requestApi(props) { $.ajax({ url: props.url, type: props.method || "PATCH", - data: props.body, + data: props.body || props.data, contentType: props.content_type || "application/json; charset=utf-8", dataType: props.data_type || "json" }).done(function (data, textStatue, jqXHR) { @@ -579,6 +579,9 @@ jumpserver.initServerSideDataTable = function (options) { ajax: { url: options.ajax_url, error: function (jqXHR, textStatus, errorThrown) { + if (jqXHR.responseText && jqXHR.responseText.indexOf("%(value)s") !== -1 ) { + return + } var msg = gettext("Unknown error occur"); if (jqXHR.responseJSON) { if (jqXHR.responseJSON.error) { @@ -953,8 +956,13 @@ function initPopover($container, $progress, $idPassword, $el, password_check_rul function rootNodeAddDom(ztree, callback) { var refreshIcon = ""; var rootNode = ztree.getNodes()[0]; - var $rootNodeRef = $("#" + rootNode.tId + "_a"); - $rootNodeRef.after(refreshIcon); + if (rootNode) { + var $rootNodeRef = $("#" + rootNode.tId + "_a"); + $rootNodeRef.after(refreshIcon); + } else { + $rootNodeRef = $('#' + ztree.setting.treeId); + $rootNodeRef.html(refreshIcon); + } var refreshIconRef = $('#tree-refresh'); refreshIconRef.bind('click', function () { ztree.destroy(); diff --git a/apps/static/js/plugins/select2/select2.full.min.js b/apps/static/js/plugins/select2/select2.full.min.js index 684edf323..313313449 100644 --- a/apps/static/js/plugins/select2/select2.full.min.js +++ b/apps/static/js/plugins/select2/select2.full.min.js @@ -1,3 +1,2 @@ -/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):jQuery)}(function(a){var b=function(){if(a&&a.fn&&a.fn.select2&&a.fn.select2.amd)var b=a.fn.select2.amd;var b;return function(){if(!b||!b.requirejs){b?c=b:b={};var a,c,d;!function(b){function e(a,b){return u.call(a,b)}function f(a,b){var c,d,e,f,g,h,i,j,k,l,m,n=b&&b.split("/"),o=s.map,p=o&&o["*"]||{};if(a&&"."===a.charAt(0))if(b){for(a=a.split("/"),g=a.length-1,s.nodeIdCompat&&w.test(a[g])&&(a[g]=a[g].replace(w,"")),a=n.slice(0,n.length-1).concat(a),k=0;k0&&(a.splice(k-1,2),k-=2)}a=a.join("/")}else 0===a.indexOf("./")&&(a=a.substring(2));if((n||p)&&o){for(c=a.split("/"),k=c.length;k>0;k-=1){if(d=c.slice(0,k).join("/"),n)for(l=n.length;l>0;l-=1)if(e=o[n.slice(0,l).join("/")],e&&(e=e[d])){f=e,h=k;break}if(f)break;!i&&p&&p[d]&&(i=p[d],j=k)}!f&&i&&(f=i,h=j),f&&(c.splice(0,h,f),a=c.join("/"))}return a}function g(a,c){return function(){var d=v.call(arguments,0);return"string"!=typeof d[0]&&1===d.length&&d.push(null),n.apply(b,d.concat([a,c]))}}function h(a){return function(b){return f(b,a)}}function i(a){return function(b){q[a]=b}}function j(a){if(e(r,a)){var c=r[a];delete r[a],t[a]=!0,m.apply(b,c)}if(!e(q,a)&&!e(t,a))throw new Error("No "+a);return q[a]}function k(a){var b,c=a?a.indexOf("!"):-1;return c>-1&&(b=a.substring(0,c),a=a.substring(c+1,a.length)),[b,a]}function l(a){return function(){return s&&s.config&&s.config[a]||{}}}var m,n,o,p,q={},r={},s={},t={},u=Object.prototype.hasOwnProperty,v=[].slice,w=/\.js$/;o=function(a,b){var c,d=k(a),e=d[0];return a=d[1],e&&(e=f(e,b),c=j(e)),e?a=c&&c.normalize?c.normalize(a,h(b)):f(a,b):(a=f(a,b),d=k(a),e=d[0],a=d[1],e&&(c=j(e))),{f:e?e+"!"+a:a,n:a,pr:e,p:c}},p={require:function(a){return g(a)},exports:function(a){var b=q[a];return"undefined"!=typeof b?b:q[a]={}},module:function(a){return{id:a,uri:"",exports:q[a],config:l(a)}}},m=function(a,c,d,f){var h,k,l,m,n,s,u=[],v=typeof d;if(f=f||a,"undefined"===v||"function"===v){for(c=!c.length&&d.length?["require","exports","module"]:c,n=0;n0&&(b.call(arguments,a.prototype.constructor),e=c.prototype.constructor),e.apply(this,arguments)}function e(){this.constructor=d}var f=b(c),g=b(a);c.displayName=a.displayName,d.prototype=new e;for(var h=0;hc;c++)a[c].apply(this,b)},c.Observable=d,c.generateChars=function(a){for(var b="",c=0;a>c;c++){var d=Math.floor(36*Math.random());b+=d.toString(36)}return b},c.bind=function(a,b){return function(){a.apply(b,arguments)}},c._convertData=function(a){for(var b in a){var c=b.split("-"),d=a;if(1!==c.length){for(var e=0;e":">",'"':""","'":"'","/":"/"};return"string"!=typeof a?a:String(a).replace(/[&<>"'\/\\]/g,function(a){return b[a]})},c.appendMany=function(b,c){if("1.7"===a.fn.jquery.substr(0,3)){var d=a();a.map(c,function(a){d=d.add(a)}),c=d}b.append(c)},c}),b.define("select2/results",["jquery","./utils"],function(a,b){function c(a,b,d){this.$element=a,this.data=d,this.options=b,c.__super__.constructor.call(this)}return b.Extend(c,b.Observable),c.prototype.render=function(){var b=a('
    ');return this.options.get("multiple")&&b.attr("aria-multiselectable","true"),this.$results=b,b},c.prototype.clear=function(){this.$results.empty()},c.prototype.displayMessage=function(b){var c=this.options.get("escapeMarkup");this.clear(),this.hideLoading();var d=a('
  • '),e=this.options.get("translations").get(b.message);d.append(c(e(b.args))),d[0].className+=" select2-results__message",this.$results.append(d)},c.prototype.hideMessages=function(){this.$results.find(".select2-results__message").remove()},c.prototype.append=function(a){this.hideLoading();var b=[];if(null==a.results||0===a.results.length)return void(0===this.$results.children().length&&this.trigger("results:message",{message:"noResults"}));a.results=this.sort(a.results);for(var c=0;c0?b.first().trigger("mouseenter"):a.first().trigger("mouseenter"),this.ensureHighlightVisible()},c.prototype.setClasses=function(){var b=this;this.data.current(function(c){var d=a.map(c,function(a){return a.id.toString()}),e=b.$results.find(".select2-results__option[aria-selected]");e.each(function(){var b=a(this),c=a.data(this,"data"),e=""+c.id;null!=c.element&&c.element.selected||null==c.element&&a.inArray(e,d)>-1?b.attr("aria-selected","true"):b.attr("aria-selected","false")})})},c.prototype.showLoading=function(a){this.hideLoading();var b=this.options.get("translations").get("searching"),c={disabled:!0,loading:!0,text:b(a)},d=this.option(c);d.className+=" loading-results",this.$results.prepend(d)},c.prototype.hideLoading=function(){this.$results.find(".loading-results").remove()},c.prototype.option=function(b){var c=document.createElement("li");c.className="select2-results__option";var d={role:"treeitem","aria-selected":"false"};b.disabled&&(delete d["aria-selected"],d["aria-disabled"]="true"),null==b.id&&delete d["aria-selected"],null!=b._resultId&&(c.id=b._resultId),b.title&&(c.title=b.title),b.children&&(d.role="group",d["aria-label"]=b.text,delete d["aria-selected"]);for(var e in d){var f=d[e];c.setAttribute(e,f)}if(b.children){var g=a(c),h=document.createElement("strong");h.className="select2-results__group";a(h);this.template(b,h);for(var i=[],j=0;j",{"class":"select2-results__options select2-results__options--nested"});m.append(i),g.append(h),g.append(m)}else this.template(b,c);return a.data(c,"data",b),c},c.prototype.bind=function(b,c){var d=this,e=b.id+"-results";this.$results.attr("id",e),b.on("results:all",function(a){d.clear(),d.append(a.data),b.isOpen()&&(d.setClasses(),d.highlightFirstItem())}),b.on("results:append",function(a){d.append(a.data),b.isOpen()&&d.setClasses()}),b.on("query",function(a){d.hideMessages(),d.showLoading(a)}),b.on("select",function(){b.isOpen()&&(d.setClasses(),d.highlightFirstItem())}),b.on("unselect",function(){b.isOpen()&&(d.setClasses(),d.highlightFirstItem())}),b.on("open",function(){d.$results.attr("aria-expanded","true"),d.$results.attr("aria-hidden","false"),d.setClasses(),d.ensureHighlightVisible()}),b.on("close",function(){d.$results.attr("aria-expanded","false"),d.$results.attr("aria-hidden","true"),d.$results.removeAttr("aria-activedescendant")}),b.on("results:toggle",function(){var a=d.getHighlightedResults();0!==a.length&&a.trigger("mouseup")}),b.on("results:select",function(){var a=d.getHighlightedResults();if(0!==a.length){var b=a.data("data");"true"==a.attr("aria-selected")?d.trigger("close",{}):d.trigger("select",{data:b})}}),b.on("results:previous",function(){var a=d.getHighlightedResults(),b=d.$results.find("[aria-selected]"),c=b.index(a);if(0!==c){var e=c-1;0===a.length&&(e=0);var f=b.eq(e);f.trigger("mouseenter");var g=d.$results.offset().top,h=f.offset().top,i=d.$results.scrollTop()+(h-g);0===e?d.$results.scrollTop(0):0>h-g&&d.$results.scrollTop(i)}}),b.on("results:next",function(){var a=d.getHighlightedResults(),b=d.$results.find("[aria-selected]"),c=b.index(a),e=c+1;if(!(e>=b.length)){var f=b.eq(e);f.trigger("mouseenter");var g=d.$results.offset().top+d.$results.outerHeight(!1),h=f.offset().top+f.outerHeight(!1),i=d.$results.scrollTop()+h-g;0===e?d.$results.scrollTop(0):h>g&&d.$results.scrollTop(i)}}),b.on("results:focus",function(a){a.element.addClass("select2-results__option--highlighted")}),b.on("results:message",function(a){d.displayMessage(a)}),a.fn.mousewheel&&this.$results.on("mousewheel",function(a){var b=d.$results.scrollTop(),c=d.$results.get(0).scrollHeight-b+a.deltaY,e=a.deltaY>0&&b-a.deltaY<=0,f=a.deltaY<0&&c<=d.$results.height();e?(d.$results.scrollTop(0),a.preventDefault(),a.stopPropagation()):f&&(d.$results.scrollTop(d.$results.get(0).scrollHeight-d.$results.height()),a.preventDefault(),a.stopPropagation())}),this.$results.on("mouseup",".select2-results__option[aria-selected]",function(b){var c=a(this),e=c.data("data");return"true"===c.attr("aria-selected")?void(d.options.get("multiple")?d.trigger("unselect",{originalEvent:b,data:e}):d.trigger("close",{})):void d.trigger("select",{originalEvent:b,data:e})}),this.$results.on("mouseenter",".select2-results__option[aria-selected]",function(b){var c=a(this).data("data");d.getHighlightedResults().removeClass("select2-results__option--highlighted"),d.trigger("results:focus",{data:c,element:a(this)})})},c.prototype.getHighlightedResults=function(){var a=this.$results.find(".select2-results__option--highlighted");return a},c.prototype.destroy=function(){this.$results.remove()},c.prototype.ensureHighlightVisible=function(){var a=this.getHighlightedResults();if(0!==a.length){var b=this.$results.find("[aria-selected]"),c=b.index(a),d=this.$results.offset().top,e=a.offset().top,f=this.$results.scrollTop()+(e-d),g=e-d;f-=2*a.outerHeight(!1),2>=c?this.$results.scrollTop(0):(g>this.$results.outerHeight()||0>g)&&this.$results.scrollTop(f)}},c.prototype.template=function(b,c){var d=this.options.get("templateResult"),e=this.options.get("escapeMarkup"),f=d(b,c);null==f?c.style.display="none":"string"==typeof f?c.innerHTML=e(f):a(c).append(f)},c}),b.define("select2/keys",[],function(){var a={BACKSPACE:8,TAB:9,ENTER:13,SHIFT:16,CTRL:17,ALT:18,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,DELETE:46};return a}),b.define("select2/selection/base",["jquery","../utils","../keys"],function(a,b,c){function d(a,b){this.$element=a,this.options=b,d.__super__.constructor.call(this)}return b.Extend(d,b.Observable),d.prototype.render=function(){var b=a('');return this._tabindex=0,null!=this.$element.data("old-tabindex")?this._tabindex=this.$element.data("old-tabindex"):null!=this.$element.attr("tabindex")&&(this._tabindex=this.$element.attr("tabindex")),b.attr("title",this.$element.attr("title")),b.attr("tabindex",this._tabindex),this.$selection=b,b},d.prototype.bind=function(a,b){var d=this,e=(a.id+"-container",a.id+"-results");this.container=a,this.$selection.on("focus",function(a){d.trigger("focus",a)}),this.$selection.on("blur",function(a){d._handleBlur(a)}),this.$selection.on("keydown",function(a){d.trigger("keypress",a),a.which===c.SPACE&&a.preventDefault()}),a.on("results:focus",function(a){d.$selection.attr("aria-activedescendant",a.data._resultId)}),a.on("selection:update",function(a){d.update(a.data)}),a.on("open",function(){d.$selection.attr("aria-expanded","true"),d.$selection.attr("aria-owns",e),d._attachCloseHandler(a)}),a.on("close",function(){d.$selection.attr("aria-expanded","false"),d.$selection.removeAttr("aria-activedescendant"),d.$selection.removeAttr("aria-owns"),d.$selection.focus(),d._detachCloseHandler(a)}),a.on("enable",function(){d.$selection.attr("tabindex",d._tabindex)}),a.on("disable",function(){d.$selection.attr("tabindex","-1")})},d.prototype._handleBlur=function(b){var c=this;window.setTimeout(function(){document.activeElement==c.$selection[0]||a.contains(c.$selection[0],document.activeElement)||c.trigger("blur",b)},1)},d.prototype._attachCloseHandler=function(b){a(document.body).on("mousedown.select2."+b.id,function(b){var c=a(b.target),d=c.closest(".select2"),e=a(".select2.select2-container--open");e.each(function(){var b=a(this);if(this!=d[0]){var c=b.data("element");c.select2("close")}})})},d.prototype._detachCloseHandler=function(b){a(document.body).off("mousedown.select2."+b.id)},d.prototype.position=function(a,b){var c=b.find(".selection");c.append(a)},d.prototype.destroy=function(){this._detachCloseHandler(this.container)},d.prototype.update=function(a){throw new Error("The `update` method must be defined in child classes.")},d}),b.define("select2/selection/single",["jquery","./base","../utils","../keys"],function(a,b,c,d){function e(){e.__super__.constructor.apply(this,arguments)}return c.Extend(e,b),e.prototype.render=function(){var a=e.__super__.render.call(this);return a.addClass("select2-selection--single"),a.html(''),a},e.prototype.bind=function(a,b){var c=this;e.__super__.bind.apply(this,arguments);var d=a.id+"-container";this.$selection.find(".select2-selection__rendered").attr("id",d),this.$selection.attr("aria-labelledby",d),this.$selection.on("mousedown",function(a){1===a.which&&c.trigger("toggle",{originalEvent:a})}),this.$selection.on("focus",function(a){}),this.$selection.on("blur",function(a){}),a.on("focus",function(b){a.isOpen()||c.$selection.focus()}),a.on("selection:update",function(a){c.update(a.data)})},e.prototype.clear=function(){this.$selection.find(".select2-selection__rendered").empty()},e.prototype.display=function(a,b){var c=this.options.get("templateSelection"),d=this.options.get("escapeMarkup");return d(c(a,b))},e.prototype.selectionContainer=function(){return a("")},e.prototype.update=function(a){if(0===a.length)return void this.clear();var b=a[0],c=this.$selection.find(".select2-selection__rendered"),d=this.display(b,c);c.empty().append(d),c.prop("title",b.title||b.text)},e}),b.define("select2/selection/multiple",["jquery","./base","../utils"],function(a,b,c){function d(a,b){d.__super__.constructor.apply(this,arguments)}return c.Extend(d,b),d.prototype.render=function(){var a=d.__super__.render.call(this);return a.addClass("select2-selection--multiple"),a.html('
      '),a},d.prototype.bind=function(b,c){var e=this;d.__super__.bind.apply(this,arguments),this.$selection.on("click",function(a){e.trigger("toggle",{originalEvent:a})}),this.$selection.on("click",".select2-selection__choice__remove",function(b){if(!e.options.get("disabled")){var c=a(this),d=c.parent(),f=d.data("data");e.trigger("unselect",{originalEvent:b,data:f})}})},d.prototype.clear=function(){this.$selection.find(".select2-selection__rendered").empty()},d.prototype.display=function(a,b){var c=this.options.get("templateSelection"),d=this.options.get("escapeMarkup");return d(c(a,b))},d.prototype.selectionContainer=function(){var b=a('
    • ×
    • ');return b},d.prototype.update=function(a){if(this.clear(),0!==a.length){for(var b=[],d=0;d1;if(d||c)return a.call(this,b);this.clear();var e=this.createPlaceholder(this.placeholder);this.$selection.find(".select2-selection__rendered").append(e)},b}),b.define("select2/selection/allowClear",["jquery","../keys"],function(a,b){function c(){}return c.prototype.bind=function(a,b,c){var d=this;a.call(this,b,c),null==this.placeholder&&this.options.get("debug")&&window.console&&console.error&&console.error("Select2: The `allowClear` option should be used in combination with the `placeholder` option."),this.$selection.on("mousedown",".select2-selection__clear",function(a){d._handleClear(a)}),b.on("keypress",function(a){d._handleKeyboardClear(a,b)})},c.prototype._handleClear=function(a,b){if(!this.options.get("disabled")){var c=this.$selection.find(".select2-selection__clear");if(0!==c.length){b.stopPropagation();for(var d=c.data("data"),e=0;e0||0===c.length)){var d=a('×');d.data("data",c),this.$selection.find(".select2-selection__rendered").prepend(d)}},c}),b.define("select2/selection/search",["jquery","../utils","../keys"],function(a,b,c){function d(a,b,c){a.call(this,b,c)}return d.prototype.render=function(b){var c=a('');this.$searchContainer=c,this.$search=c.find("input");var d=b.call(this);return this._transferTabIndex(),d},d.prototype.bind=function(a,b,d){var e=this;a.call(this,b,d),b.on("open",function(){e.$search.trigger("focus")}),b.on("close",function(){e.$search.val(""),e.$search.removeAttr("aria-activedescendant"),e.$search.trigger("focus")}),b.on("enable",function(){e.$search.prop("disabled",!1),e._transferTabIndex()}),b.on("disable",function(){e.$search.prop("disabled",!0)}),b.on("focus",function(a){e.$search.trigger("focus")}),b.on("results:focus",function(a){e.$search.attr("aria-activedescendant",a.id)}),this.$selection.on("focusin",".select2-search--inline",function(a){e.trigger("focus",a)}),this.$selection.on("focusout",".select2-search--inline",function(a){e._handleBlur(a)}),this.$selection.on("keydown",".select2-search--inline",function(a){a.stopPropagation(),e.trigger("keypress",a),e._keyUpPrevented=a.isDefaultPrevented();var b=a.which;if(b===c.BACKSPACE&&""===e.$search.val()){var d=e.$searchContainer.prev(".select2-selection__choice");if(d.length>0){var f=d.data("data");e.searchRemoveChoice(f),a.preventDefault()}}});var f=document.documentMode,g=f&&11>=f;this.$selection.on("input.searchcheck",".select2-search--inline",function(a){return g?void e.$selection.off("input.search input.searchcheck"):void e.$selection.off("keyup.search")}),this.$selection.on("keyup.search input.search",".select2-search--inline",function(a){if(g&&"input"===a.type)return void e.$selection.off("input.search input.searchcheck");var b=a.which;b!=c.SHIFT&&b!=c.CTRL&&b!=c.ALT&&b!=c.TAB&&e.handleSearch(a)})},d.prototype._transferTabIndex=function(a){this.$search.attr("tabindex",this.$selection.attr("tabindex")),this.$selection.attr("tabindex","-1")},d.prototype.createPlaceholder=function(a,b){this.$search.attr("placeholder",b.text)},d.prototype.update=function(a,b){var c=this.$search[0]==document.activeElement;this.$search.attr("placeholder",""),a.call(this,b),this.$selection.find(".select2-selection__rendered").append(this.$searchContainer),this.resizeSearch(),c&&this.$search.focus()},d.prototype.handleSearch=function(){if(this.resizeSearch(),!this._keyUpPrevented){var a=this.$search.val();this.trigger("query",{term:a})}this._keyUpPrevented=!1},d.prototype.searchRemoveChoice=function(a,b){this.trigger("unselect",{data:b}),this.$search.val(b.text),this.handleSearch()},d.prototype.resizeSearch=function(){this.$search.css("width","25px");var a="";if(""!==this.$search.attr("placeholder"))a=this.$selection.find(".select2-selection__rendered").innerWidth();else{var b=this.$search.val().length+1;a=.75*b+"em"}this.$search.css("width",a)},d}),b.define("select2/selection/eventRelay",["jquery"],function(a){function b(){}return b.prototype.bind=function(b,c,d){var e=this,f=["open","opening","close","closing","select","selecting","unselect","unselecting"],g=["opening","closing","selecting","unselecting"];b.call(this,c,d),c.on("*",function(b,c){if(-1!==a.inArray(b,f)){c=c||{};var d=a.Event("select2:"+b,{params:c});e.$element.trigger(d),-1!==a.inArray(b,g)&&(c.prevented=d.isDefaultPrevented())}})},b}),b.define("select2/translation",["jquery","require"],function(a,b){function c(a){this.dict=a||{}}return c.prototype.all=function(){return this.dict},c.prototype.get=function(a){return this.dict[a]},c.prototype.extend=function(b){this.dict=a.extend({},b.all(),this.dict)},c._cache={},c.loadPath=function(a){if(!(a in c._cache)){var d=b(a);c._cache[a]=d}return new c(c._cache[a])},c}),b.define("select2/diacritics",[],function(){var a={"Ⓐ":"A","A":"A","À":"A","Á":"A","Â":"A","Ầ":"A","Ấ":"A","Ẫ":"A","Ẩ":"A","Ã":"A","Ā":"A","Ă":"A","Ằ":"A","Ắ":"A","Ẵ":"A","Ẳ":"A","Ȧ":"A","Ǡ":"A","Ä":"A","Ǟ":"A","Ả":"A","Å":"A","Ǻ":"A","Ǎ":"A","Ȁ":"A","Ȃ":"A","Ạ":"A","Ậ":"A","Ặ":"A","Ḁ":"A","Ą":"A","Ⱥ":"A","Ɐ":"A","Ꜳ":"AA","Æ":"AE","Ǽ":"AE","Ǣ":"AE","Ꜵ":"AO","Ꜷ":"AU","Ꜹ":"AV","Ꜻ":"AV","Ꜽ":"AY","Ⓑ":"B","B":"B","Ḃ":"B","Ḅ":"B","Ḇ":"B","Ƀ":"B","Ƃ":"B","Ɓ":"B","Ⓒ":"C","C":"C","Ć":"C","Ĉ":"C","Ċ":"C","Č":"C","Ç":"C","Ḉ":"C","Ƈ":"C","Ȼ":"C","Ꜿ":"C","Ⓓ":"D","D":"D","Ḋ":"D","Ď":"D","Ḍ":"D","Ḑ":"D","Ḓ":"D","Ḏ":"D","Đ":"D","Ƌ":"D","Ɗ":"D","Ɖ":"D","Ꝺ":"D","DZ":"DZ","DŽ":"DZ","Dz":"Dz","Dž":"Dz","Ⓔ":"E","E":"E","È":"E","É":"E","Ê":"E","Ề":"E","Ế":"E","Ễ":"E","Ể":"E","Ẽ":"E","Ē":"E","Ḕ":"E","Ḗ":"E","Ĕ":"E","Ė":"E","Ë":"E","Ẻ":"E","Ě":"E","Ȅ":"E","Ȇ":"E","Ẹ":"E","Ệ":"E","Ȩ":"E","Ḝ":"E","Ę":"E","Ḙ":"E","Ḛ":"E","Ɛ":"E","Ǝ":"E","Ⓕ":"F","F":"F","Ḟ":"F","Ƒ":"F","Ꝼ":"F","Ⓖ":"G","G":"G","Ǵ":"G","Ĝ":"G","Ḡ":"G","Ğ":"G","Ġ":"G","Ǧ":"G","Ģ":"G","Ǥ":"G","Ɠ":"G","Ꞡ":"G","Ᵹ":"G","Ꝿ":"G","Ⓗ":"H","H":"H","Ĥ":"H","Ḣ":"H","Ḧ":"H","Ȟ":"H","Ḥ":"H","Ḩ":"H","Ḫ":"H","Ħ":"H","Ⱨ":"H","Ⱶ":"H","Ɥ":"H","Ⓘ":"I","I":"I","Ì":"I","Í":"I","Î":"I","Ĩ":"I","Ī":"I","Ĭ":"I","İ":"I","Ï":"I","Ḯ":"I","Ỉ":"I","Ǐ":"I","Ȉ":"I","Ȋ":"I","Ị":"I","Į":"I","Ḭ":"I","Ɨ":"I","Ⓙ":"J","J":"J","Ĵ":"J","Ɉ":"J","Ⓚ":"K","K":"K","Ḱ":"K","Ǩ":"K","Ḳ":"K","Ķ":"K","Ḵ":"K","Ƙ":"K","Ⱪ":"K","Ꝁ":"K","Ꝃ":"K","Ꝅ":"K","Ꞣ":"K","Ⓛ":"L","L":"L","Ŀ":"L","Ĺ":"L","Ľ":"L","Ḷ":"L","Ḹ":"L","Ļ":"L","Ḽ":"L","Ḻ":"L","Ł":"L","Ƚ":"L","Ɫ":"L","Ⱡ":"L","Ꝉ":"L","Ꝇ":"L","Ꞁ":"L","LJ":"LJ","Lj":"Lj","Ⓜ":"M","M":"M","Ḿ":"M","Ṁ":"M","Ṃ":"M","Ɱ":"M","Ɯ":"M","Ⓝ":"N","N":"N","Ǹ":"N","Ń":"N","Ñ":"N","Ṅ":"N","Ň":"N","Ṇ":"N","Ņ":"N","Ṋ":"N","Ṉ":"N","Ƞ":"N","Ɲ":"N","Ꞑ":"N","Ꞥ":"N","NJ":"NJ","Nj":"Nj","Ⓞ":"O","O":"O","Ò":"O","Ó":"O","Ô":"O","Ồ":"O","Ố":"O","Ỗ":"O","Ổ":"O","Õ":"O","Ṍ":"O","Ȭ":"O","Ṏ":"O","Ō":"O","Ṑ":"O","Ṓ":"O","Ŏ":"O","Ȯ":"O","Ȱ":"O","Ö":"O","Ȫ":"O","Ỏ":"O","Ő":"O","Ǒ":"O","Ȍ":"O","Ȏ":"O","Ơ":"O","Ờ":"O","Ớ":"O","Ỡ":"O","Ở":"O","Ợ":"O","Ọ":"O","Ộ":"O","Ǫ":"O","Ǭ":"O","Ø":"O","Ǿ":"O","Ɔ":"O","Ɵ":"O","Ꝋ":"O","Ꝍ":"O","Ƣ":"OI","Ꝏ":"OO","Ȣ":"OU","Ⓟ":"P","P":"P","Ṕ":"P","Ṗ":"P","Ƥ":"P","Ᵽ":"P","Ꝑ":"P","Ꝓ":"P","Ꝕ":"P","Ⓠ":"Q","Q":"Q","Ꝗ":"Q","Ꝙ":"Q","Ɋ":"Q","Ⓡ":"R","R":"R","Ŕ":"R","Ṙ":"R","Ř":"R","Ȑ":"R","Ȓ":"R","Ṛ":"R","Ṝ":"R","Ŗ":"R","Ṟ":"R","Ɍ":"R","Ɽ":"R","Ꝛ":"R","Ꞧ":"R","Ꞃ":"R","Ⓢ":"S","S":"S","ẞ":"S","Ś":"S","Ṥ":"S","Ŝ":"S","Ṡ":"S","Š":"S","Ṧ":"S","Ṣ":"S","Ṩ":"S","Ș":"S","Ş":"S","Ȿ":"S","Ꞩ":"S","Ꞅ":"S","Ⓣ":"T","T":"T","Ṫ":"T","Ť":"T","Ṭ":"T","Ț":"T","Ţ":"T","Ṱ":"T","Ṯ":"T","Ŧ":"T","Ƭ":"T","Ʈ":"T","Ⱦ":"T","Ꞇ":"T","Ꜩ":"TZ","Ⓤ":"U","U":"U","Ù":"U","Ú":"U","Û":"U","Ũ":"U","Ṹ":"U","Ū":"U","Ṻ":"U","Ŭ":"U","Ü":"U","Ǜ":"U","Ǘ":"U","Ǖ":"U","Ǚ":"U","Ủ":"U","Ů":"U","Ű":"U","Ǔ":"U","Ȕ":"U","Ȗ":"U","Ư":"U","Ừ":"U","Ứ":"U","Ữ":"U","Ử":"U","Ự":"U","Ụ":"U","Ṳ":"U","Ų":"U","Ṷ":"U","Ṵ":"U","Ʉ":"U","Ⓥ":"V","V":"V","Ṽ":"V","Ṿ":"V","Ʋ":"V","Ꝟ":"V","Ʌ":"V","Ꝡ":"VY","Ⓦ":"W","W":"W","Ẁ":"W","Ẃ":"W","Ŵ":"W","Ẇ":"W","Ẅ":"W","Ẉ":"W","Ⱳ":"W","Ⓧ":"X","X":"X","Ẋ":"X","Ẍ":"X","Ⓨ":"Y","Y":"Y","Ỳ":"Y","Ý":"Y","Ŷ":"Y","Ỹ":"Y","Ȳ":"Y","Ẏ":"Y","Ÿ":"Y","Ỷ":"Y","Ỵ":"Y","Ƴ":"Y","Ɏ":"Y","Ỿ":"Y","Ⓩ":"Z","Z":"Z","Ź":"Z","Ẑ":"Z","Ż":"Z","Ž":"Z","Ẓ":"Z","Ẕ":"Z","Ƶ":"Z","Ȥ":"Z","Ɀ":"Z","Ⱬ":"Z","Ꝣ":"Z","ⓐ":"a","a":"a","ẚ":"a","à":"a","á":"a","â":"a","ầ":"a","ấ":"a","ẫ":"a","ẩ":"a","ã":"a","ā":"a","ă":"a","ằ":"a","ắ":"a","ẵ":"a","ẳ":"a","ȧ":"a","ǡ":"a","ä":"a","ǟ":"a","ả":"a","å":"a","ǻ":"a","ǎ":"a","ȁ":"a","ȃ":"a","ạ":"a","ậ":"a","ặ":"a","ḁ":"a","ą":"a","ⱥ":"a","ɐ":"a","ꜳ":"aa","æ":"ae","ǽ":"ae","ǣ":"ae","ꜵ":"ao","ꜷ":"au","ꜹ":"av","ꜻ":"av","ꜽ":"ay","ⓑ":"b","b":"b","ḃ":"b","ḅ":"b","ḇ":"b","ƀ":"b","ƃ":"b","ɓ":"b","ⓒ":"c","c":"c","ć":"c","ĉ":"c","ċ":"c","č":"c","ç":"c","ḉ":"c","ƈ":"c","ȼ":"c","ꜿ":"c","ↄ":"c","ⓓ":"d","d":"d","ḋ":"d","ď":"d","ḍ":"d","ḑ":"d","ḓ":"d","ḏ":"d","đ":"d","ƌ":"d","ɖ":"d","ɗ":"d","ꝺ":"d","dz":"dz","dž":"dz","ⓔ":"e","e":"e","è":"e","é":"e","ê":"e","ề":"e","ế":"e","ễ":"e","ể":"e","ẽ":"e","ē":"e","ḕ":"e","ḗ":"e","ĕ":"e","ė":"e","ë":"e","ẻ":"e","ě":"e","ȅ":"e","ȇ":"e","ẹ":"e","ệ":"e","ȩ":"e","ḝ":"e","ę":"e","ḙ":"e","ḛ":"e","ɇ":"e","ɛ":"e","ǝ":"e","ⓕ":"f","f":"f","ḟ":"f","ƒ":"f","ꝼ":"f","ⓖ":"g","g":"g","ǵ":"g","ĝ":"g","ḡ":"g","ğ":"g","ġ":"g","ǧ":"g","ģ":"g","ǥ":"g","ɠ":"g","ꞡ":"g","ᵹ":"g","ꝿ":"g","ⓗ":"h","h":"h","ĥ":"h","ḣ":"h","ḧ":"h","ȟ":"h","ḥ":"h","ḩ":"h","ḫ":"h","ẖ":"h","ħ":"h","ⱨ":"h","ⱶ":"h","ɥ":"h","ƕ":"hv","ⓘ":"i","i":"i","ì":"i","í":"i","î":"i","ĩ":"i","ī":"i","ĭ":"i","ï":"i","ḯ":"i","ỉ":"i","ǐ":"i","ȉ":"i","ȋ":"i","ị":"i","į":"i","ḭ":"i","ɨ":"i","ı":"i","ⓙ":"j","j":"j","ĵ":"j","ǰ":"j","ɉ":"j","ⓚ":"k","k":"k","ḱ":"k","ǩ":"k","ḳ":"k","ķ":"k","ḵ":"k","ƙ":"k","ⱪ":"k","ꝁ":"k","ꝃ":"k","ꝅ":"k","ꞣ":"k","ⓛ":"l","l":"l","ŀ":"l","ĺ":"l","ľ":"l","ḷ":"l","ḹ":"l","ļ":"l","ḽ":"l","ḻ":"l","ſ":"l","ł":"l","ƚ":"l","ɫ":"l","ⱡ":"l","ꝉ":"l","ꞁ":"l","ꝇ":"l","lj":"lj","ⓜ":"m","m":"m","ḿ":"m","ṁ":"m","ṃ":"m","ɱ":"m","ɯ":"m","ⓝ":"n","n":"n","ǹ":"n","ń":"n","ñ":"n","ṅ":"n","ň":"n","ṇ":"n","ņ":"n","ṋ":"n","ṉ":"n","ƞ":"n","ɲ":"n","ʼn":"n","ꞑ":"n","ꞥ":"n","nj":"nj","ⓞ":"o","o":"o","ò":"o","ó":"o","ô":"o","ồ":"o","ố":"o","ỗ":"o","ổ":"o","õ":"o","ṍ":"o","ȭ":"o","ṏ":"o","ō":"o","ṑ":"o","ṓ":"o","ŏ":"o","ȯ":"o","ȱ":"o","ö":"o","ȫ":"o","ỏ":"o","ő":"o","ǒ":"o","ȍ":"o","ȏ":"o","ơ":"o","ờ":"o","ớ":"o","ỡ":"o","ở":"o","ợ":"o","ọ":"o","ộ":"o","ǫ":"o","ǭ":"o","ø":"o","ǿ":"o","ɔ":"o","ꝋ":"o","ꝍ":"o","ɵ":"o","ƣ":"oi","ȣ":"ou","ꝏ":"oo","ⓟ":"p","p":"p","ṕ":"p","ṗ":"p","ƥ":"p","ᵽ":"p","ꝑ":"p","ꝓ":"p","ꝕ":"p","ⓠ":"q","q":"q","ɋ":"q","ꝗ":"q","ꝙ":"q","ⓡ":"r","r":"r","ŕ":"r","ṙ":"r","ř":"r","ȑ":"r","ȓ":"r","ṛ":"r","ṝ":"r","ŗ":"r","ṟ":"r","ɍ":"r","ɽ":"r","ꝛ":"r","ꞧ":"r","ꞃ":"r","ⓢ":"s","s":"s","ß":"s","ś":"s","ṥ":"s","ŝ":"s","ṡ":"s","š":"s","ṧ":"s","ṣ":"s","ṩ":"s","ș":"s","ş":"s","ȿ":"s","ꞩ":"s","ꞅ":"s","ẛ":"s","ⓣ":"t","t":"t","ṫ":"t","ẗ":"t","ť":"t","ṭ":"t","ț":"t","ţ":"t","ṱ":"t","ṯ":"t","ŧ":"t","ƭ":"t","ʈ":"t","ⱦ":"t","ꞇ":"t","ꜩ":"tz","ⓤ":"u","u":"u","ù":"u","ú":"u","û":"u","ũ":"u","ṹ":"u","ū":"u","ṻ":"u","ŭ":"u","ü":"u","ǜ":"u","ǘ":"u","ǖ":"u","ǚ":"u","ủ":"u","ů":"u","ű":"u","ǔ":"u","ȕ":"u","ȗ":"u","ư":"u","ừ":"u","ứ":"u","ữ":"u","ử":"u","ự":"u","ụ":"u","ṳ":"u","ų":"u","ṷ":"u","ṵ":"u","ʉ":"u","ⓥ":"v","v":"v","ṽ":"v","ṿ":"v","ʋ":"v","ꝟ":"v","ʌ":"v","ꝡ":"vy","ⓦ":"w","w":"w","ẁ":"w","ẃ":"w","ŵ":"w","ẇ":"w","ẅ":"w","ẘ":"w","ẉ":"w","ⱳ":"w","ⓧ":"x","x":"x","ẋ":"x","ẍ":"x","ⓨ":"y","y":"y","ỳ":"y","ý":"y","ŷ":"y","ỹ":"y","ȳ":"y","ẏ":"y","ÿ":"y","ỷ":"y","ẙ":"y","ỵ":"y","ƴ":"y","ɏ":"y","ỿ":"y","ⓩ":"z","z":"z","ź":"z","ẑ":"z","ż":"z","ž":"z","ẓ":"z","ẕ":"z","ƶ":"z","ȥ":"z","ɀ":"z","ⱬ":"z","ꝣ":"z","Ά":"Α","Έ":"Ε","Ή":"Η","Ί":"Ι","Ϊ":"Ι","Ό":"Ο","Ύ":"Υ","Ϋ":"Υ","Ώ":"Ω","ά":"α","έ":"ε","ή":"η","ί":"ι","ϊ":"ι","ΐ":"ι","ό":"ο","ύ":"υ","ϋ":"υ","ΰ":"υ","ω":"ω","ς":"σ"};return a}),b.define("select2/data/base",["../utils"],function(a){function b(a,c){b.__super__.constructor.call(this)}return a.Extend(b,a.Observable),b.prototype.current=function(a){throw new Error("The `current` method must be defined in child classes.")},b.prototype.query=function(a,b){throw new Error("The `query` method must be defined in child classes.")},b.prototype.bind=function(a,b){},b.prototype.destroy=function(){},b.prototype.generateResultId=function(b,c){var d=b.id+"-result-";return d+=a.generateChars(4),d+=null!=c.id?"-"+c.id.toString():"-"+a.generateChars(4)},b}),b.define("select2/data/select",["./base","../utils","jquery"],function(a,b,c){function d(a,b){this.$element=a,this.options=b,d.__super__.constructor.call(this)}return b.Extend(d,a),d.prototype.current=function(a){var b=[],d=this;this.$element.find(":selected").each(function(){var a=c(this),e=d.item(a);b.push(e)}),a(b)},d.prototype.select=function(a){var b=this;if(a.selected=!0,c(a.element).is("option"))return a.element.selected=!0,void this.$element.trigger("change"); -if(this.$element.prop("multiple"))this.current(function(d){var e=[];a=[a],a.push.apply(a,d);for(var f=0;f=0){var k=f.filter(d(j)),l=this.item(k),m=c.extend(!0,{},j,l),n=this.option(m);k.replaceWith(n)}else{var o=this.option(j);if(j.children){var p=this.convertToOptions(j.children);b.appendMany(o,p)}h.push(o)}}return h},d}),b.define("select2/data/ajax",["./array","../utils","jquery"],function(a,b,c){function d(a,b){this.ajaxOptions=this._applyDefaults(b.get("ajax")),null!=this.ajaxOptions.processResults&&(this.processResults=this.ajaxOptions.processResults),d.__super__.constructor.call(this,a,b)}return b.Extend(d,a),d.prototype._applyDefaults=function(a){var b={data:function(a){return c.extend({},a,{q:a.term})},transport:function(a,b,d){var e=c.ajax(a);return e.then(b),e.fail(d),e}};return c.extend({},b,a,!0)},d.prototype.processResults=function(a){return a},d.prototype.query=function(a,b){function d(){var d=f.transport(f,function(d){var f=e.processResults(d,a);e.options.get("debug")&&window.console&&console.error&&(f&&f.results&&c.isArray(f.results)||console.error("Select2: The AJAX results did not return an array in the `results` key of the response.")),b(f)},function(){d.status&&"0"===d.status||e.trigger("results:message",{message:"errorLoading"})});e._request=d}var e=this;null!=this._request&&(c.isFunction(this._request.abort)&&this._request.abort(),this._request=null);var f=c.extend({type:"GET"},this.ajaxOptions);"function"==typeof f.url&&(f.url=f.url.call(this.$element,a)),"function"==typeof f.data&&(f.data=f.data.call(this.$element,a)),this.ajaxOptions.delay&&null!=a.term?(this._queryTimeout&&window.clearTimeout(this._queryTimeout),this._queryTimeout=window.setTimeout(d,this.ajaxOptions.delay)):d()},d}),b.define("select2/data/tags",["jquery"],function(a){function b(b,c,d){var e=d.get("tags"),f=d.get("createTag");void 0!==f&&(this.createTag=f);var g=d.get("insertTag");if(void 0!==g&&(this.insertTag=g),b.call(this,c,d),a.isArray(e))for(var h=0;h0&&b.term.length>this.maximumInputLength?void this.trigger("results:message",{message:"inputTooLong",args:{maximum:this.maximumInputLength,input:b.term,params:b}}):void a.call(this,b,c)},a}),b.define("select2/data/maximumSelectionLength",[],function(){function a(a,b,c){this.maximumSelectionLength=c.get("maximumSelectionLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){var d=this;this.current(function(e){var f=null!=e?e.length:0;return d.maximumSelectionLength>0&&f>=d.maximumSelectionLength?void d.trigger("results:message",{message:"maximumSelected",args:{maximum:d.maximumSelectionLength}}):void a.call(d,b,c)})},a}),b.define("select2/dropdown",["jquery","./utils"],function(a,b){function c(a,b){this.$element=a,this.options=b,c.__super__.constructor.call(this)}return b.Extend(c,b.Observable),c.prototype.render=function(){var b=a('');return b.attr("dir",this.options.get("dir")),this.$dropdown=b,b},c.prototype.bind=function(){},c.prototype.position=function(a,b){},c.prototype.destroy=function(){this.$dropdown.remove()},c}),b.define("select2/dropdown/search",["jquery","../utils"],function(a,b){function c(){}return c.prototype.render=function(b){var c=b.call(this),d=a('');return this.$searchContainer=d,this.$search=d.find("input"),c.prepend(d),c},c.prototype.bind=function(b,c,d){var e=this;b.call(this,c,d),this.$search.on("keydown",function(a){e.trigger("keypress",a),e._keyUpPrevented=a.isDefaultPrevented()}),this.$search.on("input",function(b){a(this).off("keyup")}),this.$search.on("keyup input",function(a){e.handleSearch(a)}),c.on("open",function(){e.$search.attr("tabindex",0),e.$search.focus(),window.setTimeout(function(){e.$search.focus()},0)}),c.on("close",function(){e.$search.attr("tabindex",-1),e.$search.val("")}),c.on("focus",function(){c.isOpen()&&e.$search.focus()}),c.on("results:all",function(a){if(null==a.query.term||""===a.query.term){var b=e.showSearch(a);b?e.$searchContainer.removeClass("select2-search--hide"):e.$searchContainer.addClass("select2-search--hide")}})},c.prototype.handleSearch=function(a){if(!this._keyUpPrevented){var b=this.$search.val();this.trigger("query",{term:b})}this._keyUpPrevented=!1},c.prototype.showSearch=function(a,b){return!0},c}),b.define("select2/dropdown/hidePlaceholder",[],function(){function a(a,b,c,d){this.placeholder=this.normalizePlaceholder(c.get("placeholder")),a.call(this,b,c,d)}return a.prototype.append=function(a,b){b.results=this.removePlaceholder(b.results),a.call(this,b)},a.prototype.normalizePlaceholder=function(a,b){return"string"==typeof b&&(b={id:"",text:b}),b},a.prototype.removePlaceholder=function(a,b){for(var c=b.slice(0),d=b.length-1;d>=0;d--){var e=b[d];this.placeholder.id===e.id&&c.splice(d,1)}return c},a}),b.define("select2/dropdown/infiniteScroll",["jquery"],function(a){function b(a,b,c,d){this.lastParams={},a.call(this,b,c,d),this.$loadingMore=this.createLoadingMore(),this.loading=!1}return b.prototype.append=function(a,b){this.$loadingMore.remove(),this.loading=!1,a.call(this,b),this.showLoadingMore(b)&&this.$results.append(this.$loadingMore)},b.prototype.bind=function(b,c,d){var e=this;b.call(this,c,d),c.on("query",function(a){e.lastParams=a,e.loading=!0}),c.on("query:append",function(a){e.lastParams=a,e.loading=!0}),this.$results.on("scroll",function(){var b=a.contains(document.documentElement,e.$loadingMore[0]);if(!e.loading&&b){var c=e.$results.offset().top+e.$results.outerHeight(!1),d=e.$loadingMore.offset().top+e.$loadingMore.outerHeight(!1);c+50>=d&&e.loadMore()}})},b.prototype.loadMore=function(){this.loading=!0;var b=a.extend({},{page:1},this.lastParams);b.page++,this.trigger("query:append",b)},b.prototype.showLoadingMore=function(a,b){return b.pagination&&b.pagination.more},b.prototype.createLoadingMore=function(){var b=a('
    • '),c=this.options.get("translations").get("loadingMore");return b.html(c(this.lastParams)),b},b}),b.define("select2/dropdown/attachBody",["jquery","../utils"],function(a,b){function c(b,c,d){this.$dropdownParent=d.get("dropdownParent")||a(document.body),b.call(this,c,d)}return c.prototype.bind=function(a,b,c){var d=this,e=!1;a.call(this,b,c),b.on("open",function(){d._showDropdown(),d._attachPositioningHandler(b),e||(e=!0,b.on("results:all",function(){d._positionDropdown(),d._resizeDropdown()}),b.on("results:append",function(){d._positionDropdown(),d._resizeDropdown()}))}),b.on("close",function(){d._hideDropdown(),d._detachPositioningHandler(b)}),this.$dropdownContainer.on("mousedown",function(a){a.stopPropagation()})},c.prototype.destroy=function(a){a.call(this),this.$dropdownContainer.remove()},c.prototype.position=function(a,b,c){b.attr("class",c.attr("class")),b.removeClass("select2"),b.addClass("select2-container--open"),b.css({position:"absolute",top:-999999}),this.$container=c},c.prototype.render=function(b){var c=a(""),d=b.call(this);return c.append(d),this.$dropdownContainer=c,c},c.prototype._hideDropdown=function(a){this.$dropdownContainer.detach()},c.prototype._attachPositioningHandler=function(c,d){var e=this,f="scroll.select2."+d.id,g="resize.select2."+d.id,h="orientationchange.select2."+d.id,i=this.$container.parents().filter(b.hasScroll);i.each(function(){a(this).data("select2-scroll-position",{x:a(this).scrollLeft(),y:a(this).scrollTop()})}),i.on(f,function(b){var c=a(this).data("select2-scroll-position");a(this).scrollTop(c.y)}),a(window).on(f+" "+g+" "+h,function(a){e._positionDropdown(),e._resizeDropdown()})},c.prototype._detachPositioningHandler=function(c,d){var e="scroll.select2."+d.id,f="resize.select2."+d.id,g="orientationchange.select2."+d.id,h=this.$container.parents().filter(b.hasScroll);h.off(e),a(window).off(e+" "+f+" "+g)},c.prototype._positionDropdown=function(){var b=a(window),c=this.$dropdown.hasClass("select2-dropdown--above"),d=this.$dropdown.hasClass("select2-dropdown--below"),e=null,f=this.$container.offset();f.bottom=f.top+this.$container.outerHeight(!1);var g={height:this.$container.outerHeight(!1)};g.top=f.top,g.bottom=f.top+g.height;var h={height:this.$dropdown.outerHeight(!1)},i={top:b.scrollTop(),bottom:b.scrollTop()+b.height()},j=i.topf.bottom+h.height,l={left:f.left,top:g.bottom},m=this.$dropdownParent;"static"===m.css("position")&&(m=m.offsetParent());var n=m.offset();l.top-=n.top,l.left-=n.left,c||d||(e="below"),k||!j||c?!j&&k&&c&&(e="below"):e="above",("above"==e||c&&"below"!==e)&&(l.top=g.top-n.top-h.height),null!=e&&(this.$dropdown.removeClass("select2-dropdown--below select2-dropdown--above").addClass("select2-dropdown--"+e),this.$container.removeClass("select2-container--below select2-container--above").addClass("select2-container--"+e)),this.$dropdownContainer.css(l)},c.prototype._resizeDropdown=function(){var a={width:this.$container.outerWidth(!1)+"px"};this.options.get("dropdownAutoWidth")&&(a.minWidth=a.width,a.position="relative",a.width="auto"),this.$dropdown.css(a)},c.prototype._showDropdown=function(a){this.$dropdownContainer.appendTo(this.$dropdownParent),this._positionDropdown(),this._resizeDropdown()},c}),b.define("select2/dropdown/minimumResultsForSearch",[],function(){function a(b){for(var c=0,d=0;d0&&(l.dataAdapter=j.Decorate(l.dataAdapter,r)),l.maximumInputLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,s)),l.maximumSelectionLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,t)),l.tags&&(l.dataAdapter=j.Decorate(l.dataAdapter,p)),(null!=l.tokenSeparators||null!=l.tokenizer)&&(l.dataAdapter=j.Decorate(l.dataAdapter,q)),null!=l.query){var C=b(l.amdBase+"compat/query");l.dataAdapter=j.Decorate(l.dataAdapter,C)}if(null!=l.initSelection){var D=b(l.amdBase+"compat/initSelection");l.dataAdapter=j.Decorate(l.dataAdapter,D)}}if(null==l.resultsAdapter&&(l.resultsAdapter=c,null!=l.ajax&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,x)),null!=l.placeholder&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,w)),l.selectOnClose&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,A))),null==l.dropdownAdapter){if(l.multiple)l.dropdownAdapter=u;else{var E=j.Decorate(u,v);l.dropdownAdapter=E}if(0!==l.minimumResultsForSearch&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,z)),l.closeOnSelect&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,B)),null!=l.dropdownCssClass||null!=l.dropdownCss||null!=l.adaptDropdownCssClass){var F=b(l.amdBase+"compat/dropdownCss");l.dropdownAdapter=j.Decorate(l.dropdownAdapter,F)}l.dropdownAdapter=j.Decorate(l.dropdownAdapter,y)}if(null==l.selectionAdapter){if(l.multiple?l.selectionAdapter=e:l.selectionAdapter=d,null!=l.placeholder&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,f)),l.allowClear&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,g)),l.multiple&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,h)),null!=l.containerCssClass||null!=l.containerCss||null!=l.adaptContainerCssClass){var G=b(l.amdBase+"compat/containerCss");l.selectionAdapter=j.Decorate(l.selectionAdapter,G)}l.selectionAdapter=j.Decorate(l.selectionAdapter,i)}if("string"==typeof l.language)if(l.language.indexOf("-")>0){var H=l.language.split("-"),I=H[0];l.language=[l.language,I]}else l.language=[l.language];if(a.isArray(l.language)){var J=new k;l.language.push("en");for(var K=l.language,L=0;L0){for(var f=a.extend(!0,{},e),g=e.children.length-1;g>=0;g--){var h=e.children[g],i=c(d,h);null==i&&f.children.splice(g,1)}return f.children.length>0?f:c(d,f)}var j=b(e.text).toUpperCase(),k=b(d.term).toUpperCase();return j.indexOf(k)>-1?e:null}this.defaults={amdBase:"./",amdLanguageBase:"./i18n/",closeOnSelect:!0,debug:!1,dropdownAutoWidth:!1,escapeMarkup:j.escapeMarkup,language:C,matcher:c,minimumInputLength:0,maximumInputLength:0,maximumSelectionLength:0,minimumResultsForSearch:0,selectOnClose:!1,sorter:function(a){return a},templateResult:function(a){return a.text},templateSelection:function(a){return a.text},theme:"default",width:"resolve"}},D.prototype.set=function(b,c){var d=a.camelCase(b),e={};e[d]=c;var f=j._convertData(e);a.extend(this.defaults,f)};var E=new D;return E}),b.define("select2/options",["require","jquery","./defaults","./utils"],function(a,b,c,d){function e(b,e){if(this.options=b,null!=e&&this.fromElement(e),this.options=c.apply(this.options),e&&e.is("input")){var f=a(this.get("amdBase")+"compat/inputData");this.options.dataAdapter=d.Decorate(this.options.dataAdapter,f)}}return e.prototype.fromElement=function(a){var c=["select2"];null==this.options.multiple&&(this.options.multiple=a.prop("multiple")),null==this.options.disabled&&(this.options.disabled=a.prop("disabled")),null==this.options.language&&(a.prop("lang")?this.options.language=a.prop("lang").toLowerCase():a.closest("[lang]").prop("lang")&&(this.options.language=a.closest("[lang]").prop("lang"))),null==this.options.dir&&(a.prop("dir")?this.options.dir=a.prop("dir"):a.closest("[dir]").prop("dir")?this.options.dir=a.closest("[dir]").prop("dir"):this.options.dir="ltr"),a.prop("disabled",this.options.disabled),a.prop("multiple",this.options.multiple),a.data("select2Tags")&&(this.options.debug&&window.console&&console.warn&&console.warn('Select2: The `data-select2-tags` attribute has been changed to use the `data-data` and `data-tags="true"` attributes and will be removed in future versions of Select2.'),a.data("data",a.data("select2Tags")),a.data("tags",!0)),a.data("ajaxUrl")&&(this.options.debug&&window.console&&console.warn&&console.warn("Select2: The `data-ajax-url` attribute has been changed to `data-ajax--url` and support for the old attribute will be removed in future versions of Select2."),a.attr("ajax--url",a.data("ajaxUrl")),a.data("ajax--url",a.data("ajaxUrl")));var e={};e=b.fn.jquery&&"1."==b.fn.jquery.substr(0,2)&&a[0].dataset?b.extend(!0,{},a[0].dataset,a.data()):a.data();var f=b.extend(!0,{},e);f=d._convertData(f);for(var g in f)b.inArray(g,c)>-1||(b.isPlainObject(this.options[g])?b.extend(this.options[g],f[g]):this.options[g]=f[g]);return this},e.prototype.get=function(a){return this.options[a]},e.prototype.set=function(a,b){this.options[a]=b},e}),b.define("select2/core",["jquery","./options","./utils","./keys"],function(a,b,c,d){var e=function(a,c){null!=a.data("select2")&&a.data("select2").destroy(),this.$element=a,this.id=this._generateId(a),c=c||{},this.options=new b(c,a),e.__super__.constructor.call(this);var d=a.attr("tabindex")||0;a.data("old-tabindex",d),a.attr("tabindex","-1");var f=this.options.get("dataAdapter");this.dataAdapter=new f(a,this.options);var g=this.render();this._placeContainer(g);var h=this.options.get("selectionAdapter");this.selection=new h(a,this.options),this.$selection=this.selection.render(),this.selection.position(this.$selection,g);var i=this.options.get("dropdownAdapter");this.dropdown=new i(a,this.options),this.$dropdown=this.dropdown.render(),this.dropdown.position(this.$dropdown,g);var j=this.options.get("resultsAdapter");this.results=new j(a,this.options,this.dataAdapter),this.$results=this.results.render(),this.results.position(this.$results,this.$dropdown);var k=this;this._bindAdapters(),this._registerDomEvents(),this._registerDataEvents(),this._registerSelectionEvents(),this._registerDropdownEvents(),this._registerResultsEvents(),this._registerEvents(),this.dataAdapter.current(function(a){k.trigger("selection:update",{data:a})}),a.addClass("select2-hidden-accessible"),a.attr("aria-hidden","true"),this._syncAttributes(),a.data("select2",this)};return c.Extend(e,c.Observable),e.prototype._generateId=function(a){var b="";return b=null!=a.attr("id")?a.attr("id"):null!=a.attr("name")?a.attr("name")+"-"+c.generateChars(2):c.generateChars(4),b=b.replace(/(:|\.|\[|\]|,)/g,""),b="select2-"+b},e.prototype._placeContainer=function(a){a.insertAfter(this.$element);var b=this._resolveWidth(this.$element,this.options.get("width"));null!=b&&a.css("width",b)},e.prototype._resolveWidth=function(a,b){var c=/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;if("resolve"==b){var d=this._resolveWidth(a,"style");return null!=d?d:this._resolveWidth(a,"element")}if("element"==b){var e=a.outerWidth(!1);return 0>=e?"auto":e+"px"}if("style"==b){var f=a.attr("style");if("string"!=typeof f)return null;for(var g=f.split(";"),h=0,i=g.length;i>h;h+=1){var j=g[h].replace(/\s/g,""),k=j.match(c);if(null!==k&&k.length>=1)return k[1]}return null}return b},e.prototype._bindAdapters=function(){this.dataAdapter.bind(this,this.$container),this.selection.bind(this,this.$container),this.dropdown.bind(this,this.$container),this.results.bind(this,this.$container)},e.prototype._registerDomEvents=function(){var b=this;this.$element.on("change.select2",function(){b.dataAdapter.current(function(a){b.trigger("selection:update",{data:a})})}),this.$element.on("focus.select2",function(a){b.trigger("focus",a)}),this._syncA=c.bind(this._syncAttributes,this),this._syncS=c.bind(this._syncSubtree,this),this.$element[0].attachEvent&&this.$element[0].attachEvent("onpropertychange",this._syncA);var d=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver;null!=d?(this._observer=new d(function(c){a.each(c,b._syncA),a.each(c,b._syncS)}),this._observer.observe(this.$element[0],{attributes:!0,childList:!0,subtree:!1})):this.$element[0].addEventListener&&(this.$element[0].addEventListener("DOMAttrModified",b._syncA,!1),this.$element[0].addEventListener("DOMNodeInserted",b._syncS,!1),this.$element[0].addEventListener("DOMNodeRemoved",b._syncS,!1))},e.prototype._registerDataEvents=function(){var a=this;this.dataAdapter.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerSelectionEvents=function(){var b=this,c=["toggle","focus"];this.selection.on("toggle",function(){b.toggleDropdown()}),this.selection.on("focus",function(a){b.focus(a)}),this.selection.on("*",function(d,e){-1===a.inArray(d,c)&&b.trigger(d,e)})},e.prototype._registerDropdownEvents=function(){var a=this;this.dropdown.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerResultsEvents=function(){var a=this;this.results.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerEvents=function(){var a=this;this.on("open",function(){a.$container.addClass("select2-container--open")}),this.on("close",function(){a.$container.removeClass("select2-container--open")}),this.on("enable",function(){a.$container.removeClass("select2-container--disabled")}),this.on("disable",function(){a.$container.addClass("select2-container--disabled")}),this.on("blur",function(){a.$container.removeClass("select2-container--focus")}),this.on("query",function(b){a.isOpen()||a.trigger("open",{}),this.dataAdapter.query(b,function(c){a.trigger("results:all",{data:c,query:b})})}),this.on("query:append",function(b){this.dataAdapter.query(b,function(c){a.trigger("results:append",{data:c,query:b})})}),this.on("keypress",function(b){var c=b.which;a.isOpen()?c===d.ESC||c===d.TAB||c===d.UP&&b.altKey?(a.close(),b.preventDefault()):c===d.ENTER?(a.trigger("results:select",{}),b.preventDefault()):c===d.SPACE&&b.ctrlKey?(a.trigger("results:toggle",{}),b.preventDefault()):c===d.UP?(a.trigger("results:previous",{}),b.preventDefault()):c===d.DOWN&&(a.trigger("results:next",{}),b.preventDefault()):(c===d.ENTER||c===d.SPACE||c===d.DOWN&&b.altKey)&&(a.open(),b.preventDefault())})},e.prototype._syncAttributes=function(){this.options.set("disabled",this.$element.prop("disabled")),this.options.get("disabled")?(this.isOpen()&&this.close(),this.trigger("disable",{})):this.trigger("enable",{})},e.prototype._syncSubtree=function(a,b){var c=!1,d=this;if(!a||!a.target||"OPTION"===a.target.nodeName||"OPTGROUP"===a.target.nodeName){if(b)if(b.addedNodes&&b.addedNodes.length>0)for(var e=0;e0&&(c=!0);else c=!0;c&&this.dataAdapter.current(function(a){d.trigger("selection:update",{data:a})})}},e.prototype.trigger=function(a,b){var c=e.__super__.trigger,d={open:"opening",close:"closing",select:"selecting",unselect:"unselecting"};if(void 0===b&&(b={}),a in d){var f=d[a],g={prevented:!1,name:a,args:b};if(c.call(this,f,g),g.prevented)return void(b.prevented=!0)}c.call(this,a,b)},e.prototype.toggleDropdown=function(){this.options.get("disabled")||(this.isOpen()?this.close():this.open())},e.prototype.open=function(){this.isOpen()||this.trigger("query",{})},e.prototype.close=function(){this.isOpen()&&this.trigger("close",{})},e.prototype.isOpen=function(){return this.$container.hasClass("select2-container--open")},e.prototype.hasFocus=function(){return this.$container.hasClass("select2-container--focus")},e.prototype.focus=function(a){this.hasFocus()||(this.$container.addClass("select2-container--focus"),this.trigger("focus",{}))},e.prototype.enable=function(a){this.options.get("debug")&&window.console&&console.warn&&console.warn('Select2: The `select2("enable")` method has been deprecated and will be removed in later Select2 versions. Use $element.prop("disabled") instead.'),(null==a||0===a.length)&&(a=[!0]);var b=!a[0];this.$element.prop("disabled",b)},e.prototype.data=function(){this.options.get("debug")&&arguments.length>0&&window.console&&console.warn&&console.warn('Select2: Data can no longer be set using `select2("data")`. You should consider setting the value instead using `$element.val()`.');var a=[];return this.dataAdapter.current(function(b){a=b}),a},e.prototype.val=function(b){if(this.options.get("debug")&&window.console&&console.warn&&console.warn('Select2: The `select2("val")` method has been deprecated and will be removed in later Select2 versions. Use $element.val() instead.'),null==b||0===b.length)return this.$element.val();var c=b[0];a.isArray(c)&&(c=a.map(c,function(a){return a.toString()})),this.$element.val(c).trigger("change")},e.prototype.destroy=function(){this.$container.remove(),this.$element[0].detachEvent&&this.$element[0].detachEvent("onpropertychange",this._syncA),null!=this._observer?(this._observer.disconnect(),this._observer=null):this.$element[0].removeEventListener&&(this.$element[0].removeEventListener("DOMAttrModified",this._syncA,!1),this.$element[0].removeEventListener("DOMNodeInserted",this._syncS,!1),this.$element[0].removeEventListener("DOMNodeRemoved",this._syncS,!1)),this._syncA=null,this._syncS=null,this.$element.off(".select2"),this.$element.attr("tabindex",this.$element.data("old-tabindex")),this.$element.removeClass("select2-hidden-accessible"),this.$element.attr("aria-hidden","false"),this.$element.removeData("select2"),this.dataAdapter.destroy(),this.selection.destroy(),this.dropdown.destroy(),this.results.destroy(),this.dataAdapter=null,this.selection=null,this.dropdown=null,this.results=null; -},e.prototype.render=function(){var b=a('');return b.attr("dir",this.options.get("dir")),this.$container=b,this.$container.addClass("select2-container--"+this.options.get("theme")),b.data("element",this.$element),b},e}),b.define("select2/compat/utils",["jquery"],function(a){function b(b,c,d){var e,f,g=[];e=a.trim(b.attr("class")),e&&(e=""+e,a(e.split(/\s+/)).each(function(){0===this.indexOf("select2-")&&g.push(this)})),e=a.trim(c.attr("class")),e&&(e=""+e,a(e.split(/\s+/)).each(function(){0!==this.indexOf("select2-")&&(f=d(this),null!=f&&g.push(f))})),b.attr("class",g.join(" "))}return{syncCssClasses:b}}),b.define("select2/compat/containerCss",["jquery","./utils"],function(a,b){function c(a){return null}function d(){}return d.prototype.render=function(d){var e=d.call(this),f=this.options.get("containerCssClass")||"";a.isFunction(f)&&(f=f(this.$element));var g=this.options.get("adaptContainerCssClass");if(g=g||c,-1!==f.indexOf(":all:")){f=f.replace(":all:","");var h=g;g=function(a){var b=h(a);return null!=b?b+" "+a:a}}var i=this.options.get("containerCss")||{};return a.isFunction(i)&&(i=i(this.$element)),b.syncCssClasses(e,this.$element,g),e.css(i),e.addClass(f),e},d}),b.define("select2/compat/dropdownCss",["jquery","./utils"],function(a,b){function c(a){return null}function d(){}return d.prototype.render=function(d){var e=d.call(this),f=this.options.get("dropdownCssClass")||"";a.isFunction(f)&&(f=f(this.$element));var g=this.options.get("adaptDropdownCssClass");if(g=g||c,-1!==f.indexOf(":all:")){f=f.replace(":all:","");var h=g;g=function(a){var b=h(a);return null!=b?b+" "+a:a}}var i=this.options.get("dropdownCss")||{};return a.isFunction(i)&&(i=i(this.$element)),b.syncCssClasses(e,this.$element,g),e.css(i),e.addClass(f),e},d}),b.define("select2/compat/initSelection",["jquery"],function(a){function b(a,b,c){c.get("debug")&&window.console&&console.warn&&console.warn("Select2: The `initSelection` option has been deprecated in favor of a custom data adapter that overrides the `current` method. This method is now called multiple times instead of a single time when the instance is initialized. Support will be removed for the `initSelection` option in future versions of Select2"),this.initSelection=c.get("initSelection"),this._isInitialized=!1,a.call(this,b,c)}return b.prototype.current=function(b,c){var d=this;return this._isInitialized?void b.call(this,c):void this.initSelection.call(null,this.$element,function(b){d._isInitialized=!0,a.isArray(b)||(b=[b]),c(b)})},b}),b.define("select2/compat/inputData",["jquery"],function(a){function b(a,b,c){this._currentData=[],this._valueSeparator=c.get("valueSeparator")||",","hidden"===b.prop("type")&&c.get("debug")&&console&&console.warn&&console.warn("Select2: Using a hidden input with Select2 is no longer supported and may stop working in the future. It is recommended to use a `');this.$searchContainer=t,this.$search=t.find("input");var n=e.call(this);return this._transferTabIndex(),n},e.prototype.bind=function(e,t,n){var i=this,r=t.id+"-results";e.call(this,t,n),t.on("open",function(){i.$search.attr("aria-controls",r),i.$search.trigger("focus")}),t.on("close",function(){i.$search.val(""),i.$search.removeAttr("aria-controls"),i.$search.removeAttr("aria-activedescendant"),i.$search.trigger("focus")}),t.on("enable",function(){i.$search.prop("disabled",!1),i._transferTabIndex()}),t.on("disable",function(){i.$search.prop("disabled",!0)}),t.on("focus",function(e){i.$search.trigger("focus")}),t.on("results:focus",function(e){e.data._resultId?i.$search.attr("aria-activedescendant",e.data._resultId):i.$search.removeAttr("aria-activedescendant")}),this.$selection.on("focusin",".select2-search--inline",function(e){i.trigger("focus",e)}),this.$selection.on("focusout",".select2-search--inline",function(e){i._handleBlur(e)}),this.$selection.on("keydown",".select2-search--inline",function(e){if(e.stopPropagation(),i.trigger("keypress",e),i._keyUpPrevented=e.isDefaultPrevented(),e.which===l.BACKSPACE&&""===i.$search.val()){var t=i.$searchContainer.prev(".select2-selection__choice");if(0this.maximumInputLength?this.trigger("results:message",{message:"inputTooLong",args:{maximum:this.maximumInputLength,input:t.term,params:t}}):e.call(this,t,n)},e}),e.define("select2/data/maximumSelectionLength",[],function(){function e(e,t,n){this.maximumSelectionLength=n.get("maximumSelectionLength"),e.call(this,t,n)}return e.prototype.bind=function(e,t,n){var i=this;e.call(this,t,n),t.on("select",function(){i._checkIfMaximumSelected()})},e.prototype.query=function(e,t,n){var i=this;this._checkIfMaximumSelected(function(){e.call(i,t,n)})},e.prototype._checkIfMaximumSelected=function(e,n){var i=this;this.current(function(e){var t=null!=e?e.length:0;0=i.maximumSelectionLength?i.trigger("results:message",{message:"maximumSelected",args:{maximum:i.maximumSelectionLength}}):n&&n()})},e}),e.define("select2/dropdown",["jquery","./utils"],function(t,e){function n(e,t){this.$element=e,this.options=t,n.__super__.constructor.call(this)}return e.Extend(n,e.Observable),n.prototype.render=function(){var e=t('');return e.attr("dir",this.options.get("dir")),this.$dropdown=e},n.prototype.bind=function(){},n.prototype.position=function(e,t){},n.prototype.destroy=function(){this.$dropdown.remove()},n}),e.define("select2/dropdown/search",["jquery","../utils"],function(o,e){function t(){}return t.prototype.render=function(e){var t=e.call(this),n=o('');return this.$searchContainer=n,this.$search=n.find("input"),t.prepend(n),t},t.prototype.bind=function(e,t,n){var i=this,r=t.id+"-results";e.call(this,t,n),this.$search.on("keydown",function(e){i.trigger("keypress",e),i._keyUpPrevented=e.isDefaultPrevented()}),this.$search.on("input",function(e){o(this).off("keyup")}),this.$search.on("keyup input",function(e){i.handleSearch(e)}),t.on("open",function(){i.$search.attr("tabindex",0),i.$search.attr("aria-controls",r),i.$search.trigger("focus"),window.setTimeout(function(){i.$search.trigger("focus")},0)}),t.on("close",function(){i.$search.attr("tabindex",-1),i.$search.removeAttr("aria-controls"),i.$search.removeAttr("aria-activedescendant"),i.$search.val(""),i.$search.trigger("blur")}),t.on("focus",function(){t.isOpen()||i.$search.trigger("focus")}),t.on("results:all",function(e){null!=e.query.term&&""!==e.query.term||(i.showSearch(e)?i.$searchContainer.removeClass("select2-search--hide"):i.$searchContainer.addClass("select2-search--hide"))}),t.on("results:focus",function(e){e.data._resultId?i.$search.attr("aria-activedescendant",e.data._resultId):i.$search.removeAttr("aria-activedescendant")})},t.prototype.handleSearch=function(e){if(!this._keyUpPrevented){var t=this.$search.val();this.trigger("query",{term:t})}this._keyUpPrevented=!1},t.prototype.showSearch=function(e,t){return!0},t}),e.define("select2/dropdown/hidePlaceholder",[],function(){function e(e,t,n,i){this.placeholder=this.normalizePlaceholder(n.get("placeholder")),e.call(this,t,n,i)}return e.prototype.append=function(e,t){t.results=this.removePlaceholder(t.results),e.call(this,t)},e.prototype.normalizePlaceholder=function(e,t){return"string"==typeof t&&(t={id:"",text:t}),t},e.prototype.removePlaceholder=function(e,t){for(var n=t.slice(0),i=t.length-1;0<=i;i--){var r=t[i];this.placeholder.id===r.id&&n.splice(i,1)}return n},e}),e.define("select2/dropdown/infiniteScroll",["jquery"],function(n){function e(e,t,n,i){this.lastParams={},e.call(this,t,n,i),this.$loadingMore=this.createLoadingMore(),this.loading=!1}return e.prototype.append=function(e,t){this.$loadingMore.remove(),this.loading=!1,e.call(this,t),this.showLoadingMore(t)&&(this.$results.append(this.$loadingMore),this.loadMoreIfNeeded())},e.prototype.bind=function(e,t,n){var i=this;e.call(this,t,n),t.on("query",function(e){i.lastParams=e,i.loading=!0}),t.on("query:append",function(e){i.lastParams=e,i.loading=!0}),this.$results.on("scroll",this.loadMoreIfNeeded.bind(this))},e.prototype.loadMoreIfNeeded=function(){var e=n.contains(document.documentElement,this.$loadingMore[0]);if(!this.loading&&e){var t=this.$results.offset().top+this.$results.outerHeight(!1);this.$loadingMore.offset().top+this.$loadingMore.outerHeight(!1)<=t+50&&this.loadMore()}},e.prototype.loadMore=function(){this.loading=!0;var e=n.extend({},{page:1},this.lastParams);e.page++,this.trigger("query:append",e)},e.prototype.showLoadingMore=function(e,t){return t.pagination&&t.pagination.more},e.prototype.createLoadingMore=function(){var e=n('
    • '),t=this.options.get("translations").get("loadingMore");return e.html(t(this.lastParams)),e},e}),e.define("select2/dropdown/attachBody",["jquery","../utils"],function(f,a){function e(e,t,n){this.$dropdownParent=f(n.get("dropdownParent")||document.body),e.call(this,t,n)}return e.prototype.bind=function(e,t,n){var i=this;e.call(this,t,n),t.on("open",function(){i._showDropdown(),i._attachPositioningHandler(t),i._bindContainerResultHandlers(t)}),t.on("close",function(){i._hideDropdown(),i._detachPositioningHandler(t)}),this.$dropdownContainer.on("mousedown",function(e){e.stopPropagation()})},e.prototype.destroy=function(e){e.call(this),this.$dropdownContainer.remove()},e.prototype.position=function(e,t,n){t.attr("class",n.attr("class")),t.removeClass("select2"),t.addClass("select2-container--open"),t.css({position:"absolute",top:-999999}),this.$container=n},e.prototype.render=function(e){var t=f(""),n=e.call(this);return t.append(n),this.$dropdownContainer=t},e.prototype._hideDropdown=function(e){this.$dropdownContainer.detach()},e.prototype._bindContainerResultHandlers=function(e,t){if(!this._containerResultsHandlersBound){var n=this;t.on("results:all",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("results:append",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("results:message",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("select",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("unselect",function(){n._positionDropdown(),n._resizeDropdown()}),this._containerResultsHandlersBound=!0}},e.prototype._attachPositioningHandler=function(e,t){var n=this,i="scroll.select2."+t.id,r="resize.select2."+t.id,o="orientationchange.select2."+t.id,s=this.$container.parents().filter(a.hasScroll);s.each(function(){a.StoreData(this,"select2-scroll-position",{x:f(this).scrollLeft(),y:f(this).scrollTop()})}),s.on(i,function(e){var t=a.GetData(this,"select2-scroll-position");f(this).scrollTop(t.y)}),f(window).on(i+" "+r+" "+o,function(e){n._positionDropdown(),n._resizeDropdown()})},e.prototype._detachPositioningHandler=function(e,t){var n="scroll.select2."+t.id,i="resize.select2."+t.id,r="orientationchange.select2."+t.id;this.$container.parents().filter(a.hasScroll).off(n),f(window).off(n+" "+i+" "+r)},e.prototype._positionDropdown=function(){var e=f(window),t=this.$dropdown.hasClass("select2-dropdown--above"),n=this.$dropdown.hasClass("select2-dropdown--below"),i=null,r=this.$container.offset();r.bottom=r.top+this.$container.outerHeight(!1);var o={height:this.$container.outerHeight(!1)};o.top=r.top,o.bottom=r.top+o.height;var s=this.$dropdown.outerHeight(!1),a=e.scrollTop(),l=e.scrollTop()+e.height(),c=ar.bottom+s,d={left:r.left,top:o.bottom},p=this.$dropdownParent;"static"===p.css("position")&&(p=p.offsetParent());var h=p.offset();d.top-=h.top,d.left-=h.left,t||n||(i="below"),u||!c||t?!c&&u&&t&&(i="below"):i="above",("above"==i||t&&"below"!==i)&&(d.top=o.top-h.top-s),null!=i&&(this.$dropdown.removeClass("select2-dropdown--below select2-dropdown--above").addClass("select2-dropdown--"+i),this.$container.removeClass("select2-container--below select2-container--above").addClass("select2-container--"+i)),this.$dropdownContainer.css(d)},e.prototype._resizeDropdown=function(){var e={width:this.$container.outerWidth(!1)+"px"};this.options.get("dropdownAutoWidth")&&(e.minWidth=e.width,e.position="relative",e.width="auto"),this.$dropdown.css(e)},e.prototype._showDropdown=function(e){this.$dropdownContainer.appendTo(this.$dropdownParent),this._positionDropdown(),this._resizeDropdown()},e}),e.define("select2/dropdown/minimumResultsForSearch",[],function(){function e(e,t,n,i){this.minimumResultsForSearch=n.get("minimumResultsForSearch"),this.minimumResultsForSearch<0&&(this.minimumResultsForSearch=1/0),e.call(this,t,n,i)}return e.prototype.showSearch=function(e,t){return!(function e(t){for(var n=0,i=0;i');return e.attr("dir",this.options.get("dir")),this.$container=e,this.$container.addClass("select2-container--"+this.options.get("theme")),u.StoreData(e[0],"element",this.$element),e},d}),e.define("select2/compat/utils",["jquery"],function(s){return{syncCssClasses:function(e,t,n){var i,r,o=[];(i=s.trim(e.attr("class")))&&s((i=""+i).split(/\s+/)).each(function(){0===this.indexOf("select2-")&&o.push(this)}),(i=s.trim(t.attr("class")))&&s((i=""+i).split(/\s+/)).each(function(){0!==this.indexOf("select2-")&&null!=(r=n(this))&&o.push(r)}),e.attr("class",o.join(" "))}}}),e.define("select2/compat/containerCss",["jquery","./utils"],function(s,a){function l(e){return null}function e(){}return e.prototype.render=function(e){var t=e.call(this),n=this.options.get("containerCssClass")||"";s.isFunction(n)&&(n=n(this.$element));var i=this.options.get("adaptContainerCssClass");if(i=i||l,-1!==n.indexOf(":all:")){n=n.replace(":all:","");var r=i;i=function(e){var t=r(e);return null!=t?t+" "+e:e}}var o=this.options.get("containerCss")||{};return s.isFunction(o)&&(o=o(this.$element)),a.syncCssClasses(t,this.$element,i),t.css(o),t.addClass(n),t},e}),e.define("select2/compat/dropdownCss",["jquery","./utils"],function(s,a){function l(e){return null}function e(){}return e.prototype.render=function(e){var t=e.call(this),n=this.options.get("dropdownCssClass")||"";s.isFunction(n)&&(n=n(this.$element));var i=this.options.get("adaptDropdownCssClass");if(i=i||l,-1!==n.indexOf(":all:")){n=n.replace(":all:","");var r=i;i=function(e){var t=r(e);return null!=t?t+" "+e:e}}var o=this.options.get("dropdownCss")||{};return s.isFunction(o)&&(o=o(this.$element)),a.syncCssClasses(t,this.$element,i),t.css(o),t.addClass(n),t},e}),e.define("select2/compat/initSelection",["jquery"],function(i){function e(e,t,n){n.get("debug")&&window.console&&console.warn&&console.warn("Select2: The `initSelection` option has been deprecated in favor of a custom data adapter that overrides the `current` method. This method is now called multiple times instead of a single time when the instance is initialized. Support will be removed for the `initSelection` option in future versions of Select2"),this.initSelection=n.get("initSelection"),this._isInitialized=!1,e.call(this,t,n)}return e.prototype.current=function(e,t){var n=this;this._isInitialized?e.call(this,t):this.initSelection.call(null,this.$element,function(e){n._isInitialized=!0,i.isArray(e)||(e=[e]),t(e)})},e}),e.define("select2/compat/inputData",["jquery","../utils"],function(s,i){function e(e,t,n){this._currentData=[],this._valueSeparator=n.get("valueSeparator")||",","hidden"===t.prop("type")&&n.get("debug")&&console&&console.warn&&console.warn("Select2: Using a hidden input with Select2 is no longer supported and may stop working in the future. It is recommended to use a `